Showing preview only (929K chars total). Download the full file or copy to clipboard to get everything.
Repository: sigstore/sigstore-python
Branch: main
Commit: 3a19f924b4f5
Files: 207
Total size: 869.0 KB
Directory structure:
gitextract_rwb714tf/
├── .gitattributes
├── .github/
│ ├── actions/
│ │ └── upload-coverage/
│ │ └── action.yml
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── check-embedded-root.yml
│ ├── ci.yml
│ ├── conformance.yml
│ ├── cross-os.yml
│ ├── cross-version-verify.yaml
│ ├── depsreview.yml
│ ├── docs.yml
│ ├── lint.yml
│ ├── pin-requirements.yml
│ ├── release.yml
│ ├── requirements.yml
│ ├── scorecards-analysis.yml
│ └── staging-tests.yml
├── .gitignore
├── CHANGELOG.md
├── CODEOWNERS
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── cloudbuild.yaml
├── docs/
│ ├── advanced/
│ │ ├── custom_trust.md
│ │ └── offline.md
│ ├── api/
│ │ ├── errors.md
│ │ ├── hashes.md
│ │ ├── index.md
│ │ ├── models.md
│ │ ├── oidc.md
│ │ ├── sign.md
│ │ └── verify/
│ │ ├── policy.md
│ │ └── verifier.md
│ ├── index.md
│ ├── installation.md
│ ├── policy.md
│ ├── scripts/
│ │ └── gen_ref_pages.py
│ ├── signing.md
│ ├── stylesheets/
│ │ └── custom.css
│ └── verify.md
├── install/
│ └── requirements.in
├── mkdocs.yml
├── pyproject.toml
├── sigstore/
│ ├── __init__.py
│ ├── __main__.py
│ ├── _cli.py
│ ├── _internal/
│ │ ├── __init__.py
│ │ ├── fulcio/
│ │ │ ├── __init__.py
│ │ │ └── client.py
│ │ ├── key_details.py
│ │ ├── merkle.py
│ │ ├── oidc/
│ │ │ ├── __init__.py
│ │ │ └── oauth.py
│ │ ├── rekor/
│ │ │ ├── __init__.py
│ │ │ ├── checkpoint.py
│ │ │ ├── client.py
│ │ │ └── client_v2.py
│ │ ├── sct.py
│ │ ├── timestamp.py
│ │ ├── trust.py
│ │ └── tuf.py
│ ├── _store/
│ │ ├── __init__.py
│ │ ├── https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/
│ │ │ ├── root.json
│ │ │ ├── signing_config.v0.2.json
│ │ │ └── trusted_root.json
│ │ └── https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/
│ │ ├── root.json
│ │ ├── signing_config.v0.2.json
│ │ └── trusted_root.json
│ ├── _utils.py
│ ├── dsse/
│ │ ├── __init__.py
│ │ └── _predicate.py
│ ├── errors.py
│ ├── hashes.py
│ ├── models.py
│ ├── oidc.py
│ ├── py.typed
│ ├── sign.py
│ └── verify/
│ ├── __init__.py
│ ├── policy.py
│ └── verifier.py
└── test/
├── assets/
│ ├── a.dsse.staging-rekor-v2.txt
│ ├── a.dsse.staging-rekor-v2.txt.sigstore.json
│ ├── a.txt
│ ├── a.txt.crt
│ ├── a.txt.sig
│ ├── b.txt
│ ├── b.txt.crt
│ ├── b.txt.sig
│ ├── bad.txt
│ ├── bad.txt.crt
│ ├── bad.txt.sig
│ ├── bundle.txt
│ ├── bundle.txt.crt
│ ├── bundle.txt.sig
│ ├── bundle.txt.sigstore
│ ├── bundle_cve_2022_36056.txt
│ ├── bundle_cve_2022_36056.txt.sigstore
│ ├── bundle_invalid_version.txt
│ ├── bundle_invalid_version.txt.sigstore
│ ├── bundle_no_cert_v1.txt
│ ├── bundle_no_cert_v1.txt.sigstore
│ ├── bundle_no_checkpoint.txt
│ ├── bundle_no_checkpoint.txt.bundle
│ ├── bundle_no_checkpoint.txt.crt
│ ├── bundle_no_checkpoint.txt.sigstore
│ ├── bundle_no_log_entry.txt
│ ├── bundle_no_log_entry.txt.sigstore
│ ├── bundle_v3.txt
│ ├── bundle_v3.txt.sigstore
│ ├── bundle_v3_alt.txt
│ ├── bundle_v3_alt.txt.sigstore
│ ├── bundle_v3_github.whl
│ ├── bundle_v3_github.whl.sigstore
│ ├── bundle_v3_no_signed_time.txt
│ ├── bundle_v3_no_signed_time.txt.sigstore.json
│ ├── c.txt
│ ├── c.txt.crt
│ ├── c.txt.sig
│ ├── integration/
│ │ ├── Python-3.12.5.tgz.sigstore
│ │ ├── a.txt
│ │ ├── attest/
│ │ │ ├── slsa_predicate_v0_2.json
│ │ │ └── slsa_predicate_v1_0.json
│ │ ├── b.txt
│ │ ├── bundle_v3.txt
│ │ ├── bundle_v3.txt.sigstore
│ │ └── c.txt
│ ├── offline-rekor.txt
│ ├── offline-rekor.txt.crt
│ ├── offline-rekor.txt.sig
│ ├── signing_config/
│ │ ├── signingconfig-only-v1-rekor.v2.json
│ │ └── signingconfig.v2.json
│ ├── staging-rekor-v2.txt
│ ├── staging-rekor-v2.txt.sigstore.json
│ ├── staging-tuf/
│ │ ├── 16.snapshot.json
│ │ ├── 17.targets.json
│ │ ├── targets/
│ │ │ ├── 0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem
│ │ │ ├── 0f395087486ba318321eda478d847962b1dd89846c7dc6e95752a6b110669393.signing_config.v0.2.json
│ │ │ ├── 1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub
│ │ │ ├── 7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub
│ │ │ ├── 782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem
│ │ │ └── ed6a9cf4e7c2e3297a4b5974fce0d17132f03c63512029d7aa3a402b43acab49.trusted_root.json
│ │ └── timestamp.json
│ ├── trust_config/
│ │ ├── config.badtype.json
│ │ └── config.v1.json
│ ├── trusted_root/
│ │ ├── certificate_authority.empty.json
│ │ ├── certificate_authority.json
│ │ ├── certificate_authority.missingroot.json
│ │ ├── trustedroot.badtype.json
│ │ ├── trustedroot.v1.json
│ │ └── trustedroot.v1.local_tlog_ed25519_rekor-tiles.json
│ ├── tsa/
│ │ ├── bundle.duplicate.sigstore
│ │ ├── bundle.many_timestamp.sigstore
│ │ ├── bundle.txt
│ │ ├── bundle.txt.late_timestamp.sigstore
│ │ ├── bundle.txt.sigstore
│ │ ├── ca.json
│ │ ├── issue1482-message
│ │ ├── issue1482-timestamp-with-no-cert
│ │ └── trust_config.json
│ └── x509/
│ ├── bogus-intermediate-with-eku.pem
│ ├── bogus-intermediate.pem
│ ├── bogus-leaf-invalid-eku.pem
│ ├── bogus-leaf-invalid-ku.pem
│ ├── bogus-leaf-missing-eku.pem
│ ├── bogus-leaf.pem
│ ├── bogus-root-invalid-ku.pem
│ ├── bogus-root-missing-ku.pem
│ ├── bogus-root-noncritical-bc.pem
│ ├── bogus-root.pem
│ ├── build-testcases.py
│ ├── nonroot-privkey.pem
│ └── root-privkey.pem
├── conftest.py
├── integration/
│ ├── cli/
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_attest.py
│ │ ├── test_plumbing.py
│ │ ├── test_sign.py
│ │ └── test_verify.py
│ └── sigstore-python-conformance
└── unit/
├── __init__.py
├── conftest.py
├── internal/
│ ├── __init__.py
│ ├── fulcio/
│ │ ├── __init__.py
│ │ └── test_client.py
│ ├── oidc/
│ │ ├── __init__.py
│ │ └── test_issuer.py
│ ├── rekor/
│ │ ├── __init__.py
│ │ └── test_client_v2.py
│ ├── test_key_details.py
│ ├── test_sct.py
│ ├── test_timestamping.py
│ └── test_trust.py
├── test_dsse.py
├── test_hashes.py
├── test_models.py
├── test_oidc.py
├── test_session_reuse.py
├── test_sign.py
├── test_store.py
├── test_utils.py
├── test_version.py
└── verify/
├── __init__.py
├── test_policy.py
└── test_verifier.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# These directories contain TUF and other assets that are either digested
# or sized-checked so CRLF normalization breaks them.
sigstore/_store/** binary diff=text
test/assets/** binary diff=text
test/assets/x509/** -binary
================================================
FILE: .github/actions/upload-coverage/action.yml
================================================
# Derived from <https://github.com/pyca/cryptography/blob/SOME_REF/.github/actions/upload-coverage/action.yml>
# Originally authored by the PyCA Cryptography maintainers, and licensed under
# the terms of the BSD license:
# <https://github.com/pyca/cryptography/blob/main/LICENSE.BSD>
name: Upload Coverage
description: Upload coverage files
runs:
using: "composite"
steps:
# FIXME(jl): codecov has the option of including machine information in filename that would solve this unique naming
# issue more completely.
- run: |
COVERAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "COVERAGE_UUID=${COVERAGE_UUID}" >> $GITHUB_OUTPUT
if [ -f .coverage ]; then
mv .coverage .coverage.${COVERAGE_UUID}
fi
id: coverage-uuid
shell: bash
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }}
include-hidden-files: 'true'
path: |
.coverage.*
*.lcov
if-no-files-found: ignore
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
directory: /
schedule:
interval: daily
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
open-pull-requests-limit: 99
rebase-strategy: "disabled"
groups:
actions:
patterns:
- "*"
- package-ecosystem: github-actions
directory: .github/actions/upload-coverage/
schedule:
interval: daily
open-pull-requests-limit: 99
rebase-strategy: "disabled"
groups:
actions:
patterns:
- "*"
================================================
FILE: .github/pull_request_template.md
================================================
<!--
Thanks for opening a pull request! Please do not just delete this text. The three fields below are mandatory.
Please remember to:
- This PR requires an issue. If it is a new feature, the issue should proceed the PR and will have allowed sufficient time for discussions to take place. Use
issue tags such as "Closes #XYZ" or "Resolves sigstore/repo-name#XYZ".
- add [documentation](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
- ensure your commits are signed-off, as sigstore uses the [DCO](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) using `git commit -s`, or `git commit -s --amend` if you want to amend already existing commits
Thank you :)
-->
#### Summary
<!--
Explain the **motivation** for making this change. What existing problem does the pull request solve? How can reviewers test this PR?
-->
#### Release Note
<!--
Add a release note for each of the following conditions in CHANGELOG.md:
* Config changes (additions, deletions, updates)
* API additions—new endpoint, new response fields, or newly accepted request parameters
* Database changes (any)
* Websocket additions or changes
* Anything noteworthy to an administrator running private sigstore instances (err on the side of over-communicating)
* New features and improvements, including behavioural changes, UI changes and CLI changes
* Bug fixes and fixes of previous known issues
* Deprecation warnings, breaking changes, or compatibility notes
Use past-tense.
-->
#### Documentation
<!--
Does this change require an update to documentation? How will users implement your new feature?
Please reference a PR within https://docs.sigstore.dev
-->
================================================
FILE: .github/workflows/check-embedded-root.yml
================================================
name: Check embedded root
on:
workflow_dispatch:
schedule:
- cron: '13 13 * * 3'
jobs:
check-embedded-root:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: "pip"
cache-dependency-path: pyproject.toml
- name: Setup environment
run: make dev
- name: Check if embedded root is up-to-date
run: |
make update-embedded-root
git diff --exit-code
- if: failure()
name: Create an issue if embedded root is not up-to-date
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const repo = context.repo.owner + "/" + context.repo.repo
const body = `
The Sigstore [TUF repository](https://tuf-repo-cdn.sigstore.dev/) contents have changed: the data embedded
in sigstore-python sources can be updated. This is not urgent but will improve cold-cache performance.
Run \`make update-embedded-root\` to update the embedded data.
This issue was filed by _${context.workflow}_ [workflow run](${context.serverUrl}/${repo}/actions/runs/${context.runId}).
`
const issues = await github.rest.search.issuesAndPullRequests({
q: "label:embedded-root-update+state:open+type:issue+repo:" + repo,
})
if (issues.data.total_count > 0) {
console.log("Issue for embedded root update exists already.")
} else {
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: "Embedded TUF root is not up-to-date",
labels: ["embedded-root-update"],
body: body,
})
console.log("New issue created.")
}
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches:
- main
- series/*
pull_request:
schedule:
- cron: "0 11 * * *"
workflow_dispatch:
permissions: {}
jobs:
test:
# Avoid scheduled runs in forks
if: github.event_name != 'schedule' || github.repository == 'sigstore/sigstore-python'
permissions:
# Needed to access the workflow's OIDC identity.
id-token: write
strategy:
matrix:
conf:
- { py: "3.10", os: "ubuntu-latest" }
- { py: "3.11", os: "ubuntu-latest" }
- { py: "3.12", os: "ubuntu-latest" }
- { py: "3.13", os: "ubuntu-latest" }
- { py: "3.14", os: "ubuntu-latest" }
# NOTE: We only test Windows and macOS on the latest Python;
# these primarily exist to ensure that we don't accidentally
# introduce Linux-isms into the development tooling.
- { py: "3.14", os: "windows-latest" }
- { py: "3.14", os: "macos-latest" }
runs-on: ${{ matrix.conf.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.conf.py }}
allow-prereleases: true
cache: "pip"
cache-dependency-path: pyproject.toml
- name: deps
run: make dev SIGSTORE_EXTRA=test
- name: test (offline)
if: matrix.conf.os == 'ubuntu-latest'
run: |
# Look at me. I am the captain now.
sudo sysctl -w kernel.unprivileged_userns_clone=1
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
# We use `unshare` to "un-share" the default networking namespace,
# in effect running the tests as if the host is offline.
# This in turn effectively exercises the correctness of our
# "online-only" test markers, since any test that's online
# but not marked as such will fail.
# We also explicitly exclude the integration tests, since these are
# always online.
unshare --map-root-user --net make test T="test/unit" TEST_ARGS="--skip-online -vv --showlocals"
- name: test
run: make test TEST_ARGS="-vv --showlocals"
# TODO: Refactor this or remove it entirely once there's
# a suitable staging TSA instance.
- name: test (timestamp-authority)
if: ${{ matrix.conf.os == 'ubuntu-latest' }}
run: |
# Fetch the latest sigstore/timestamp-authority build
SIGSTORE_TIMESTAMP_VERSION=$(gh api /repos/sigstore/timestamp-authority/releases --jq '.[0].tag_name')
wget https://github.com/sigstore/timestamp-authority/releases/download/${SIGSTORE_TIMESTAMP_VERSION}/timestamp-server-linux-amd64 -O /tmp/timestamp-server
chmod +x /tmp/timestamp-server
# Run the TSA in background
/tmp/timestamp-server serve --port 3000 --disable-ntp-monitoring &
export TEST_SIGSTORE_TIMESTAMP_AUTHORITY_URL="http://localhost:3000/api/v1/timestamp"
# Ensure Timestamp Authority tests are not skipped by
# having pytest show skipped tests and verifying ours are running
set -o pipefail
make test TEST_ARGS="-m timestamp_authority -rs" | tee output
! grep -q "skipping test that requires a Timestamp Authority" output || (echo "ERROR: Found skip message" && exit 1)
env:
# Needed for `gh api` above.
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: test (interactive)
if: (github.event_name != 'pull_request') || !github.event.pull_request.head.repo.fork
run: make test-interactive TEST_ARGS="-vv --showlocals"
- uses: ./.github/actions/upload-coverage
# only aggregate test coverage over linux-based tests to avoid any OS-specific filesystem information stored in
# coverage metadata.
if: ${{ matrix.conf.os == 'ubuntu-latest' }}
all-tests-pass:
if: always() && (github.event_name != 'schedule' || github.repository == 'sigstore/sigstore-python')
needs:
- test
runs-on: ubuntu-latest
steps:
- name: check test jobs
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
coverage:
needs:
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
- run: pip install coverage[toml]
- name: download coverage data
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: all-artifacts/
- name: combine coverage data
id: combinecoverage
run: |
set +e
python -m coverage combine all-artifacts/coverage-data-*
echo "## python coverage" >> $GITHUB_STEP_SUMMARY
python -m coverage report -m --format=markdown >> $GITHUB_STEP_SUMMARY
================================================
FILE: .github/workflows/conformance.yml
================================================
name: Conformance Tests
on:
push:
branches:
- main
workflow_dispatch:
pull_request:
schedule:
- cron: "45 7 * * 1,4"
permissions: {}
jobs:
conformance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: "pip"
cache-dependency-path: pyproject.toml
- name: install sigstore-python
run: python -m pip install .
- uses: sigstore/sigstore-conformance@9611941d54398f2e3f6383b6f744442a56d2fb2a # v0.0.26
with:
entrypoint: ${{ github.workspace }}/test/integration/sigstore-python-conformance
xfail: "test_verify*intoto-with-custom-trust-root] test_verify*managed-key-happy-path] test_verify*managed-key-and-trusted-root]" # see issues 1442, 1244
file-issue-on-failure:
needs: [conformance]
if: failure() && github.event_name == 'schedule' && github.repository == 'sigstore/sigstore-python'
permissions:
issues: write # required to file an issue
runs-on: ubuntu-latest
steps:
- name: File an issue for conformance test failure
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[CI] Scheduled conformance test failed`,
body: `
A scheduled conformance test failed, see [run details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).
`
});
================================================
FILE: .github/workflows/cross-os.yml
================================================
# Copyright 2025 The Sigstore Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Cross-platform sign and verify
on:
push:
branches:
- main
- series/*
pull_request:
workflow_dispatch:
permissions: {}
defaults:
run:
shell: bash
jobs:
sign:
name: Sign on ${{ matrix.os }}
runs-on: ${{ matrix.os }}-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu, macos, windows]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: "pip"
cache-dependency-path: pyproject.toml
- run: pip install .
- name: Fetch testing oidc token
uses: sigstore-conformance/extremely-dangerous-public-oidc-beacon@4a8befcc16064dac9e97f210948d226e5c869bdc # v1.0.0
- name: Sign
run: python -m sigstore --staging sign --identity-token $(cat oidc-token.txt) test/assets/a.txt
- name: upload signature bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.os }}-bundle
path: test/assets/a.txt.sigstore.json
if-no-files-found: error
retention-days: 1
verify:
name: Verify ${{ matrix.signed-with-os }} bundle on ${{ matrix.os }}
if: ${{ always() }} # don't stop some verification if one of the signing jobs failed
needs: [sign]
runs-on: ${{ matrix.os }}-latest
strategy:
fail-fast: false # Don't cancel other jobs if one fails
matrix:
os: [ubuntu, macos, windows]
signed-with-os: [ubuntu, macos, windows]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: "pip"
cache-dependency-path: pyproject.toml
- run: pip install .
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.signed-with-os }}-bundle
- name: Verify
run: |
python -m sigstore --staging verify github --verbose \
--cert-identity "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" \
--bundle a.txt.sigstore.json \
test/assets/a.txt
================================================
FILE: .github/workflows/cross-version-verify.yaml
================================================
# Copyright 2025 The Sigstore Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Cross-version verify
on:
push:
branches:
- main
- series/*
pull_request:
workflow_dispatch:
permissions: {}
jobs:
sign:
name: Sign
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: "pip"
cache-dependency-path: pyproject.toml
- run: pip install .
- name: Fetch testing oidc token
uses: sigstore-conformance/extremely-dangerous-public-oidc-beacon@4a8befcc16064dac9e97f210948d226e5c869bdc # v1.0.0
- name: Sign
run: |
touch artifact
python -m sigstore --staging sign --bundle artifact-staging-rekor2.sigstore.json --identity-token $(cat oidc-token.txt) --rekor-version=2 artifact
python -m sigstore --staging sign --bundle artifact-staging-rekor1.sigstore.json --identity-token $(cat oidc-token.txt) --rekor-version=1 artifact
python -m sigstore sign --bundle artifact-prod-rekor1.sigstore.json --identity-token $(cat oidc-token.txt) --rekor-version=1 artifact
- name: upload signature bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: bundle
path: artifact*.sigstore.json
if-no-files-found: error
retention-days: 1
verify:
name: Verify with ${{ matrix.version }} on ${{ matrix.env }}
needs: [sign]
runs-on: ubuntu-latest
strategy:
fail-fast: false # Don't cancel other jobs if one fails
matrix:
# hand crafted list of old versions we care about
version: [3.5.6, 3.6.7, 4.0.0, 4.1.0, 4.2.0]
env: [staging, prod]
exclude:
# exclude staging for versions with https://github.com/sigstore/sigstore-python/issues/1656
- env: staging
version: 3.5.6
- env: staging
version: 4.0.0
- env: staging
version: 4.1.0
steps:
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
- run: pip install sigstore==${{ matrix.version }}
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: bundle
- run: touch artifact
- name: Verify (Rekor v2)
# Rekor v2 is currently only available on staging, and only supported on sigstore-python 4.x
if: startsWith(matrix.version, '3.') != true && matrix.env == 'staging'
env:
ENV_OPT: ${{ matrix.env == 'staging' && '--staging' || '' }}
BUNDLE: artifact-${{matrix.env}}-rekor2.sigstore.json
run: |
python -m sigstore $ENV_OPT verify github --verbose \
--cert-identity "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" \
--bundle $BUNDLE \
artifact
- name: Verify (Rekor v1)
env:
ENV_OPT: ${{ matrix.env == 'staging' && '--staging' || '' }}
BUNDLE: artifact-${{matrix.env}}-rekor1.sigstore.json
run: |
python -m sigstore $ENV_OPT verify github --verbose \
--cert-identity "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" \
--bundle $BUNDLE \
artifact
================================================
FILE: .github/workflows/depsreview.yml
================================================
#
# Copyright 2022 The Sigstore Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
name: License and Vulnerability Scan
uses: sigstore/community/.github/workflows/reusable-dependency-review.yml@9b1b5aca605f92ec5b1bf3681b1e61b3dbc420cc
================================================
FILE: .github/workflows/docs.yml
================================================
name: Documentation
on:
push:
branches:
- main
permissions: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: "pip"
cache-dependency-path: pyproject.toml
- name: setup
run: |
make dev SIGSTORE_EXTRA=doc
- name: build docs
run: |
make doc
- name: upload docs artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: ./html/
# This is copied from the official `pdoc` example:
# https://github.com/mitmproxy/pdoc/blob/main/.github/workflows/docs.yml
#
# Deploy the artifact to GitHub pages.
# This is a separate job so that only actions/deploy-pages has the necessary permissions.
deploy:
needs: build
if: github.repository == 'sigstore/sigstore-python'
runs-on: ubuntu-latest
permissions:
# NOTE: Needed to push to the repository.
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
permissions: {}
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: "pip"
cache-dependency-path: pyproject.toml
- name: deps
run: make dev SIGSTORE_EXTRA=lint
- name: lint
run: make lint
check-readme:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# NOTE: We intentionally check --help rendering against our minimum Python,
# since it changes slightly between Python versions.
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.10"
cache: "pip"
cache-dependency-path: pyproject.toml
- name: deps
run: make dev
- name: check-readme
run: make check-readme
licenses:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# adapted from Warehouse's bin/licenses
- run: |
for fn in $(find . -type f -name "*.py"); do
if [[ ! "$(head -5 $fn | grep "^ *\(#\|\*\|\/\/\) .* License\(d*\)")" ]]; then
echo "${fn} is missing a license"
exit 1
fi
done
x509-testcases:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# NOTE: We intentionally check test certificates against our minimum supported Python.
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.10"
cache: "pip"
cache-dependency-path: pyproject.toml
- name: deps
run: make dev
- name: ensure testcase generation does not regress
run: make gen-x509-testcases
all-lints-pass:
if: always()
needs:
- lint
- check-readme
- licenses
- x509-testcases
runs-on: ubuntu-latest
steps:
- name: check lint jobs
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
jobs: ${{ toJSON(needs) }}
================================================
FILE: .github/workflows/pin-requirements.yml
================================================
name: Pin Requirements
on:
workflow_dispatch:
inputs:
tag:
description: Tag to pin dependencies against.
required: false
type: string
workflow_call:
inputs:
tag:
description: Tag to pin dependencies against.
required: false
type: string
permissions:
contents: read
jobs:
update-pinned-requirements:
runs-on: ubuntu-latest
permissions:
contents: write # Branch creation for PR.
outputs:
sigstore-release-tag: ${{ steps.get-branch.outputs.sigstore-release-tag }}
sigstore-pin-requirements-branch: ${{ steps.get-branch.outputs.sigstore-pin-requirements-branch }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: main
# NOTE: Needed for `git describe` below.
fetch-depth: 0
fetch-tags: true
# NOTE: Needed to push back to the repo.
persist-credentials: true
- name: Get latest tag
run: |
latest_tag=$(git describe --tags --abbrev=0)
echo "LATEST_TAG=${latest_tag}" >> "${GITHUB_ENV}"
- name: Set SIGSTORE_RELEASE_TAG and SIGSTORE_NEW_BRANCH
id: get-branch
env:
INPUT_TAG: "${{ inputs.tag }}"
run: |
if [[ -n "${INPUT_TAG}" ]]; then
effective_tag="${INPUT_TAG}"
else
effective_tag="${LATEST_TAG}"
fi
# Environment
echo "SIGSTORE_RELEASE_TAG=${effective_tag}" >> "${GITHUB_ENV}"
echo "SIGSTORE_NEW_BRANCH=pin-requirements/sigstore/${effective_tag}" >> "${GITHUB_ENV}"
# Outputs
echo "sigstore-release-tag=${effective_tag}" >> "${GITHUB_OUTPUT}"
echo "sigstore-pin-requirements-branch=pin-requirements/sigstore/${effective_tag}" >> "${GITHUB_OUTPUT}"
- name: Configure git
run: |
# Set up committer info.
# https://github.com/orgs/community/discussions/26560
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version-file: install/.python-version
cache: "pip"
cache-dependency-path: pyproject.toml
- name: Install dependencies
run: pip install pip-tools
- name: Compute version from tag
run: |
echo "SIGSTORE_RELEASE_VERSION=$(echo "${SIGSTORE_RELEASE_TAG}" | sed 's/^v//')" >> "${GITHUB_ENV}"
- name: Update requirements
run: |
cd install
echo "sigstore==${SIGSTORE_RELEASE_VERSION}" > requirements.in
pip-compile --allow-unsafe --generate-hashes --upgrade --output-file=requirements.txt requirements.in
- name: Commit changes and push to branch
run: |
git commit --all -s -m "[BOT] install: update pinned requirements"
git push -f origin "main:${SIGSTORE_NEW_BRANCH}"
test-requirements:
needs: update-pinned-requirements
uses: ./.github/workflows/requirements.yml
with:
# We can't use `env` variables in this context.
# https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
ref: ${{ needs.update-pinned-requirements.outputs.sigstore-pin-requirements-branch }}
create-pr:
needs:
- update-pinned-requirements
- test-requirements
runs-on: ubuntu-latest
permissions:
contents: write # Pull Request branch modification.
pull-requests: write # Pull Request creation.
env:
SIGSTORE_RELEASE_TAG: ${{ needs.update-pinned-requirements.outputs.sigstore-release-tag }}
SIGSTORE_PIN_REQUIREMENTS_BRANCH: ${{ needs.update-pinned-requirements.outputs.sigstore-pin-requirements-branch }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ env.SIGSTORE_PIN_REQUIREMENTS_BRANCH }}
# NOTE: Needed to push back to the repo.
persist-credentials: true
- name: Reset remote PR branch
run: |
git fetch origin main
git push -f origin "origin/main:${SIGSTORE_PIN_REQUIREMENTS_BRANCH}"
- name: Open pull request
env:
GH_TOKEN: ${{ github.token }}
run: |
# Try to create a new PR, or update if it already exists
# NOTE: Branch deletion after merge is handled by repository settings
PR_TITLE="Update pinned requirements for ${SIGSTORE_RELEASE_TAG}"
PR_BODY="Pins dependencies for <https://github.com/sigstore/sigstore-python/releases/tag/${SIGSTORE_RELEASE_TAG}>."
if ! gh pr create \
--title "${PR_TITLE}" \
--body "${PR_BODY}" \
--base main \
--head "${SIGSTORE_PIN_REQUIREMENTS_BRANCH}"; then
# PR already exists, update it
gh pr edit "${SIGSTORE_PIN_REQUIREMENTS_BRANCH}" \
--title "${PR_TITLE}" \
--body "${PR_BODY}"
fi
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
release:
types:
- published
permissions: {}
jobs:
build:
name: Build and sign artifacts
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
# NOTE: We intentionally don't use a cache in the release step,
# to reduce the risk of cache poisoning.
python-version: "3.x"
- name: deps
run: python -m pip install -U build
- name: build
run: python -m build
- name: sign
run: |
mkdir -p smoketest-artifacts
# we smoke-test sigstore by installing each of the distributions
# we've built in a fresh environment and using each to sign and
# verify for itself, using the ambient OIDC identity
for dist in dist/*; do
dist_base="$(basename "${dist}")"
python -m venv smoketest-env
./smoketest-env/bin/python -m pip install "${dist}"
# NOTE: signing artifacts currently go in a separate directory,
# to avoid confusing the package uploader (which otherwise tries
# to upload them to PyPI and fails). Future versions of twine
# and the gh-action-pypi-publish action should support these artifacts.
./smoketest-env/bin/python -m \
sigstore sign "${dist}" \
--output-signature smoketest-artifacts/"${dist_base}.sig" \
--output-certificate smoketest-artifacts/"${dist_base}.crt" \
--bundle smoketest-artifacts/"${dist_base}.sigstore"
# Verify using `.sig` `.crt` pair;
./smoketest-env/bin/python -m \
sigstore verify identity "${dist}" \
--signature "smoketest-artifacts/${dist_base}.sig" \
--cert "smoketest-artifacts/${dist_base}.crt" \
--cert-oidc-issuer https://token.actions.githubusercontent.com \
--cert-identity ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/.github/workflows/release.yml@${GITHUB_REF}
# Verify using `.sigstore` bundle;
./smoketest-env/bin/python -m \
sigstore verify identity "${dist}" \
--bundle "smoketest-artifacts/${dist_base}.sigstore" \
--cert-oidc-issuer https://token.actions.githubusercontent.com \
--cert-identity ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/.github/workflows/release.yml@${GITHUB_REF}
rm -rf smoketest-env
done
- name: Upload built packages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: built-packages
path: ./dist/
if-no-files-found: warn
- name: Upload smoketest-artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: smoketest-artifacts
path: smoketest-artifacts/
if-no-files-found: warn
generate-provenance:
needs: [build]
runs-on: ubuntu-latest
permissions:
id-token: write # To sign the provenance.
attestations: write # To persist the attestation files.
steps:
- name: Download artifacts directories # goes to current working directory
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- name: Generate build provenance
uses: actions/attest-build-provenance@v4
with:
subject-path: "built-packages/*"
release-pypi:
needs: [build, generate-provenance]
runs-on: ubuntu-latest
permissions:
# Used to authenticate to PyPI via OIDC.
id-token: write
steps:
- name: Download artifacts directories # goes to current working directory
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- name: publish
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
packages-dir: built-packages/
release-github:
needs: [build, generate-provenance]
runs-on: ubuntu-latest
permissions:
# Needed to upload release assets.
contents: write
steps:
- name: Download artifacts directories # goes to current working directory
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- name: Upload artifacts to github
# Confusingly, this action also supports updating releases, not
# just creating them. This is what we want here, since we've manually
# created the release that triggered the action.
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
# smoketest-artifacts/ contains the signatures and certificates.
files: |
built-packages/*
# Trigger workflow to generate pinned requirements.txt.
pin-requirements:
permissions:
# Needed to create branch and pull request.
pull-requests: write
contents: write
# Workflow depends on uploaded release assets.
needs: [release-github]
# Only trigger workflow on full releases.
if: ${{ !github.event.release.prerelease }}
uses: ./.github/workflows/pin-requirements.yml
with:
tag: ${{ github.ref_name }}
================================================
FILE: .github/workflows/requirements.yml
================================================
name: Test requirements.txt
on:
push:
branches:
- main
workflow_call:
inputs:
ref:
description: The branch, tag, or revision to test.
type: string
required: true
pull_request:
schedule:
- cron: "0 12 * * *"
permissions: {}
jobs:
test_requirements:
name: requirements.txt / ${{ matrix.python_version }}
runs-on: ubuntu-latest
env:
SIGSTORE_REF: ${{ inputs.ref }}
strategy:
matrix:
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- name: Populate reference from context
if: ${{ env.SIGSTORE_REF == '' }}
run: |
echo "SIGSTORE_REF=${GITHUB_REF}" >> "${GITHUB_ENV}"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ env.SIGSTORE_REF }}
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
name: Install Python ${{ matrix.python_version }}
with:
python-version: ${{ matrix.python_version }}
allow-prereleases: true
cache: "pip"
- name: Run test install
run: python -m pip install --require-hashes --no-deps -r install/requirements.txt
================================================
FILE: .github/workflows/scorecards-analysis.yml
================================================
name: Scorecards supply-chain security
on:
# Only the default branch is supported.
workflow_dispatch: # Manual
branch_protection_rule:
schedule:
- cron: '30 4 * * 0'
push:
branches: [ main ]
# Clear default permissions.
permissions: {}
jobs:
analysis:
name: Scorecards analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
actions: read
contents: read
# Needed to access GitHub's OIDC token which ensures the uploaded results integrity.
id-token: write
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# Read-only PAT token. To create it,
# follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.
repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Publish the results to enable scorecard badges. For more details, see
# https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories, `publish_results` will automatically be set to `false`,
# regardless of the value entered here.
publish_results: true
# Upload the results as artifacts (optional).
- name: "Upload artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
sarif_file: results.sarif
================================================
FILE: .github/workflows/staging-tests.yml
================================================
name: Staging Instance Tests
on:
push:
branches:
- main
schedule:
- cron: "0 */8 * * *"
permissions: {}
jobs:
staging-tests:
if: github.event_name != 'schedule' || github.repository == 'sigstore/sigstore-python'
runs-on: ubuntu-latest
permissions:
# Needed to access the workflow's OIDC identity.
id-token: write
# Needed to create an issue, on failure.
issues: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.x"
cache: "pip"
cache-dependency-path: pyproject.toml
- name: staging tests
env:
SIGSTORE_LOGLEVEL: DEBUG
run: |
# This is similar to the "smoketest" that we run during the
# release workflow, except that we run against Sigstore's
# staging instances instead.
# We also don't bother to build distributions.
python -m venv staging-env
./staging-env/bin/python -m pip install .
# Our signing target is not important here, so we just sign
# the README in the repository.
./staging-env/bin/python -m sigstore --verbose --staging sign README.md
# Verification also requires a different Rekor instance, so we
# also test it.
./staging-env/bin/python -m sigstore --verbose --staging verify identity \
--cert-oidc-issuer https://token.actions.githubusercontent.com \
--cert-identity ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/.github/workflows/staging-tests.yml@${GITHUB_REF} \
README.md
- name: generate an issue if staging tests fail
if: failure()
run: |
cat <<- EOF > /tmp/staging-instance-issue.md
## Staging instance failure
A scheduled test against Sigstore's staging instance has failed.
This suggests one of three conditions:
* A backwards-incompatible change in a Sigstore component;
* A regression in \`sigstore-python\`;
* A transient error.
The full CI failure can be found here:
${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/$GITHUB_RUN_ID
EOF
- name: open an issue if the staging tests fail
if: failure()
uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6.0.0
with:
title: "[CI] Integration failure: staging instance"
# created in the previous step
content-filepath: /tmp/staging-instance-issue.md
labels: bug,component:cicd,component:tests
assignees: woodruffw,di,tetsuo-cpp
================================================
FILE: .gitignore
================================================
.cache/
env/
pip-wheel-metadata/
*.egg-info/
__pycache__/
.coverage*
html/
dist/
.python-version
build
# Ignore some file types that may litter the root directory
*.txt
*.crt
*.sig
*.pem
*.sh
*.pub
*.rekor
*.sigstore
*.sigstore.json
# Don't ignore these files when we intend to include them
!sigstore/_store/*.crt
!sigstore/_store/*.pem
!sigstore/_store/*.pub
!test/assets/**
!test/assets/staging-tuf/**
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to `sigstore-python` will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
All versions prior to 0.9.0 are untracked.
## [Unreleased]
### Fixed
* Fixed ~60s hang after completing browser-based OIDC authentication.
The OIDC redirect server had incomplete HTTP responses and no connection
management, causing a keep-alive deadlock with the browser.
## [4.2.0]
### Fixed
* Add state validation to OIDC flow to prevent Cross-site request forgery
during OIDC authorization
([GHSA-hm8f-75xx-w2vr](https://github.com/sigstore/sigstore-python/security/advisories/GHSA-hm8f-75xx-w2vr))
* verification now ensures that artifact digest documented in bundle and the real digest match
(this is a bundle consistency check: bundle signature was always verified over real digest)
([#1652](https://github.com/sigstore/sigstore-python/pull/1652))
* Fix issue with Signed Certificate Timestamp parsing where extensions
were not allowed by sigstore-python
([1657](https://github.com/sigstore/sigstore-python/pull/1657), [1659](https://github.com/sigstore/sigstore-python/pull/1659))
### Changed
* Update supported public key algorithms
([#1604](https://github.com/sigstore/sigstore-python/pull/1604))
* trust: Update embedded TUF root
([#1589](https://github.com/sigstore/sigstore-python/pull/1589))
### Removed
* Removed support for Python 3.9 as it is end-of-life
([#1645](https://github.com/sigstore/sigstore-python/pull/1645))
* Removed unused nonce in Oauth flow
([#1649](https://github.com/sigstore/sigstore-python/pull/1649))
## [4.1.0]
### Added
* cli: Support using other Sigstore instances with `--instance URL`.
New instances are trusted with new top level command `trust-instance ROOTFILE`.
[#1548](https://github.com/sigstore/sigstore-python/pull/1548)
### Changed
* Added cryptography 46 to list of compatible cryptography releases
([#1544](https://github.com/sigstore/sigstore-python/pull/1544))
* Improved error message when verifying bundles with unsupported log entry versions
([#1569](https://github.com/sigstore/sigstore-python/pull/1569))
### Fixed
* cli: Always read/write UTF-8. This fixes an issue on Windows where the platform
default encoding was used: the issue has existed for a while, but became more visible
with signature bundles that contain rekor2 entries.
[#1553](https://github.com/sigstore/sigstore-python/pull/1553)
## [4.0.0]
This is a major release with a host of API and functionality changes. The major new feature
is Rekor v2 support but many other changes are also included, see list below.
### Added
* cli: Add `--rekor-version` to `sign` command arguments: This can be useful
if Sigstore instance provides multiple Rekor versions and user wants to
override the default choice
[#1471](https://github.com/sigstore/sigstore-python/pull/1471)
* cli: Support parallel signing. When multiple artifacts are signed, the Rekor
requests are submitted in parallel: this is especially useful with Rekor v2.
[#1468](https://github.com/sigstore/sigstore-python/pull/1468), [#1478](https://github.com/sigstore/sigstore-python/pull/1478),
[#1485](https://github.com/sigstore/sigstore-python/pull/1485)
* oidc (API): Allow custom audience claims via API
[#1402](https://github.com/sigstore/sigstore-python/pull/1402)
* rekor (API): Support Rekor v2 (aka rekor-tiles) in both verification and signing.
[#1370](https://github.com/sigstore/sigstore-python/pull/1370), [#1422](https://github.com/sigstore/sigstore-python/pull/1422),
[#1432](https://github.com/sigstore/sigstore-python/pull/1432)
* trust (API): Make TrustedRoot, SigningConfig and ClientTrustConfig public API
[#1496](https://github.com/sigstore/sigstore-python/pull/1496)
### Changed
* cli: Improve verify UX when wrong instance is used
[#1510](https://github.com/sigstore/sigstore-python/pull/1510)
* deps: replace sigstore_protobuf_specs dependency with sigstore-models
[#1470](https://github.com/sigstore/sigstore-python/pull/1470)
* trust: Update embedded TUF root
[#1515](https://github.com/sigstore/sigstore-python/pull/1515)
* trust (API): TrustConfig now provides the `production()`and `staging()` helpers. Similar methods were removed from
SigningConfig, TrustedRoot, SigningContext and Issuer. Use TrustConfig everywhere in code base.
[#1363](https://github.com/sigstore/sigstore-python/pull/1363)
* trust (API): support SigningConfig v0.2, remove support for v0.1. The new format now fully defines the
sigstore instance the client uses. `SigningConfig` class now has methods to return actual clients
(like RekorClient) instead of just URLs for that sigstore instance. The `--trust-config` cli option now
expects the trust config to contain a v0.2 SigningConfig.
[#1358](https://github.com/sigstore/sigstore-python/pull/1358), [#1407](https://github.com/sigstore/sigstore-python/pull/1407)
* trust: Support ed25519 keys in trusted root
[#1377](https://github.com/sigstore/sigstore-python/pull/1377)
### Fixed
* rekor: resolve circular import of LogEntry
[#1458](https://github.com/sigstore/sigstore-python/pull/1458)
* rekor: Fix checkpoint signature lookup when there are multiple signatures
[#1514](https://github.com/sigstore/sigstore-python/pull/1514)
* rekor: Fix entry handling so inclusion promise is optional
[#1382](https://github.com/sigstore/sigstore-python/pull/1382)
* rekor: Avoid trailing slash in post to /entries
[#1366](https://github.com/sigstore/sigstore-python/pull/1366)
* sign: fetch TSA timestamps before submitting an entry to Rekor
[#1463](https://github.com/sigstore/sigstore-python/pull/1463)
* timestamp: Specify sha256 in TSA timestamp request
[#1373](https://github.com/sigstore/sigstore-python/pull/1373)
* trust: Fail less hard when trusted root contains unknown keys
[#1424](https://github.com/sigstore/sigstore-python/pull/1424)
* verify: Fix TSA cert chain construction (fixes issue in the case where certificate is not embedded in
the timestamp)
[#1482](https://github.com/sigstore/sigstore-python/pull/1482)
* verify: Use TSA hash algorithm specified in the timestamp (SHA-256, SHA-384 and SHA-512 are supported)
[#1385](https://github.com/sigstore/sigstore-python/pull/1385)
* verify: Check artifact signing time against all established times
[#1381](https://github.com/sigstore/sigstore-python/pull/1381)
* verify: Handle unset TSA timestamp validity end
[#1368](https://github.com/sigstore/sigstore-python/pull/1368)
## [3.6.6]
### Changed
* Improved error message when verifying bundles with rekor v2 entries
([#1565](https://github.com/sigstore/sigstore-python/pull/1565))
* Added cryptography 46 to list of compatible cryptography releases
([#1566](https://github.com/sigstore/sigstore-python/pull/1566))
## [3.6.5]
### Fixed
* Fixed verified time handling so that additional timestamps cannot break
otherwise valid signature bundles ([#1492](https://github.com/sigstore/sigstore-python/pull/1492))
### Changed
* Added cryptography 45 to list of compatible cryptography releases
([#1498](https://github.com/sigstore/sigstore-python/pull/1498))
## [3.6.4]
### Fixed
* Bumped the `rfc3161-client` dependency to `>=1.0.3` to fix a security
vulnerability ([#1451](https://github.com/sigstore/sigstore-python/pull/1451))
## [3.6.3]
### Fixed
* Verify: Avoid hard failure if trusted root contains unsupported keytypes (as verification
may succeed without that key).
[#1425](https://github.com/sigstore/sigstore-python/pull/1425)
## [3.6.2]
### Fixed
* Fixed issue where a trust root with multiple rekor keys was not considered valid:
Now any rekor key listed in the trust root is considered good to verify entries
[#1350](https://github.com/sigstore/sigstore-python/pull/1350)
### Changed
* Upgraded python-tuf dependency to 6.0: Connections to TUF repository
now use system certificates (instead of certifi) and have automatic
retries
* Updated the embedded TUF root to version 12
## [3.6.1]
### Fixed
* Relaxed the transitive dependency on `cryptography` to allow v43 and v44
to be resolved
([#1251](https://github.com/sigstore/sigstore-python/pull/1251))
## [3.6.0]
### Added
* API: The DSSE `Envelope` class now performs automatic validation
([#1211](https://github.com/sigstore/sigstore-python/pull/1211))
* API: Added `signature` property to `Envelope` class for accessing raw
signature bytes ([#1211](https://github.com/sigstore/sigstore-python/pull/1211))
* Signed timestamps embedded in bundles are now automatically verified
against Timestamp Authorities provided within the Trusted Root ([#1206]
(https://github.com/sigstore/sigstore-python/pull/1206))
* Bundles are now generated with signed timestamps when signing if the
Trusted Root contains one or more Timestamp Authorities
([#1216](https://github.com/sigstore/sigstore-python/pull/1216))
### Removed
* Support for "detached" SCTs has been fully removed, aligning
sigstore-python with other sigstore clients
([#1236](https://github.com/sigstore/sigstore-python/pull/1236))
### Fixed
* Fixed a CLI parsing bug introduced in 3.5.1 where a warning about
verifying legacy bundles was never shown
([#1198](https://github.com/sigstore/sigstore-python/pull/1198))
* Strengthened the requirement that an inclusion promise is present
*if* no other source of signed time is present
([#1247](https://github.com/sigstore/sigstore-python/pull/1247))
## [3.5.3]
### Fixed
* Corrective release for [3.5.2]
## [3.5.2]
### Fixed
* Pinned `cryptography` dependency strictly to prevent future breakage
## [3.5.1]
### Fixed
* Fixed a CLI parsing bug introduced in 3.5.0 when attempting
to suppress irrelevant warnings
([#1192](https://github.com/sigstore/sigstore-python/pull/1192))
## [3.5.0]
### Added
* CLI: The `sigstore plumbing update-trust-root` command has been added.
Like other plumbing-level commands, this is considered unstable and
changes are not subject to our semver policy until explicitly noted
([#1174](https://github.com/sigstore/sigstore-python/pull/1174))
### Fixed
* CLI: Fixed an incorrect warning when verifying detached `.crt`/`.sig`
inputs ([#1179](https://github.com/sigstore/sigstore-python/pull/1179))
## [3.4.0]
### Changed
* CLI: When verifying, the `--offline` flag now fully disables all online
operations, including routine local TUF repository refreshes
([#1143](https://github.com/sigstore/sigstore-python/pull/1143))
* `sigstore-python`'s minimum supported Python version is now 3.9
### Fixed
* CLI: The `sigstore verify` subcommands now always check for a matching
input file, rather than unconditionally falling back to matching on a
valid `sha256:...` digest pattern
([#1152](https://github.com/sigstore/sigstore-python/pull/1152))
## [3.3.0]
### Added
* CLI: The `sigstore verify` command now outputs the inner in-toto statement
when verifying DSSE envelopes. If verification is successful, the output
will be the inner in-toto statement. This allows the user to see the
statement's predicate, which `sigstore-python` does not verify and should be
verified by the user.
* CLI: The `sigstore attest` subcommand has been added. This command is
similar to `cosign attest` in that it signs over an artifact and a
predicate using a DSSE envelope. This commands requires the user to pass
a path to the file containing the predicate, and the predicate type.
Currently only the SLSA Provenance v0.2 and v1.0 types are supported.
* CLI: The `sigstore verify` command now supports verifying digests. This means
that the user can now pass a digest like `sha256:aaaa....` instead of the
path to an artifact, and `sigstore-python` will verify it as if it was the
artifact with that digest.
## [3.2.0]
### Added
* API: `models.Bundle.BundleType` is now a public API
([#1089](https://github.com/sigstore/sigstore-python/pull/1089))
* CLI: The `sigstore plumbing` subcommand hierarchy has been added. This
hierarchy is for *developer-only* interactions, such as fixing malformed
Sigstore bundles. These subcommands are **not considered stable until
explicitly documented as such**.
([#1089](https://github.com/sigstore/sigstore-python/pull/1089))
### Changed
* CLI: The default console logger now emits to `stderr`, rather than `stdout`
([#1089](https://github.com/sigstore/sigstore-python/pull/1089))
## [3.1.0]
### Added
* API: `dsse.StatementBuilder` has been added. It can be used to construct an
in-toto `Statement` for subsequent enveloping and signing.
This API is public but is **not considered stable until the next major
release.**
([#1077](https://github.com/sigstore/sigstore-python/pull/1077))
* API: `dsse.Digest`, `dsse.DigestSet`, and `dsse.Subject` have been added.
These types can be used with the `StatementBuilder` API as part of in-toto
`Statement` construction.
These API are public but are **not considered stable until the next major
release.**
([#1078](https://github.com/sigstore/sigstore-python/pull/1078))
### Changed
* API: `verify_dsse` now rejects bundles with DSSE envelopes that have more than
one signature, rather than checking all signatures against the same key
([#1062](https://github.com/sigstore/sigstore-python/pull/1062))
## [3.0.0]
Maintainers' note: this is a major release, with significant public API and CLI
changes. We **strongly** recommend you read the entries below to fully
understand the changes between `2.x` and `3.x`.
### Added
* API: `Signer.sign_artifact()` has been added, replacing the removed
`Signer.sign()` API
* API: `Signer.sign_dsse()` has been added. It takes an in-toto `Statement`
as an input, producing a DSSE-formatted signature rather than a "bare"
signature ([#804](https://github.com/sigstore/sigstore-python/pull/804))
* API: "v3" Sigstore bundles are now supported during verification
([#901](https://github.com/sigstore/sigstore-python/pull/901))
* API: `Verifier.verify(...)` can now take a `Hashed` as an input, performing
signature verification on a pre-computed hash value
([#904](https://github.com/sigstore/sigstore-python/pull/904))
* API: The `sigstore.dsse` module has been been added, including APIs
for representing in-toto statements and DSSE envelopes
([#930](https://github.com/sigstore/sigstore-python/pull/930))
* CLI: The `--trust-config` flag has been added as a global option,
enabling consistent "BYO PKI" uses of `sigstore` with a single flag
([#1010](https://github.com/sigstore/sigstore-python/pull/1010))
* CLI: The `sigstore verify` subcommands can now verify bundles containing
DSSE entries, such as those produced by
[GitHub Artifact Attestations](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)
([#1015](https://github.com/sigstore/sigstore-python/pull/1015))
### Removed
* **BREAKING API CHANGE**: `SigningResult` has been removed.
The public signing APIs now return `sigstore.models.Bundle`.
* **BREAKING API CHANGE**: `VerificationMaterials` has been removed.
The public verification APIs now accept `sigstore.models.Bundle`.
* **BREAKING API CHANGE**: `Signer.sign(...)` has been removed. Use
either `sign_artifact(...)` or `sign_dsse(...)`, depending on whether
you're signing opaque bytes or an in-toto statement.
* **BREAKING API CHANGE**: `VerificationResult` has been removed.
The public verification and policy APIs now raise
`sigstore.errors.VerificationError` on failure.
* **BREAKING CLI CHANGE**: The `--rekor-url` and `--fulcio-url`
flags have been entirely removed. To configure a custom PKI, use
`--trust-config`
([#1010](https://github.com/sigstore/sigstore-python/pull/1010))
### Changed
* **BREAKING API CHANGE**: `Verifier.verify(...)` now takes a `bytes | Hashed`
as its verification input, rather than implicitly receiving the input through
the `VerificationMaterials` parameter
([#904](https://github.com/sigstore/sigstore-python/pull/904))
* **BREAKING API CHANGE**: `VerificationMaterials.rekor_entry(...)` now takes
a `Hashed` parameter to convey the digest used for Rekor entry lookup
([#904](https://github.com/sigstore/sigstore-python/pull/904))
* **BREAKING API CHANGE**: `Verifier.verify(...)` now takes a `sigstore.models.Bundle`,
instead of a `VerificationMaterials` ([#937](https://github.com/sigstore/sigstore-python/pull/937))
* **BREAKING CLI CHANGE**: `sigstore sign` now emits `{input}.sigstore.json`
by default instead of `{input}.sigstore`, per the client specification
([#1007](https://github.com/sigstore/sigstore-python/pull/1007))
* sigstore-python now requires inclusion proofs in all signing and verification
flows, regardless of bundle version of input types. Inputs that do not
have an inclusion proof (such as detached materials) cause an online lookup
before any further processing is performed
([#937](https://github.com/sigstore/sigstore-python/pull/937))
* sigstore-python now generates "v3" bundles by default during signing
([#937](https://github.com/sigstore/sigstore-python/pull/937))
* CLI: Bundles are now always verified offline. The offline flag has no effect.
([#937](https://github.com/sigstore/sigstore-python/pull/937))
* CLI: "Detached" materials are now always verified online, due to a lack of
an inclusion proof. Passing `--offline` with detached materials will cause
an error ([#937](https://github.com/sigstore/sigstore-python/pull/937))
* API: `sigstore.transparency` has been removed, and its pre-existing APIs
have been re-homed under `sigstore.models`
([#990](https://github.com/sigstore/sigstore-python/pull/990))
* API: `oidc.IdentityToken.expected_certificate_subject` has been renamed
to `oidc.IdentityToken.federated_issuer` to better describe what it actually
contains. No functional changes have been made to it
([#1016](https://github.com/sigstore/sigstore-python/pull/1016))
* API: `policy.Identity` now takes an **optional** OIDC issuer, rather than a
required one ([#1015](https://github.com/sigstore/sigstore-python/pull/1015))
* CLI: `sigstore verify github` now requires `--cert-identity` **or**
`--repository`, not just `--cert-identity`
([#1015](https://github.com/sigstore/sigstore-python/pull/1015))
## [2.1.5]
## Fixed
* Backported b32ad1bd (slsa-github-generator upgrade) to make release possible
## [2.1.4]
## Fixed
* Pinned `securesystemslib` dependency strictly to prevent future breakage
## [2.1.3]
## Fixed
* Loosened a version constraint on the `sigstore-protobuf-specs` dependency,
to ease use in testing environments
([#943](https://github.com/sigstore/sigstore-python/pull/943))
## [2.1.2]
This is a corrective release for [2.1.1].
## [2.1.1]
### Fixed
* Fixed an incorrect assumption about Rekor checkpoints that future releases
of Rekor will not uphold ([#891](https://github.com/sigstore/sigstore-python/pull/891))
## [2.1.0]
### Added
* CLI: `sigstore verify`'s subcommands now discover `{input}.sigstore.json`
by default, in addition to the previous `{input}.sigstore`. The former now
takes precedence over the latter, and supplying both results in an error
([#820](https://github.com/sigstore/sigstore-python/pull/820))
## [2.0.1]
### Fixed
* CLI: When using `--certificate-chain`, read as `bytes` instead of `str`
as expected by the underlying API ([#796](https://github.com/sigstore/sigstore-python/pull/796))
## [2.0.0]
### Added
* CLI: `sigstore sign` and `sigstore get-identity-token` now support the
`--oauth-force-oob` option; which has the same behavior as the
preexisting `SIGSTORE_OAUTH_FORCE_OOB` environment variable
([#667](https://github.com/sigstore/sigstore-python/pull/667))
* Version `0.2` of the Sigstore bundle format is now supported
([#705](https://github.com/sigstore/sigstore-python/pull/705))
* API addition: `VerificationMaterials.to_bundle()` is a new public API for
producing a standard Sigstore bundle from `sigstore-python`'s internal
representation ([#719](https://github.com/sigstore/sigstore-python/pull/719))
* API addition: New method `sign.SigningResult.to_bundle()` allows signing
applications to serialize to the bundle format that is already usable in
verification with `verify.VerificationMaterials.from_bundle()`
([#765](https://github.com/sigstore/sigstore-python/pull/765))
### Changed
* `sigstore verify` now performs additional verification of Rekor's inclusion
proofs by cross-checking them against signed checkpoints
([#634](https://github.com/sigstore/sigstore-python/pull/634))
* A cached copy of the trust bundle is now included with the distribution
([#611](https://github.com/sigstore/sigstore-python/pull/611))
* Stopped emitting .sig and .crt signing outputs by default in `sigstore sign`.
Sigstore bundles are now preferred
([#614](https://github.com/sigstore/sigstore-python/pull/614))
* Trust root configuration now assumes that the TUF repository contains a trust
bundle, rather than falling back to deprecated individual targets
([#626](https://github.com/sigstore/sigstore-python/pull/626))
* API change: the `sigstore.oidc.IdentityToken` API has been stabilized as
a wrapper for OIDC tokens
([#635](https://github.com/sigstore/sigstore-python/pull/635))
* API change: `Signer.sign` now takes a `sigstore.oidc.IdentityToken` for
its `identity` argument, rather than a "raw" OIDC token
([#635](https://github.com/sigstore/sigstore-python/pull/635))
* API change: `Issuer.identity_token` now returns a
`sigstore.oidc.IdentityToken`, rather than a "raw" OIDC token
([#635](https://github.com/sigstore/sigstore-python/pull/635))
* `sigstore verify` is not longer a backwards-compatible alias for
`sigstore verify identity`, as it was during the 1.0 release series
([#642](https://github.com/sigstore/sigstore-python/pull/642))
* API change: the `Signer` API has been broken up into `SigningContext`
and `Signer`, allowing a `SigningContext` to create individual `Signer`
instances that correspond to a single `IdentityToken`. This new API
also enables ephemeral key and certificate reuse across multiple inputs,
reducing the number of cryptographic operations and network roundtrips
required when signing more than one input
([#645](https://github.com/sigstore/sigstore-python/pull/645))
* `sigstore sign` now uses an ephemeral P-256 keypair, rather than P-384
([#662](https://github.com/sigstore/sigstore-python/pull/662))
* API change: `RekorClientError` does not try to always parse response
content as JSON
([#694](https://github.com/sigstore/sigstore-python/pull/694))
* API change: `LogEntry.inclusion_promise` can now be `None`, but only
if `LogEntry.inclusion_proof` is not `None`
([#705](https://github.com/sigstore/sigstore-python/pull/705))
* `sigstore-python`'s minimum supported Python version is now 3.8
([#745](https://github.com/sigstore/sigstore-python/pull/745))
### Fixed
* Fixed a case where `sigstore verify` would fail to verify an otherwise valid
inclusion proof due to an incorrect timerange check
([#633](https://github.com/sigstore/sigstore-python/pull/633))
* Removed an unnecessary and backwards-incompatible parameter from the
`sigstore.oidc.detect_credential` API
([#641](https://github.com/sigstore/sigstore-python/pull/641))
* Fixed a case where `sigstore sign` (and `sigstore verify`) could fail while
using a private instance due to a missing due to a missing `ExtendedKeyUsage`
in the CA. We now enforce the fact that the TBSPrecertificate signer must be
a valid CA ([#658](https://github.com/sigstore/sigstore-python/pull/658))
* Fixed a case where identity token retrieval would produce an unhelpful
error message ([#767](https://github.com/sigstore/sigstore-python/pull/767))
## [1.1.2]
### Fixed
* Updated the `staging-root.json` for recent changes to the Sigstore staging
instance ([#602](https://github.com/sigstore/sigstore-python/pull/602))
* Switched TUF requests to their CDN endpoints, rather than direct GCS
access ([#609](https://github.com/sigstore/sigstore-python/pull/609))
## [1.1.1]
### Added
* `sigstore sign` now supports the `--output-directory` flag, which places
default outputs in the specified directory. Without this flag, default outputs
are placed adjacent to the signing input.
([#627](https://github.com/sigstore/sigstore-python/pull/627))
* The whole test suite can now be run locally with `make test-interactive`.
([#576](https://github.com/sigstore/sigstore-python/pull/576))
Users will be prompted to authenticate with their identity provider twice to
generate staging and production OIDC tokens, which are used to test the
`sigstore.sign` module. All signing tests need to be completed before token
expiry, which is currently 60 seconds after issuance.
* Network-related errors from the `sigstore._internal.tuf` module now have better
diagnostics.
([#525](https://github.com/sigstore/sigstore-python/pull/525))
### Changed
* Replaced ambient credential detection logic with the `id` package
([#535](https://github.com/sigstore/sigstore-python/pull/535))
* Revamped error diagnostics reporting. All errors with diagnostics now implement
`sigstore.errors.Error`.
* Trust root materials are now retrieved from a single trust bundle,
if it is available via TUF
([#542](https://github.com/sigstore/sigstore-python/pull/542))
* Improved diagnostics around Signed Certificate Timestamp verification failures.
([#555](https://github.com/sigstore/sigstore-python/pull/555))
### Fixed
* Fixed a bug in TUF target handling revealed by changes to the production
and staging TUF repos
([#522](https://github.com/sigstore/sigstore-python/pull/522))
## [1.1.0]
### Added
* `sigstore sign` now supports Sigstore bundles, which encapsulate the same
state as the default `{input}.crt`, `{input}.sig`, and `{input}.rekor`
files combined. The default output for the Sigstore bundle is
`{input}.sigstore`; this can be disabled with `--no-bundle` or changed with
`--bundle <FILE>`
([#465](https://github.com/sigstore/sigstore-python/pull/465))
* `sigstore verify` now supports Sigstore bundles. By default, `sigstore` looks
for an `{input}.sigstore`; this can be changed with `--bundle <FILE>` or the
legacy method of verification can be used instead via the `--signature` and
`--certificate` flags
([#478](https://github.com/sigstore/sigstore-python/pull/478))
* `sigstore verify identity` and `sigstore verify github` now support the
`--offline` flag, which tells `sigstore` to do offline transparency log
entry verification. This option replaces the unstable
`--require-rekor-offline` option, which has been removed
([#478](https://github.com/sigstore/sigstore-python/pull/478))
### Fixed
* Constrained our dependency on `pyOpenSSL` to `>= 23.0.0` to prevent
a runtime error caused by incompatible earlier versions
([#448](https://github.com/sigstore/sigstore-python/pull/448))
### Removed
* `--rekor-bundle` and `--require-rekor-offline` have been removed entirely,
as their functionality have been wholly supplanted by Sigstore bundle support
and the new `sigstore verify --offline` flag
([#478](https://github.com/sigstore/sigstore-python/pull/478))
## [1.0.0]
### Changed
* `sigstore.rekor` is now `sigstore.transparency`, and its constituent APIs
have been renamed to removed implementation detail references
([#402](https://github.com/sigstore/sigstore-python/pull/402))
* `sigstore.transparency.RekorEntryMissing` is now `LogEntryMissing`
([#414](https://github.com/sigstore/sigstore-python/pull/414))
### Fixed
* The TUF network timeout has been relaxed from 4 seconds to 30 seconds,
which should reduce the likelihood of spurious timeout errors in environments
like GitHub Actions ([#432](https://github.com/sigstore/sigstore-python/pull/432))
## [0.10.0]
### Added
* `sigstore` now supports the `-v`/`--verbose` flag as an alternative to
`SIGSTORE_LOGLEVEL` for debug logging
([#372](https://github.com/sigstore/sigstore-python/pull/372))
* The `sigstore verify identity` has been added, and is functionally
equivalent to the existing `sigstore verify` subcommand.
`sigstore verify` is unchanged, but will be marked deprecated in a future
stable version of `sigstore-python`
([#379](https://github.com/sigstore/sigstore-python/pull/379))
* `sigstore` now has a public, importable Python API! You can find its
documentation [here](https://sigstore.github.io/sigstore-python/)
([#383](https://github.com/sigstore/sigstore-python/pull/383))
* `sigstore --staging` is now the intended way to request Sigstore's staging
instance, rather than per-subcommand options like `sigstore sign --staging`.
The latter is unchanged, but will be marked deprecated in a future stable
version of `sigstore-python`
([#383](https://github.com/sigstore/sigstore-python/pull/383))
* The per-subcommand options `--rekor-url` and `--rekor-root-pubkey` have been
moved to the top-level `sigstore` command. Their subcommand forms are unchanged
and will continue to work, but will be marked deprecated in a future stable
version of `sigstore-python`
([#381](https://github.com/sigstore/sigstore-python/pull/383))
* `sigstore verify github` has been added, allowing for verification of
GitHub-specific claims within given certificate(s)
([#381](https://github.com/sigstore/sigstore-python/pull/381))
### Changed
* The default behavior of `SIGSTORE_LOGLEVEL` has changed; the logger
configured is now the `sigstore.*` hierarchy logger, rather than the "root"
logger ([#372](https://github.com/sigstore/sigstore-python/pull/372))
* The caching mechanism used for TUF has been changed slightly, to use
more future-proof paths ([#373](https://github.com/sigstore/sigstore-python/pull/373))
### Fixed
* Fulcio certificate handling now includes "inactive" but still valid certificates,
allowing users to verify older signatures without custom certificate chains
([#386](https://github.com/sigstore/sigstore-python/pull/386))
## [0.9.0]
### Added
* `sigstore verify` now supports `--certificate-chain` and `--rekor-url`
during verification. Ordinary uses (i.e. the default or `--staging`)
are not affected ([#323](https://github.com/sigstore/sigstore-python/pull/323))
### Changed
* `sigstore sign` and `sigstore verify` now stream their input, rather than
consuming it into a single buffer
([#329](https://github.com/sigstore/sigstore-python/pull/329))
* A series of Python 3.11 deprecation warnings were eliminated
([#341](https://github.com/sigstore/sigstore-python/pull/341))
* The "splash" page presented to users during the OAuth flow has been updated
to reflect the user-friendly page added to `cosign`
([#356](https://github.com/sigstore/sigstore-python/pull/356))
* `sigstore` now uses TUF to retrieve its trust material for Fulcio and Rekor,
replacing the material that was previously baked into `sigstore._store`
([#351](https://github.com/sigstore/sigstore-python/pull/351))
### Removed
* CLI: The `--certificate-chain`, `--rekor-root-pubkey` and `-ctfe` flags have been entirely removed ([#936](https://github.com/sigstore/sigstore-python/pull/936))
<!--Release URLs -->
[Unreleased]: https://github.com/sigstore/sigstore-python/compare/v4.2.0...HEAD
[4.2.0]: https://github.com/sigstore/sigstore-python/compare/v4.1.0...v4.2.0
[4.1.0]: https://github.com/sigstore/sigstore-python/compare/v4.0.0...v4.1.0
[4.0.0]: https://github.com/sigstore/sigstore-python/compare/v3.6.5...v4.0.0
[3.6.5]: https://github.com/sigstore/sigstore-python/compare/v3.6.4...v3.6.5
[3.6.4]: https://github.com/sigstore/sigstore-python/compare/v3.6.3...v3.6.4
[3.6.3]: https://github.com/sigstore/sigstore-python/compare/v3.6.2...v3.6.3
[3.6.2]: https://github.com/sigstore/sigstore-python/compare/v3.6.1...v3.6.2
[3.6.1]: https://github.com/sigstore/sigstore-python/compare/v3.6.0...v3.6.1
[3.6.0]: https://github.com/sigstore/sigstore-python/compare/v3.5.3...v3.6.0
[3.5.3]: https://github.com/sigstore/sigstore-python/compare/v3.5.2...v3.5.3
[3.5.2]: https://github.com/sigstore/sigstore-python/compare/v3.5.1...v3.5.2
[3.5.1]: https://github.com/sigstore/sigstore-python/compare/v3.5.0...v3.5.1
[3.5.0]: https://github.com/sigstore/sigstore-python/compare/v3.4.0...v3.5.0
[3.4.0]: https://github.com/sigstore/sigstore-python/compare/v3.3.0...v3.4.0
[3.3.0]: https://github.com/sigstore/sigstore-python/compare/v3.2.0...v3.3.0
[3.2.0]: https://github.com/sigstore/sigstore-python/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/sigstore/sigstore-python/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/sigstore/sigstore-python/compare/v2.1.5...v3.0.0
[2.1.5]: https://github.com/sigstore/sigstore-python/compare/v2.1.4...v2.1.5
[2.1.4]: https://github.com/sigstore/sigstore-python/compare/v2.1.3...v2.1.4
[2.1.3]: https://github.com/sigstore/sigstore-python/compare/v2.1.2...v2.1.3
[2.1.2]: https://github.com/sigstore/sigstore-python/compare/v2.1.1...v2.1.2
[2.1.1]: https://github.com/sigstore/sigstore-python/compare/v2.1.0...v2.1.1
[2.1.0]: https://github.com/sigstore/sigstore-python/compare/v2.0.1...v2.1.0
[2.0.1]: https://github.com/sigstore/sigstore-python/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/sigstore/sigstore-python/compare/v1.1.2...v2.0.0
[1.1.2]: https://github.com/sigstore/sigstore-python/compare/v1.1.1...v1.1.2
[1.1.1]: https://github.com/sigstore/sigstore-python/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/sigstore/sigstore-python/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/sigstore/sigstore-python/compare/v0.10.0...v1.0.0
[0.10.0]: https://github.com/sigstore/sigstore-python/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/sigstore/sigstore-python/compare/v0.8.3...v0.9.0
================================================
FILE: CODEOWNERS
================================================
@sigstore/codeowners-sigstore-python
# The CODEOWNERS are managed via a GitHub team, but the current list is (in alphabetical order):
# di
# tetsuo-cpp
# woodruffw
================================================
FILE: CONTRIBUTING.md
================================================
Contributing to sigstore
========================
Thank you for your interest in contributing to `sigstore`!
The information below will help you set up a local development environment,
as well as performing common development tasks.
## Requirements
`sigstore`'s only development environment requirement *should* be Python 3.10
or newer. Development and testing is actively performed on macOS and Linux,
but Windows and other supported platforms that are supported by Python
should also work.
If you're on a system that has GNU Make, you can use the convenience targets
included in the `Makefile` that comes in the `sigstore` repository detailed
below. But this isn't required; all steps can be done without Make.
## Development steps
First, clone this repository:
```bash
git clone https://github.com/sigstore/sigstore-python
cd sigstore
```
Then, use one of the `Makefile` targets to run a task. The first time this is
run, this will also set up the local development virtual environment, and will
install `sigstore` as an editable package into this environment.
Any changes you make to the `sigstore` source tree will take effect
immediately in the virtual environment.
### Linting
You can lint locally with:
```bash
make lint
```
`sigstore` is automatically linted and formatted with a collection of tools:
* [`ruff`](https://github.com/charliermarsh/ruff): Code formatting, PEP-8 linting, style enforcement
* [`mypy`](https://mypy.readthedocs.io/en/stable/): Static type checking
* [`bandit`](https://github.com/PyCQA/bandit): Security issue scanning
* [`interrogate`](https://interrogate.readthedocs.io/en/latest/): Documentation coverage
To automatically apply any lint-suggested changes, you can run:
```bash
make reformat
```
### Testing
You can run the tests locally with:
```bash
make test
```
or:
```bash
make test-interactive
```
to run tests that require OIDC credentials (will prompt for authentication to generate tokens).
Note that `test-interactive` may fail if you have a slow network, as the tokens generated are only
valid for 60 seconds after their issuance.
You can also filter by a pattern (uses `pytest -k`):
```bash
make test TESTS=test_version
```
To test a specific file:
```bash
make test T=path/to/file.py
```
`sigstore` has a [`pytest`](https://docs.pytest.org/)-based unit test suite,
including code coverage with [`coverage.py`](https://coverage.readthedocs.io/).
#### X.509 test cases
`sigstore` includes some checked-in X.509 test assets under
[`test/unit/assets/x509`](./test/unit/assets/x509/).
These assets are generated by the adjacent
[`build-testcases.py`](./test/unit/assets/x509/build-testcases.py) script,
which can be updated to generate additional test cases.
To re-build the X.509 test cases, you can use `make`:
```bash
make gen-x509-testcases
```
### Documentation
If you're running Python 3.10 or newer, you can run the documentation build locally:
```bash
make doc
```
`sigstore` uses [`pdoc`](https://github.com/mitmproxy/pdoc) to generate HTML
documentation for the public Python APIs.
### Releasing
**NOTE**: If you're a non-maintaining contributor, you don't need the steps
here! They're documented for completeness and for onboarding future maintainers.
Releases of `sigstore` are managed with [`bump`](https://github.com/di/bump)
and GitHub Actions.
```bash
# default release (patch bump)
make release
# override the default
# vX.Y.Z -> vX.Y.Z-rc.0
make release BUMP_ARGS="--pre rc.0"
# vX.Y.Z -> vN.0.0
make release BUMP_ARGS="--major"
```
`make release` will fail if there are any untracked changes in the source tree.
If `make release` succeeds, you'll see an output like this:
```
RUN ME MANUALLY: git push origin main && git push origin vX.Y.Z
```
Run that last command sequence to complete the release.
## Development practices
Here are some guidelines to follow if you're working on a new feature or changes to
`sigstore`'s internal APIs:
* *Keep the `sigstore` APIs as private as possible*. Nearly all of `sigstore`'s
APIs should be private and treated as unstable and unsuitable for public use.
If you're adding a new module to the source tree, prefix the filename with an underscore to
emphasize that it's an internal (e.g., `sigstore/_foo.py` instead of `sigstore/foo.py`).
* *Perform judicious debug logging.* `sigstore` uses the standard Python
[`logging`](https://docs.python.org/3/library/logging.html) module. Use
`logger.debug` early and often -- users who experience errors can submit better
bug reports when their debug logs include helpful context!
* *Update the [CHANGELOG](./CHANGELOG.md)*. If your changes are public or result
in changes to `sigstore`'s CLI, please record them under the "Unreleased" section,
with an entry in an appropriate subsection ("Added", "Changed", "Removed", or "Fixed").
* Ensure your commits are signed off, as sigstore uses the
[DCO](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin).
You can do it using `git commit -s`, or `git commit -s --amend` if you want to amend already existing commits.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
SHELL := /bin/bash
PY_MODULE := sigstore
ALL_PY_SRCS := $(shell find $(PY_MODULE) -name '*.py') \
$(shell find test -name '*.py') \
$(shell find docs/scripts -name '*.py') \
# Optionally overridden by the user, if they're using a virtual environment manager.
VENV ?= env
# On Windows, venv scripts/shims are under `Scripts` instead of `bin`.
VENV_BIN := $(VENV)/bin
ifeq ($(OS),Windows_NT)
VENV_BIN := $(VENV)/Scripts
endif
# Optionally overridden by the user in the `release` target.
BUMP_ARGS :=
# Optionally overridden by the user in the `test` target.
TESTS ?=
# Optionally overridden by the user/CI, to limit the installation to a specific
# subset of development dependencies.
SIGSTORE_EXTRA := dev
# If the user selects a specific test pattern to run, set `pytest` to fail fast
# and only run tests that match the pattern.
# Otherwise, run all tests and enable coverage assertions, since we expect
# complete test coverage.
ifneq ($(TESTS),)
TEST_ARGS := -x -k $(TESTS) $(TEST_ARGS)
COV_ARGS :=
else
TEST_ARGS := $(TEST_ARGS)
# TODO: Re-enable coverage testing
# COV_ARGS := --fail-under 100
endif
ifneq ($(T),)
T := $(T)
else
T := test/unit test/integration
endif
.PHONY: all
all:
@echo "Run my targets individually!"
$(VENV)/pyvenv.cfg: pyproject.toml
# Create our Python 3 virtual environment
python3 -m venv $(VENV)
$(VENV_BIN)/python -m pip install --upgrade pip
$(VENV_BIN)/python -m pip install -e .[$(SIGSTORE_EXTRA)]
.PHONY: dev
dev: $(VENV)/pyvenv.cfg
.PHONY: run
run: $(VENV)/pyvenv.cfg
@. $(VENV_BIN)/activate && sigstore $(ARGS)
.PHONY: lint
lint: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
ruff format --check $(ALL_PY_SRCS) && \
ruff check $(ALL_PY_SRCS) && \
mypy $(PY_MODULE) && \
bandit -c pyproject.toml -r $(PY_MODULE) && \
python docs/scripts/gen_ref_pages.py --check
.PHONY: reformat
reformat: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
ruff check --fix $(ALL_PY_SRCS) && \
ruff format $(ALL_PY_SRCS)
.PHONY: test
test: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
$(TEST_ENV) pytest --cov-append --cov=$(PY_MODULE) $(T) $(TEST_ARGS) && \
python -m coverage report -m $(COV_ARGS)
.PHONY: test-interactive
test-interactive: TEST_ENV += \
SIGSTORE_IDENTITY_TOKEN_production=$$($(MAKE) -s run ARGS="get-identity-token") \
SIGSTORE_IDENTITY_TOKEN_staging=$$($(MAKE) -s run ARGS="--staging get-identity-token")
test-interactive: test
.PHONY: gen-x509-testcases
gen-x509-testcases: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
export TESTCASE_OVERWRITE=1 && \
python test/assets/x509/build-testcases.py && \
git diff --exit-code
.PHONY: doc
doc: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
python docs/scripts/gen_ref_pages.py --overwrite && \
mkdocs build --strict --site-dir html
.PHONY: package
package: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
python3 -m build
.PHONY: release
release: $(VENV)/pyvenv.cfg
@. $(VENV_BIN)/activate && \
NEXT_VERSION=$$(bump $(BUMP_ARGS)) && \
git add $(PY_MODULE)/_version.py && git diff --quiet --exit-code && \
git commit -m "version: v$${NEXT_VERSION}" && \
git tag v$${NEXT_VERSION} && \
echo "RUN ME MANUALLY: git push origin main && git push origin v$${NEXT_VERSION}"
.PHONY: check-readme
check-readme:
# sigstore --help
@diff \
<( \
awk '/@begin-sigstore-help@/{f=1;next} /@end-sigstore-help@/{f=0} f' \
< README.md | sed '1d;$$d' \
) \
<( \
$(MAKE) -s run ARGS="--help" \
)
# sigstore sign --help
@diff \
<( \
awk '/@begin-sigstore-sign-help@/{f=1;next} /@end-sigstore-sign-help@/{f=0} f' \
< README.md | sed '1d;$$d' \
) \
<( \
$(MAKE) -s run ARGS="sign --help" \
)
# sigstore attest --help
@diff \
<( \
awk '/@begin-sigstore-attest-help@/{f=1;next} /@end-sigstore-attest-help@/{f=0} f' \
< README.md | sed '1d;$$d' \
) \
<( \
$(MAKE) -s run ARGS="attest --help" \
)
# sigstore verify identity --help
@diff \
<( \
awk '/@begin-sigstore-verify-identity-help@/{f=1;next} /@end-sigstore-verify-identity-help@/{f=0} f' \
< README.md | sed '1d;$$d' \
) \
<( \
$(MAKE) -s run ARGS="verify identity --help" \
)
# sigstore verify github --help
@diff \
<( \
awk '/@begin-sigstore-verify-github-help@/{f=1;next} /@end-sigstore-verify-github-help@/{f=0} f' \
< README.md | sed '1d;$$d' \
) \
<( \
$(MAKE) -s run ARGS="verify github --help" \
)
.PHONY: edit
edit:
$(EDITOR) $(ALL_PY_SRCS)
update-embedded-root: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
python -m sigstore plumbing update-trust-root
cp ~/.local/share/sigstore-python/tuf/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/root.json \
sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/root.json
cp ~/.cache/sigstore-python/tuf/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/trusted_root.json \
~/.cache/sigstore-python/tuf/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/signing_config.v0.2.json \
sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/
update-embedded-root-staging: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
python -m sigstore --staging plumbing update-trust-root
cp ~/.local/share/sigstore-python/tuf/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/root.json \
sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/root.json
cp ~/.cache/sigstore-python/tuf/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/trusted_root.json \
~/.cache/sigstore-python/tuf/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/signing_config.v0.2.json \
sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/
================================================
FILE: README.md
================================================
sigstore-python
===============
<!--- @begin-badges@ --->
[](https://github.com/sigstore/sigstore-python/actions/workflows/ci.yml)
[](https://pypi.org/project/sigstore)
[](https://securityscorecards.dev/viewer/?uri=github.com/sigstore/sigstore-python)
[](https://slsa.dev/)

[](https://sigstore.github.io/sigstore-python)
<!--- @end-badges@ --->
`sigstore` is a Python tool for generating and verifying Sigstore signatures.
You can use it to sign and verify Python package distributions, or anything
else!
## Index
* [Features](#features)
* [Installation](#installation)
* [Usage](#usage)
* [Signing](#signing)
* [Verifying](#verifying)
* [Generic identities](#generic-identities)
* [Signatures from GitHub Actions](#signatures-from-github-actions)
* [Advanced usage](#advanced-usage)
* [Troubleshooting](#troubleshooting)
* [Documentation](#documentation)
* [Licensing](#licensing)
* [Community](#community)
* [Contributing](#contributing)
* [Code of Conduct](#code-of-conduct)
* [Security](#security)
* [SLSA Provenance](#slsa-provenance)
## Features
* Support for keyless signature generation and verification with [Sigstore](https://www.sigstore.dev/)
* Support for signing with ["ambient" OpenID Connect identities](https://github.com/sigstore/sigstore-python#signing-with-ambient-credentials)
* A comprehensive [CLI](https://github.com/sigstore/sigstore-python#usage) and corresponding
[importable Python API](https://sigstore.github.io/sigstore-python)
## Installation
`sigstore` requires Python 3.10 or newer, and can be installed directly via `pip`:
```console
python -m pip install sigstore
```
See the [installation](https://sigstore.github.io/sigstore-python/installation) page in the documentation for more
installation options.
## Usage
For Python API usage, see our [API](https://sigstore.github.io/sigstore-python/api/).
You can run `sigstore` as a standalone program:
```console
sigstore --help
```
Top-level:
<!-- @begin-sigstore-help@ -->
```
usage: sigstore [-h] [-v] [-V]
[--staging | --instance URL | --trust-config FILE]
COMMAND ...
a tool for signing and verifying Python package distributions
positional arguments:
COMMAND the operation to perform
attest sign one or more inputs using DSSE
sign sign one or more inputs
verify verify one or more inputs
get-identity-token
retrieve and return a Sigstore-compatible OpenID
Connect token
trust-instance Initialize trust for a Sigstore instance
plumbing developer-only plumbing operations
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
-V, --version show program's version number and exit
--staging Use sigstore's staging instance, instead of the default
production instance. Mutually exclusive with other
instance configuration arguments. (default: False)
--instance URL Use a given Sigstore instance URL, instead of the
default production instance. Mutually exclusive with
other instance configuration arguments. (default: None)
--trust-config FILE Use given client trust configuration instead of using
the default production instance. Mutually exclusive
with other instance configuration arguments. (default:
None)
```
<!-- @end-sigstore-help@ -->
### Signing
<!-- @begin-sigstore-sign-help@ -->
```
usage: sigstore sign [-h] [-v] [--rekor-version VERSION]
[--identity-token TOKEN] [--oidc-client-id ID]
[--oidc-client-secret SECRET]
[--oidc-disable-ambient-providers] [--oidc-issuer URL]
[--oauth-force-oob] [--no-default-files]
[--signature FILE] [--certificate FILE] [--bundle FILE]
[--output-directory DIR] [--overwrite]
FILE [FILE ...]
positional arguments:
FILE The file to sign
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
--rekor-version VERSION
Force the rekor transparency log version. Valid values
are [1, 2]. By default the highest available version
is used
OpenID Connect options:
--identity-token TOKEN
the OIDC identity token to use (default: None)
--oidc-client-id ID The custom OpenID Connect client ID to use during
OAuth2 (default: sigstore)
--oidc-client-secret SECRET
The custom OpenID Connect client secret to use during
OAuth2 (default: None)
--oidc-disable-ambient-providers
Disable ambient OpenID Connect credential detection
(e.g. on GitHub Actions) (default: False)
--oidc-issuer URL The OpenID Connect issuer to use (default: None)
--oauth-force-oob Force an out-of-band OAuth flow and do not
automatically start the default web browser (default:
False)
Output options:
--no-default-files Don't emit the default output files
({input}.sigstore.json) (default: False)
--signature FILE, --output-signature FILE
Write a single signature to the given file; does not
work with multiple input files (default: None)
--certificate FILE, --output-certificate FILE
Write a single certificate to the given file; does not
work with multiple input files (default: None)
--bundle FILE Write a single Sigstore bundle to the given file; does
not work with multiple input files (default: None)
--output-directory DIR
Write default outputs to the given directory
(conflicts with --signature, --certificate, --bundle)
(default: None)
--overwrite Overwrite preexisting signature and certificate
outputs, if present (default: False)
```
<!-- @end-sigstore-sign-help@ -->
### Signing with DSSE envelopes
<!-- @begin-sigstore-attest-help@ -->
```
usage: sigstore attest [-h] [-v] [--rekor-version VERSION] --predicate FILE
--predicate-type TYPE [--identity-token TOKEN]
[--oidc-client-id ID] [--oidc-client-secret SECRET]
[--oidc-disable-ambient-providers] [--oidc-issuer URL]
[--oauth-force-oob] [--bundle FILE] [--overwrite]
FILE [FILE ...]
positional arguments:
FILE The file to sign
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
--rekor-version VERSION
Force the rekor transparency log version. Valid values
are [1, 2]. By default the highest available version
is used
DSSE options:
--predicate FILE Path to the predicate file (default: None)
--predicate-type TYPE
Specify a predicate type
(https://slsa.dev/provenance/v0.2,
https://slsa.dev/provenance/v1) (default: None)
OpenID Connect options:
--identity-token TOKEN
the OIDC identity token to use (default: None)
--oidc-client-id ID The custom OpenID Connect client ID to use during
OAuth2 (default: sigstore)
--oidc-client-secret SECRET
The custom OpenID Connect client secret to use during
OAuth2 (default: None)
--oidc-disable-ambient-providers
Disable ambient OpenID Connect credential detection
(e.g. on GitHub Actions) (default: False)
--oidc-issuer URL The OpenID Connect issuer to use (default: None)
--oauth-force-oob Force an out-of-band OAuth flow and do not
automatically start the default web browser (default:
False)
Output options:
--bundle FILE Write a single Sigstore bundle to the given file; does
not work with multiple input files (default: None)
--overwrite Overwrite preexisting bundle outputs, if present
(default: False)
```
<!-- @end-sigstore-attest-help@ -->
### Verifying
#### Identities
<!-- @begin-sigstore-verify-identity-help@ -->
```
usage: sigstore verify identity [-h] [-v] [--certificate FILE]
[--signature FILE] [--bundle FILE] [--offline]
--cert-identity IDENTITY --cert-oidc-issuer
URL
FILE_OR_DIGEST [FILE_OR_DIGEST ...]
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
Verification inputs:
--certificate FILE, --cert FILE
The PEM-encoded certificate to verify against; not
used with multiple inputs (default: None)
--signature FILE The signature to verify against; not used with
multiple inputs (default: None)
--bundle FILE The Sigstore bundle to verify with; not used with
multiple inputs (default: None)
FILE_OR_DIGEST The file path or the digest to verify. The digest
should start with the 'sha256:' prefix.
Verification options:
--offline Perform offline verification; requires a Sigstore
bundle (default: False)
--cert-identity IDENTITY
The identity to check for in the certificate's Subject
Alternative Name (default: None)
--cert-oidc-issuer URL
The OIDC issuer URL to check for in the certificate's
OIDC issuer extension (default: None)
```
<!-- @end-sigstore-verify-identity-help@ -->
#### Signatures from GitHub Actions
<!-- @begin-sigstore-verify-github-help@ -->
```
usage: sigstore verify github [-h] [-v] [--certificate FILE]
[--signature FILE] [--bundle FILE] [--offline]
[--cert-identity IDENTITY] [--trigger EVENT]
[--sha SHA] [--name NAME] [--repository REPO]
[--ref REF]
FILE_OR_DIGEST [FILE_OR_DIGEST ...]
options:
-h, --help show this help message and exit
-v, --verbose run with additional debug logging; supply multiple
times to increase verbosity (default: 0)
Verification inputs:
--certificate FILE, --cert FILE
The PEM-encoded certificate to verify against; not
used with multiple inputs (default: None)
--signature FILE The signature to verify against; not used with
multiple inputs (default: None)
--bundle FILE The Sigstore bundle to verify with; not used with
multiple inputs (default: None)
FILE_OR_DIGEST The file path or the digest to verify. The digest
should start with the 'sha256:' prefix.
Verification options:
--offline Perform offline verification; requires a Sigstore
bundle (default: False)
--cert-identity IDENTITY
The identity to check for in the certificate's Subject
Alternative Name (default: None)
--trigger EVENT The GitHub Actions event name that triggered the
workflow (default: None)
--sha SHA The `git` commit SHA that the workflow run was invoked
with (default: None)
--name NAME The name of the workflow that was triggered (default:
None)
--repository REPO The repository slug that the workflow was triggered
under (default: None)
--ref REF The `git` ref that the workflow was invoked with
(default: None)
```
<!-- @end-sigstore-verify-github-help@ -->
## Troubleshooting
First, please make sure you are using a recent and supported release:
sigstore-python project provides support for the latest release and
best effort critical bug fixes for the latest 3.6.x release.
### Common issues
1. "_bundle contains a transparency log entry that is incompatible with
this version of sigstore-python_" (as well as "_not enough sources of
verified time_") means an upgrade is necessary to verify this signature
bundle: Signature bundles with Rekor v2 transparency log entries can only be
verified with sigstore-python 4 and above
1. verifying without a network connection results in HTTP errors: By default
sigstore-python checks for updates to the trusted key material on every
startup. This can be avoided temporarily with `--offline` but please read the
[documentation](https://sigstore.github.io/sigstore-python/advanced/offline/)
for caveats
1. Signing results in HTTP errors: Signing with sigstore-python depends on multiple
Sigstore services. Retrying on failure may be a useful workaround if any of
these services fail but filing issues for specific failures is appreciated
### My problem is something else
Please [open an issue](https://github.com/sigstore/sigstore-python/issues/new?template=bug.md) or ask in the [slack channel](#community).
## Documentation
`sigstore` documentation is available on [https://sigstore.github.io/sigstore-python](https://sigstore.github.io/sigstore-python)
## Licensing
`sigstore` is licensed under the Apache 2.0 License.
## Community
`sigstore-python` is developed as part of the [Sigstore](https://sigstore.dev) project.
We also use a [Slack channel](https://sigstore.slack.com)!
Click [here](https://join.slack.com/t/sigstore/shared_invite/zt-mhs55zh0-XmY3bcfWn4XEyMqUUutbUQ) for the invite link.
## Contributing
See [the contributing docs](https://github.com/sigstore/.github/blob/main/CONTRIBUTING.md) for details.
## Code of Conduct
Everyone interacting with this project is expected to follow the
[sigstore Code of Conduct](https://github.com/sigstore/.github/blob/main/CODE_OF_CONDUCT.md).
## Security
Should you discover any security issues, please refer to sigstore's [security
process](https://github.com/sigstore/.github/blob/main/SECURITY.md).
================================================
FILE: cloudbuild.yaml
================================================
steps:
# Install dependencies
- name: python
entrypoint: python
args: ["-m", "pip", "install", ".", "--user"]
# Sign with ambient GCP credentials
- name: python
entrypoint: python
args: ["-m", "sigstore", "sign", "README.md"]
env:
- "GOOGLE_SERVICE_ACCOUNT_NAME=sigstore-python-test@projectsigstore.iam.gserviceaccount.com"
================================================
FILE: docs/advanced/custom_trust.md
================================================
# Custom Sigstore instances
By default, `sigstore` is configured to work with the public `sigstore.dev`
instance. The trust materials for this instance are bundled with the client,
allowing for a seamless out-of-the-box experience.
In addition to the public instance, `sigstore` also supports using custom
Sigstore instances. When using a custom instance, you are responsible
for providing the trust materials (at least once). This document outlines
the methods for doing so.
### Using a custom instance
Using a custom Sigstore instance is a two-step process:
1. First, you must establish trust for the new instance. This is done using the
`sigstore trust-instance` command. This step only needs to be performed once.
2. Once trust is established, you can use the `--instance` flag with `sigstore`
commands like `sign` and `verify` to point to your custom instance.
To establish trust for a custom instance, you need its TUF root file. You can then run:
```console
$ sigstore --instance https://my-sigstore.example.com trust-instance my-root.json
```
After successfully adding the new instance, you can use it for signing and verifying
artifacts. For example, to sign a file:
```console
$ sigstore --instance https://my-sigstore.example.com sign foo.txt
```
### Using a custom instance with local configuration
The trust configuration can also be provided as a local file -- but the user is now
responsible for keeping the trust configuration updated.
The `--trust-config` flag, accepts a JSON-formatted file conforming to the `ClientTrustConfig`
message in the [Sigstore protobuf specs](https://github.com/sigstore/protobuf-specs).
This file configures the entire Sigstore instance state, *including* the URIs
used to access the CA and artifact transparency services as well as the
cryptographic root of trust itself.
To use a custom client config, prepend `--trust-config` to any `sigstore`
command:
```console
$ sigstore --trust-config custom.trustconfig.json sign foo.txt
$ sigstore --trust-config custom.trustconfig.json verify identity foo.txt ...
```
================================================
FILE: docs/advanced/offline.md
================================================
# Offline Verification
!!! danger
Because `--offline` disables trust root updates, `sigstore-python` falls back
to the latest cached trust root or, if none exists, the trust root baked
into `sigstore-python` itself. Like with any other offline verification,
this means that users may miss trust root changes (such as new root keys,
or revocations) unless they separately keep the trust root up-to-date.
Users who need to operationalize offline verification may wish to do this
by distributing their own trust configuration; see
[Custom instance with local configuration](./custom_trust.md#using-a-custom-instance-with-local-configuration).
During verification, there are two kinds of network access that `sigstore-python`
*can* perform:
1. When verifying against "detached" materials (e.g. separate `.crt` and `.sig`
files), `sigstore-python` can perform an online transparency log lookup.
2. By default, during all verifications, `sigstore-python` will attempt to
refresh the locally cached root of trust via a TUF update.
When performing bundle verification (i.e. `.sigstore` or `.sigstore.json`),
(1) does not apply. However, (2) can still result in online accesses.
To perform **fully** offline verification, pass `--offline` to your
`sigstore verify` subcommand:
```bash
$ sigstore verify identity foo.txt \
--offline \
--cert-identity 'hamilcar@example.com' \
--cert-oidc-issuer 'https://github.com/login/oauth'
```
Alternatively, users may choose to bypass TUF entirely by passing
an entire trust configuration to `sigstore-python` via `--trust-config`:
```bash
$ sigstore --trust-config public.trustconfig.json verify identity ...
```
This will similarly result in fully offline operation, as the trust
configuration contains a full trust root.
================================================
FILE: docs/api/errors.md
================================================
:::sigstore.errors
================================================
FILE: docs/api/hashes.md
================================================
:::sigstore.hashes
================================================
FILE: docs/api/index.md
================================================
!!! note
The API reference is automatically generated from the docstrings
:::sigstore
================================================
FILE: docs/api/models.md
================================================
:::sigstore.models
================================================
FILE: docs/api/oidc.md
================================================
:::sigstore.oidc
================================================
FILE: docs/api/sign.md
================================================
:::sigstore.sign
================================================
FILE: docs/api/verify/policy.md
================================================
:::sigstore.verify.policy
================================================
FILE: docs/api/verify/verifier.md
================================================
:::sigstore.verify.verifier
================================================
FILE: docs/index.md
================================================
# Home
## Introduction
`sigstore` is a Python tool for generating and verifying [Sigstore] signatures.
You can use it to sign and verify Python package distributions, or anything
else!
## Features
* Support for keyless signature generation and verification with [Sigstore](https://www.sigstore.dev/)
* Support for signing with ["ambient" OpenID Connect identities](./signing.md#signing-with-ambient-credentials)
* A comprehensive [CLI](#using-sigstore) and corresponding
[importable Python API](./api/index.md)
## Installing `sigstore`
```console
python -m pip install sigstore
```
See [installation](./installation.md) for more detailed installation instructions or options.
## Using `sigstore`
You can run `sigstore` as a standalone program, or via `python -m`:
```console
sigstore --help
python -m sigstore --help
```
- Use `sigstore` to [sign](./signing.md)
- Use `sigstore` to [verify](./verify.md)
## SLSA Provenance
This project emits a [SLSA] provenance on its release! This enables you to verify the integrity
of the downloaded artifacts and ensured that the binary's code really comes from this source code.
To do so, please follow the instructions [here](https://github.com/slsa-framework/slsa-github-generator#verification-of-provenance).
[SLSA]: https://slsa.dev/
[Sigstore]: https://www.sigstore.dev/
================================================
FILE: docs/installation.md
================================================
# Installation
## With `pip`
`sigstore` requires Python 3.9 or newer, and can be installed directly via `pip`:
```console
python -m pip install sigstore
```
Optionally, to install `sigstore` and all its dependencies with [hash-checking mode](https://pip.pypa.io/en/stable/topics/secure-installs/#hash-checking-mode) enabled, run the following:
```console
python -m pip install -r https://raw.githubusercontent.com/sigstore/sigstore-python/main/install/requirements.txt
```
This installs the requirements file located [here](https://github.com/sigstore/sigstore-python/blob/main/install/requirements.txt), which is kept up-to-date.
## With `uv`
!!! warning
`sigstore` depends on `betterproto` pre-releases versions, which are by default not resolved by `uv`.
```console
uv pip install --prerelease=allow sigstore
```
`sigstore` can also be used as tool:
```console
uvx --prerelease=allow sigstore --help
```
## GitHub Actions
`sigstore-python` has [an official GitHub Action](https://github.com/sigstore/gh-action-sigstore-python)!
You can install it from the [GitHub Marketplace](https://github.com/marketplace/actions/gh-action-sigstore-python), or
add it to your CI manually:
```yaml
jobs:
sigstore-python:
steps:
- uses: sigstore/gh-action-sigstore-python@v3.0.0
with:
inputs: foo.txt
```
See the [action documentation](https://github.com/sigstore/gh-action-sigstore-python/blob/main/README.md) for more details and usage examples.
================================================
FILE: docs/policy.md
================================================
# Policies
This document describes the set of policies followed by `sigstore-python`
when signing or verifying a bundle.
`sigstore-python` follows the [Sigstore: Client Spec] and this document
outline mimic the one from the spec.
## Signing
### Authentication
`sigstore-python` supports several authentication mechanisms :
- An OAuth flow: this mode is preferred for interactive workflows.
- An _ambient_ detection: this mode is preferred for un-attended workflows
(i.e., continuous integration system)
### Key generation
`sigstore-python` uses [ECDSA] as its signing algorithm.
### Certificate Issuance
_using Fulcio_
### Signing
When needed, the payload pre-hashing algorithm is `SHA2_256`.
### Timestamping
If Timestamp Authorities have been provided in the Signing Config, a
Timestamp Request using the hash of the signature is automatically sent to the
provided Timestamp Authorities.
This step allows to attest of the signature time.
### Submission of Signing Metadata to Transparency Service
The Transparency Service, [rekor], is used by `sigstore-python` to provide a
public, immutable record of signing events. This step is crucial for ensuring
the integrity and transparency of the signing process.
!!! warning
This step is performed before the `Timestamping` step in the workflow.
### Signing Choices
Here's a summary of the key choices in the `sigstore-python` signing process:
| Option | `sigstore-python` |
|-------------------------------|------------------------------|
| Digital signature algorithm | ECDSA |
| Signature metadata format | ??? |
| Payload pre-hashing algorithm | SHA2 (256) |
| Long-lived signing keys | not used |
| Timestamping | Used if provided |
| Transparency | Always used (rekor) |
| Other workflows | no other workflows supported |
## Verification
`sigstore-python` supports configuring the verification process using policies
but this must be done using the [api](./api/index.md). By default, the CLI uses
the [`Identity`][sigstore.verify.policy] verification policy.
### Establishing a Time for the Signature
If the bundle contains one or more signed times from Timestamping Authorities,
they will be used as the time source. In this case, a Timestamp Authority
configuration must be provided in the `ClientTrustConfig`. When verifying
Timestamp Authorities Responses, at least one must be valid.
If there is a Transparency Service Timestamp, this is also used as a source
of trusted time.
The verification will fail if no sources of time are found.
### Certificate
For a signature to be considered valid, it must meet two key criteria:
- The signature must have an associated timestamp.
- Every certificate in the chain, from the signing certificate up to the root
certificate, must be valid at the time of signing.
This approach is known as the “hybrid model” of certificate verification, as
described by [Braun et al.].
This validation process is repeated for each available source of trusted time.
The signature is only considered valid if it passes the validation checks
against all of these time sources.
#### SignedCertificateTimestamp
The `SignedCertificateTimestamp` is extracted from the leaf certificate and
verified using the verification key from the Certificate Transparency Log.
#### Identity Verification Policy
The system verifies that the signing certificate conforms to the Sigstore X. 509
profile as well as `Identity Policy`.
### Transparency Log Entry
The Verifier now verifies the inclusion proof and signed checkpoint for the
log entry using [rekor].
If there is an inclusion promise, this is also verified.
#### Time insertion check
The system verifies that the transparency log entry’s insertion timestamp falls
within the certificate’s validity period.
If the insertion timestamp is outside the certificate’s validity period, it
could indicate potential backdating or use of an expired certificate, and the
verification will fail.
### Signature Verification
The next verification step is to verify the actual signature. This ensures
that the signed content has not been tampered with and was indeed signed by the
claimed entity.
The verification process differs slightly depending on the type of signed
content:
- DSSE: The entire envelope structure is used as the verification payload.
- Artifacts: The raw bytes of the artifacts serve as the verification payload.
#### Final step
Finally, a last consistency check is performed to verify that the constructed
payload is indeed the one that has been signed. This step is ussed to prevent
variants of [CVE-2022-36056].
[Sigstore: Client Spec]: https://docs.google.com/document/d/1kbhK2qyPPk8SLavHzYSDM8-Ueul9_oxIMVFuWMWKz0E/edit?usp=sharing
[ECDSA]: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
[rekor]: https://github.com/sigstore/rekor
[Braun et al.]: https://research.tue.nl/en/publications/how-to-avoid-the-breakdown-of-public-key-infrastructures-forward-
[CVE-2022-36056]: https://github.com/sigstore/cosign/security/advisories/GHSA-8gw7-4j42-w388
================================================
FILE: docs/scripts/gen_ref_pages.py
================================================
# Copyright 2022 The Sigstore Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import shutil
import sys
from pathlib import Path
root = Path(__file__).parent.parent.parent
src = root / "sigstore"
api_root = root / "docs" / "api"
def main(args: argparse.Namespace) -> None:
"""Main script."""
if args.overwrite:
shutil.rmtree(api_root, ignore_errors=True)
elif not args.check and api_root.exists():
print(f"API root {api_root} already exists, skipping.")
sys.exit(0)
seen = set()
for path in src.rglob("*.py"):
module_path = path.relative_to(src).with_suffix("")
full_doc_path = api_root / path.relative_to(src).with_suffix(".md")
# Exclude private entries
if any(part.startswith("_") for part in module_path.parts):
continue
if args.check and not full_doc_path.is_file():
print(f"File {full_doc_path} does not exist.", file=sys.stderr)
sys.exit(1)
full_doc_path.parent.mkdir(parents=True, exist_ok=True)
with full_doc_path.open("w") as f:
f.write(f":::sigstore.{str(module_path).replace('/', '.')}\n ")
seen.add(full_doc_path)
# Add the root
with (api_root / "index.md").open("w") as f:
f.write("""!!! note
The API reference is automatically generated from the docstrings
:::sigstore
""")
seen.add(api_root / "index.md")
if args.check:
if diff := set(api_root.rglob("*.md")).symmetric_difference(seen):
print(f"Found leftover documentation file: {diff}", file=sys.stderr)
sys.exit(1)
else:
print("API doc generated.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate the structure for the API documentation."
)
parser.add_argument("--overwrite", action="store_true", default=False)
parser.add_argument("--check", action="store_true", default=False)
arguments = parser.parse_args()
if arguments.check and arguments.overwrite:
print("You can't specify both --check and --overwrite.", file=sys.stderr)
sys.exit(1)
main(arguments)
================================================
FILE: docs/signing.md
================================================
# Signing
!!! warning
By default signing an artifact creates a public record in `Rekor` which is publicly available.
The transparency log entry is browsable at `https://search.sigstore.dev/?logIndex=<LOG_INDEX>`
and disclose the signing identity.
## Identities
### Signing with ambient credentials
For environments that support OpenID Connect, `sigstore` supports ambient credential
detection. This includes many popular CI platforms and cloud providers. See the full list of
supported environments [here](https://github.com/di/id#supported-environments).
### Signing with an email identity
`sigstore` can use an OAuth2 + OpenID flow to establish an email identity,
allowing you to request signing certificates that attest to control over
that email.
By default, `sigstore` attempts to do [ambient credential detection](#signing-with-ambient-credentials), which may preempt
the OAuth2 flow. To force the OAuth2 flow, you can explicitly disable ambient detection:
```console
$ sigstore sign --oidc-disable-ambient-providers foo.txt
```
### Signing with an explicit identity token
If you can't use an ambient credential or the OAuth2 flow, you can pass a pre-created
identity token directly into `sigstore sign`:
```console
$ sigstore sign --identity-token YOUR-LONG-JWT-HERE foo.txt
```
Note that passing a custom identity token does not circumvent Fulcio's requirements,
namely the Fulcio's supported identity providers and the claims expected within the token.
!!! note
The examples in the section below are using ambient credential detection.
When no credentials are detected, it opens a browser to perform an interactive OAuth2 authentication flow.
## Signing an artifact
The easiest option to sign an artifact with `sigstore` is to use the `sign` command.
For example, signing `sigstore-python` [README.md](https://github.com/sigstore/sigstore-python/blob/main/README.md).
```console
$ sigstore sign README.md
Waiting for browser interaction...
Using ephemeral certificate:
-----BEGIN CERTIFICATE-----
MIIC2TCCAl+gAwIBAgIUdqkRnuxTr6bgdKtNiItu3+y8UkIwCgYIKoZIzj0EAwMw
NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl
cm1lZGlhdGUwHhcNMjQxMjEyMDk1NTU5WhcNMjQxMjEyMTAwNTU5WjAAMFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAEjb33vsuuNr4phkmpkUvMB19rnXLtS9QqZGT+
kDetyi9+wYv/g2oOFDfEm7UHPLUeZJ6Bad8Zd7H/JqGUhuJ7gaOCAX4wggF6MA4G
A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUJpNq
0mPqLw1ypudG98REMY7mjyowHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y
ZD8wLgYDVR0RAQH/BCQwIoEgYWxleGlzLmNoYWxsYW5kZUB0cmFpbG9mYml0cy5j
b20wKQYKKwYBBAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMCsG
CisGAQQBg78wAQgEHQwbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgor
BgEEAdZ5AgQCBHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p
7o4AAAGTukvv5QAABAMARzBFAiEA3oqdIinnZ9rGb7CTxQ60G6xi6l3T+z6vkSr2
ERAnIp4CIHbx61camOWU8dClH2WMUfguQ11+D82IQQBnHF968g22MAoGCCqGSM49
BAMDA2gAMGUCMQDdf8S5Y/UhAp2vd2eo+RsjtfsasXSI51kO1ppNz42rSa6b5djW
8+we6/OzVQW+THYCMBaBHPNntloKD040Pce6f8W3HpydbUzshJ24Emt/EaTPqH/g
gYd2xz5hd4vQ7Ysmsg==
-----END CERTIFICATE-----
Transparency log entry created at index: 155016378
MEQCIHVjH0I3iarhB5hD0MEE4AZ7GpCPZhXpdsVsSFlZIynVAiA10qzWt9FBC5pjD6+1kLRS14F+muVD1NJZNw6b+/WADQ==
Sigstore bundle written to README.md.sigstore.json
```
The log entry is available at : [https://search.sigstore.dev/?logIndex=155016378](https://search.sigstore.dev/?logIndex=155016378)
## Attest
`sigstore` can be used to generate attestations for software artifacts using [SLSA].
!!! info "What is SLSA?"
Supply-chain Levels for Software Artifacts, or SLSA ("salsa").
It’s a security framework, a checklist of standards and controls to prevent tampering, improve integrity, and secure packages and infrastructure. It’s how you get from "safe enough" to being as resilient as possible, at any link in the chain.
At the moment, `sigstore` supports the following predicates types:
- [https://slsa.dev/provenance/v1](https://slsa.dev/spec/v1.0/provenance)
- [https://slsa.dev/provenance/v0.2](https://slsa.dev/spec/v0.2/provenance)
Example :
```console
$ sigstore attest \
--predicate-type "https://slsa.dev/provenance/v1" \
--predicate ./test/assets/integration/attest/slsa_predicate_v1_0.json \
./README.md
Waiting for browser interaction...
Using ephemeral certificate:
-----BEGIN CERTIFICATE-----
MIIC2TCCAmCgAwIBAgIUI1GUnwGV69rXWAixrFmwAcZ7j7IwCgYIKoZIzj0EAwMw
NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl
cm1lZGlhdGUwHhcNMjQxMjEyMTAxODUwWhcNMjQxMjEyMTAyODUwWjAAMFkwEwYH
KoZIzj0CAQYIKoZIzj0DAQcDQgAEZPieQV37ByUyf+zWMGjXmom+kM4INxPcO1Kf
DhjV3RmhTAlKOYXGU38O/KUNka5BLTb4f5r1bNwGhiEf9qcmNqOCAX8wggF7MA4G
A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUUexC
qnLoKejMCAAgNxN77wSlIHkwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y
ZD8wLgYDVR0RAQH/BCQwIoEgYWxleGlzLmNoYWxsYW5kZUB0cmFpbG9mYml0cy5j
b20wKQYKKwYBBAGDvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMCsG
CisGAQQBg78wAQgEHQwbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGLBgor
BgEEAdZ5AgQCBH0EewB5AHcA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p
7o4AAAGTumDcJAAABAMASDBGAiEAprGPiBTcRK8ZFM+x3HLE+2s82xPAecHfJo9F
RXNI+CMCIQCYzRBQtTehd+LLmwkXjPJEsJ5CpI7q1uDhhspyplVSLjAKBggqhkjO
PQQDAwNnADBkAjAjO7BG9Gx6ggm1/IP75l+LzUnAP/DP0BOBeM0/lXZN3BBUvtdq
+oTUzmmY/VpCWggCMEcCMn4UDIF/jBrVhES8ks57T8LjRX6xacpn9ufpkTlnKs6w
S8/kL6jEREOcdnpOSQ==
-----END CERTIFICATE-----
Transparency log entry created at index: 155019253
Sigstore bundle written to README.md.sigstore.json
```
[SLSA]: https://slsa.dev/
================================================
FILE: docs/stylesheets/custom.css
================================================
/* From https://github.com/sigstore/community/blob/main/artwork/Sigstore_BrandGuide_March2023.pdf */
:root {
--md-primary-fg-color: #2e2f71;
--md-primary-bg-color: #f9f7ef;
}
================================================
FILE: docs/verify.md
================================================
# Verifying
## Generic identities
This is the most common verification done with `sigstore`, and therefore
the one you probably want: you can use it to verify that a signature was
produced by a particular identity (like `hamilcar@example.com`), as attested
to by a particular OIDC provider (like `https://github.com/login/oauth`).
```console
$ sigstore verify identity --cert-identity <IDENTITY> --cert-oidc-issuer <URL> FILE_OR_DIGEST
```
The following command will verify that the bundle `tests/assets/bundle.txt.sigstore` was signed by `a@tny.town` using
the staging infrastructure of `sigstore`.
```console
$ sigstore --staging verify identity --cert-identity "a@tny.town" --cert-oidc-issuer "https://github.com/login/oauth" test/assets/bundle.txt
```
## Verifying from GitHub Actions
If your signatures are coming from GitHub Actions (e.g., a workflow that uses its [ambient credentials](./signing.md#signing-with-ambient-credentials)),
then you can use the `sigstore verify github` subcommand to verify
claims more precisely than `sigstore verify identity` allows.
`sigstore verify github` can be used to verify claims specific to signatures coming from GitHub
Actions. `sigstore-python` signs releases via GitHub Actions, so the examples below are working
examples of how you can verify a given `sigstore-python` release.
When using `sigstore verify github`, you must pass `--cert-identity` or `--repository`, or both.
Unlike `sigstore verify identity`, `--cert-oidc-issuer` is **not** required (since it's
inferred to be GitHub Actions).
Verifying with `--cert-identity`:
```console
$ sigstore verify github sigstore-0.10.0-py3-none-any.whl \
--bundle sigstore-0.10.0-py3-none-any.whl.bundle \
--cert-identity https://github.com/sigstore/sigstore-python/.github/workflows/release.yml@refs/tags/v0.10.0
```
Verifying with `--repository`:
```console
$ sigstore verify github sigstore-0.10.0-py3-none-any.whl \
--bundle sigstore-0.10.0-py3-none-any.whl.bundle \
--repository sigstore/sigstore-python
```
Additional GitHub Actions specific claims can be verified like so:
```console
$ sigstore verify github sigstore-0.10.0-py3-none-any.whl \
--bundle sigstore-0.10.0-py3-none-any.whl.bundle \
--cert-identity https://github.com/sigstore/sigstore-python/.github/workflows/release.yml@refs/tags/v0.10.0 \
--trigger release \
--sha 66581529803929c3ccc45334632ccd90f06e0de4 \
--name Release \
--repository sigstore/sigstore-python \
--ref refs/tags/v0.10.0
```
## Verifying against a bundle
By default, `sigstore verify identity` will attempt to find a `<filename>.sigstore.json`
or `<filename>.sigstore` in the same directory as the file being verified:
```console
# looks for foo.txt.sigstore.json
$ sigstore verify identity foo.txt \
--cert-identity 'hamilcar@example.com' \
--cert-oidc-issuer 'https://github.com/login/oauth'
```
Multiple files can be verified at once:
```console
# looks for {foo,bar}.txt.sigstore.json
$ python -m sigstore verify identity foo.txt bar.txt \
--cert-identity 'hamilcar@example.com' \
--cert-oidc-issuer 'https://github.com/login/oauth'
```
## Verifying a digest instead of a file
`sigstore-python` supports verifying digests directly, without requiring the artifact to be
present. The digest should be prefixed with the `sha256:` string:
```console
$ sigstore verify identity sha256:ce8ab2822671752e201ea1e19e8c85e73d497e1c315bfd9c25f380b7625d1691 \
--cert-identity 'hamilcar@example.com' \
--cert-oidc-issuer 'https://github.com/login/oauth'
--bundle 'foo.txt.sigstore.json'
```
================================================
FILE: install/requirements.in
================================================
sigstore==4.2.0
================================================
FILE: mkdocs.yml
================================================
# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json
site_name: sigstore-python
site_url: https://sigstore.github.io/sigstore-python
repo_url: https://github.com/sigstore/sigstore-python
site_description: sigstore-python, a Sigstore client written in Python
repo_name: sigstore-python
edit_uri: edit/main/docs/
theme:
name: material
icon:
repo: fontawesome/brands/github
logo: assets/images/logo.png
features:
- content.action.edit
- content.code.copy
- header.autohide
- navigation.instant
- navigation.instant.progress
- navigation.footer
- search.highlight
- search.suggest
palette:
primary: custom
font:
text: Inter
extra_css:
- stylesheets/custom.css
nav:
- Home: index.md
- Installation: installation.md
- Signing: signing.md
- Verifying: verify.md
- Policy: policy.md
- Advanced:
- Custom Sigstore Instances: advanced/custom_trust.md
- Offline Verification: advanced/offline.md
# begin-api-section
- API:
- api/index.md
- Models: api/models.md
- Errors: api/errors.md
- Hashes: api/hashes.md
- OIDC: api/oidc.md
- Sign: api/sign.md
- Verify:
- Policy: api/verify/policy.md
- Verifier: api/verify/verifier.md
# end-api-section
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
copyright: sigstore © 2024
plugins:
- search
- social
- mkdocstrings:
handlers:
python:
options:
members_order: source
unwrap_annotated: true
modernize_annotations: true
merge_init_into_class: true
docstring_section_style: spacy
signature_crossrefs: true
show_symbol_type_toc: true
filters:
- '!^_'
validation:
omitted_files: warn
unrecognized_links: warn
anchors: warn
not_found: warn
extra:
generator: false
social:
- icon: fontawesome/brands/slack
link: https://sigstore.slack.com
- icon: fontawesome/brands/x-twitter
link: https://twitter.com/projectsigstore
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "sigstore"
dynamic = ["version"]
description = "A tool for signing Python package distributions"
readme = "README.md"
license = { file = "LICENSE" }
authors = [
{ name = "Sigstore Authors", email = "sigstore-dev@googlegroups.com" },
]
classifiers = [
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Topic :: Security",
"Topic :: Security :: Cryptography",
]
dependencies = [
"cryptography >= 42, < 47",
"id >= 1.1.0",
"importlib_resources ~= 5.7; python_version < '3.11'",
"pyasn1 ~= 0.6",
"pydantic >= 2,< 3",
"pyjwt >= 2.1",
"pyOpenSSL >= 23.0.0",
"requests",
"rich >= 13,< 16",
"rfc8785 ~= 0.1.2",
"rfc3161-client >= 1.0.3,< 1.1.0",
# Both sigstore-models and sigstore-rekor types are unstable
# so we pin them conservatively.
"sigstore-models == 0.0.6",
"sigstore-rekor-types == 0.0.18",
"tuf ~= 6.0",
"platformdirs ~= 4.2",
]
requires-python = ">=3.10"
[project.scripts]
sigstore = "sigstore._cli:main"
[project.urls]
Homepage = "https://pypi.org/project/sigstore/"
Issues = "https://github.com/sigstore/sigstore-python/issues"
Source = "https://github.com/sigstore/sigstore-python"
Documentation = "https://sigstore.github.io/sigstore-python/"
[project.optional-dependencies]
test = ["pytest", "pytest-cov", "pretend", "coverage[toml]"]
lint = [
"bandit",
# "interrogate >= 1.7.0",
"mypy ~= 1.1",
# NOTE(ww): ruff is under active development, so we pin conservatively here
# and let Dependabot periodically perform this update.
"ruff < 0.15.12",
"types-requests",
"types-pyOpenSSL",
]
doc = ["mkdocs-material[imaging]", "mkdocstrings-python"]
dev = ["build", "bump >= 1.3.2", "sigstore[doc,test,lint]"]
[tool.coverage.run]
# branch coverage in addition to statement coverage.
branch = true
# FIXME(jl): currently overridden. see: https://pytest-cov.readthedocs.io/en/latest/config.html
# include machine name, process id, and a random number in `.coverage-*` so each file is distinct.
parallel = true
# store relative path info for aggregation across runs with potentially differing filesystem layouts.
# see: https://coverage.readthedocs.io/en/7.1.0/config.html#config-run-relative-files
relative_files = true
# don't attempt code coverage for the CLI entrypoints
omit = ["sigstore/_cli.py"]
[tool.coverage.report]
exclude_lines = [
"@abc.abstractmethod",
"@typing.overload",
"if typing.TYPE_CHECKING",
]
[tool.interrogate]
# don't enforce documentation coverage for packaging, testing, the virtual
# environment, or the CLI (which is documented separately).
exclude = ["env", "test", "sigstore/_cli.py"]
ignore-semiprivate = true
ignore-private = true
# Ignore nested classes for docstring coverage because we use them primarily
# for pydantic model configuration.
ignore-nested-classes = true
fail-under = 100
[tool.mypy]
allow_redefinition = true
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
ignore_missing_imports = true
no_implicit_optional = true
sqlite_cache = true
strict = true
strict_equality = true
warn_no_return = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true
plugins = ["pydantic.mypy"]
[tool.bandit]
exclude_dirs = ["./test"]
[tool.ruff.lint]
extend-select = ["I", "UP"]
ignore = [
"UP007", # https://github.com/pydantic/pydantic/issues/4146
"UP011",
"UP015",
]
================================================
FILE: sigstore/__init__.py
================================================
# Copyright 2022 The Sigstore Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The `sigstore` Python APIs.
For command-line usage of `sigstore`, refer to the `sigstore`
[README](https://github.com/sigstore/sigstore-python).
Otherwise, here are some quick starting points:
* `sigstore.verify`: verifying of Sigstore signatures,
including flexible policy control
* `sigstore.sign`: creation of Sigstore signatures
"""
__version__ = "4.2.0"
================================================
FILE: sigstore/__main__.py
================================================
# Copyright 2022 The Sigstore Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The `python -m sigstore` entrypoint.
"""
if __name__ == "__main__": # pragma: no cover
from sigstore._cli import main
main()
================================================
FILE: sigstore/_cli.py
================================================
# Copyright 2022 The Sigstore Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import argparse
import base64
import json
import logging
import os
import sys
from concurrent import futures
from dataclasses import dataclass
from pathlib import Path
from typing import Any, NoReturn, TypeAlias, Union
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.x509 import load_pem_x509_certificate
from pydantic import ValidationError
from rich.console import Console
from rich.logging import RichHandler
from sigstore_models.bundle.v1 import Bundle as RawBundle
from sigstore_models.common.v1 import HashAlgorithm
from sigstore import __version__, dsse
from sigstore._internal.fulcio.client import ExpiredCertificate
from sigstore._internal.rekor import _hashedrekord_from_parts
from sigstore._internal.rekor.client import RekorClient
from sigstore._utils import sha256_digest
from sigstore.dsse import StatementBuilder, Subject
from sigstore.dsse._predicate import (
PredicateType,
SLSAPredicateV0_2,
SLSAPredicateV1_0,
)
from sigstore.errors import CertValidationError, Error, VerificationError
from sigstore.hashes import Hashed
from sigstore.models import Bundle, ClientTrustConfig, InvalidBundle
from sigstore.oidc import (
ExpiredIdentity,
IdentityToken,
Issuer,
detect_credential,
)
from sigstore.sign import Signer, SigningContext
from sigstore.verify import (
Verifier,
policy,
)
_console = Console(file=sys.stderr)
logging.basicConfig(
format="%(message)s", datefmt="[%X]", handlers=[RichHandler(console=_console)]
)
_logger = logging.getLogger(__name__)
# NOTE: We configure the top package logger, rather than the root logger,
# to avoid overly verbose logging in third-party code by default.
_package_logger = logging.getLogger("sigstore")
_package_logger.setLevel(os.environ.get("SIGSTORE_LOGLEVEL", "INFO").upper())
@dataclass(frozen=True)
class SigningOutputs:
signature: Path | None = None
certificate: Path | None = None
bundle: Path | None = None
@dataclass(frozen=True)
class VerificationUnbundledMaterials:
certificate: Path
signature: Path
@dataclass(frozen=True)
class VerificationBundledMaterials:
bundle: Path
VerificationMaterials: TypeAlias = Union[
VerificationUnbundledMaterials, VerificationBundledMaterials
]
# Map of inputs -> outputs for signing operations
OutputMap: TypeAlias = dict[Path, SigningOutputs]
def _fatal(message: str) -> NoReturn:
"""
Logs a fatal condition and exits.
"""
_logger.fatal(message)
sys.exit(1)
def _invalid_arguments(args: argparse.Namespace, message: str) -> NoReturn:
"""
An `argparse` helper that fixes up the type hints on our use of
`ArgumentParser.error`.
"""
args._parser.error(message)
raise ValueError("unreachable")
def _boolify_env(envvar: str) -> bool:
"""
An `argparse` helper for turning an environment variable into a boolean.
The semantics here closely mirror `distutils.util.strtobool`.
See: <https://docs.python.org/3/distutils/apiref.html#distutils.util.strtobool>
"""
val = os.getenv(envvar)
if val is None:
return False
val = val.lower()
if val in {"y", "yes", "true", "t", "on", "1"}:
return True
elif val in {"n", "no", "false", "f", "off", "0"}:
return False
else:
raise ValueError(f"can't coerce '{val}' to a boolean")
def _add_shared_verify_input_options(group: argparse._ArgumentGroup) -> None:
"""
Common input options, shared between all `sigstore verify` subcommands.
"""
group.add_argument(
"--certificate",
"--cert",
metavar="FILE",
type=Path,
default=os.getenv("SIGSTORE_CERTIFICATE"),
help="The PEM-encoded certificate to verify against; not used with multiple inputs",
)
group.add_argument(
"--signature",
metavar="FILE",
type=Path,
default=os.getenv("SIGSTORE_SIGNATURE"),
help="The signature to verify against; not used with multiple inputs",
)
group.add_argument(
"--bundle",
metavar="FILE",
type=Path,
default=os.getenv("SIGSTORE_BUNDLE"),
help=("The Sigstore bundle to verify with; not used with multiple inputs"),
)
def file_or_digest(arg: str) -> Hashed | Path:
path = Path(arg)
if path.is_file():
return path
elif arg.startswith("sha256"):
digest = bytes.fromhex(arg[len("sha256:") :])
if len(digest) != 32:
raise ValueError
return Hashed(
digest=digest,
algorithm=HashAlgorithm.SHA2_256,
)
else:
raise ValueError
group.add_argument(
"files_or_digest",
metavar="FILE_OR_DIGEST",
type=file_or_digest,
nargs="+",
help="The file path or the digest to verify. The digest should start with the 'sha256:' prefix.",
)
def _add_shared_verification_options(group: argparse._ArgumentGroup) -> None:
group.add_argument(
"--offline",
action="store_true",
default=_boolify_env("SIGSTORE_OFFLINE"),
help="Perform offline verification; requires a Sigstore bundle",
)
def _add_shared_oidc_options(
group: argparse._ArgumentGroup | argparse.ArgumentParser,
) -> None:
"""
Common OIDC options, shared between `sigstore sign` and `sigstore get-identity-token`.
"""
group.add_argument(
"--oidc-client-id",
metavar="ID",
type=str,
default=os.getenv("SIGSTORE_OIDC_CLIENT_ID", "sigstore"),
help="The custom OpenID Connect client ID to use during OAuth2",
)
group.add_argument(
"--oidc-client-secret",
metavar="SECRET",
type=str,
default=os.getenv("SIGSTORE_OIDC_CLIENT_SECRET"),
help="The custom OpenID Connect client secret to use during OAuth2",
)
group.add_argument(
"--oidc-disable-ambient-providers",
action="store_true",
default=_boolify_env("SIGSTORE_OIDC_DISABLE_AMBIENT_PROVIDERS"),
help="Disable ambient OpenID Connect credential detection (e.g. on GitHub Actions)",
)
group.add_argument(
"--oidc-issuer",
metavar="URL",
type=str,
default=os.getenv("SIGSTORE_OIDC_ISSUER", None),
help="The OpenID Connect issuer to use",
)
group.add_argument(
"--oauth-force-oob",
action="store_true",
default=_boolify_env("SIGSTORE_OAUTH_FORCE_OOB"),
help="Force an out-of-band OAuth flow and do not automatically start the default web browser",
)
def _parser() -> argparse.ArgumentParser:
# Arguments in parent_parser can be used for both commands and subcommands
parent_parser = argparse.ArgumentParser(add_help=False)
parent_parser.add_argument(
"-v",
"--verbose",
action="count",
default=0,
help="run with additional debug logging; supply multiple times to increase verbosity",
)
parser = argparse.ArgumentParser(
prog="sigstore",
description="a tool for signing and verifying Python package distributions",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
parser.add_argument(
"-V", "--version", action="version", version=f"sigstore {__version__}"
)
global_instance_options = parser.add_mutually_exclusive_group()
global_instance_options.add_argument(
"--staging",
action="store_true",
default=_boolify_env("SIGSTORE_STAGING"),
help=(
"Use sigstore's staging instance, instead of the default production instance."
" Mutually exclusive with other instance configuration arguments."
),
)
global_instance_options.add_argument(
"--instance",
metavar="URL",
type=str,
help=(
"Use a given Sigstore instance URL, instead of the default production instance."
" Mutually exclusive with other instance configuration arguments."
),
)
global_instance_options.add_argument(
"--trust-config",
metavar="FILE",
type=Path,
help=(
"Use given client trust configuration instead of using the default production"
" instance. Mutually exclusive with other instance configuration arguments."
),
)
subcommands = parser.add_subparsers(
required=True,
dest="subcommand",
metavar="COMMAND",
help="the operation to perform",
)
# `sigstore attest`
attest = subcommands.add_parser(
"attest",
help="sign one or more inputs using DSSE",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
attest.add_argument(
"--rekor-version",
type=int,
metavar="VERSION",
default=argparse.SUPPRESS,
help="Force the rekor transparency log version. Valid values are [1, 2]. By default the highest available version is used",
)
attest.add_argument(
"files",
metavar="FILE",
type=Path,
nargs="+",
help="The file to sign",
)
dsse_options = attest.add_argument_group("DSSE options")
dsse_options.add_argument(
"--predicate",
metavar="FILE",
type=Path,
required=True,
help="Path to the predicate file",
)
dsse_options.add_argument(
"--predicate-type",
metavar="TYPE",
choices=list(PredicateType),
type=PredicateType,
required=True,
help=f"Specify a predicate type ({', '.join(list(PredicateType))})",
)
oidc_options = attest.add_argument_group("OpenID Connect options")
oidc_options.add_argument(
"--identity-token",
metavar="TOKEN",
type=str,
default=os.getenv("SIGSTORE_IDENTITY_TOKEN"),
help="the OIDC identity token to use",
)
_add_shared_oidc_options(oidc_options)
output_options = attest.add_argument_group("Output options")
output_options.add_argument(
"--bundle",
metavar="FILE",
type=Path,
default=os.getenv("SIGSTORE_BUNDLE"),
help=(
"Write a single Sigstore bundle to the given file; does not work with multiple input "
"files"
),
)
output_options.add_argument(
"--overwrite",
action="store_true",
default=_boolify_env("SIGSTORE_OVERWRITE"),
help="Overwrite preexisting bundle outputs, if present",
)
# `sigstore sign`
sign = subcommands.add_parser(
"sign",
help="sign one or more inputs",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
sign.add_argument(
"--rekor-version",
type=int,
metavar="VERSION",
default=argparse.SUPPRESS,
help="Force the rekor transparency log version. Valid values are [1, 2]. By default the highest available version is used",
)
oidc_options = sign.add_argument_group("OpenID Connect options")
oidc_options.add_argument(
"--identity-token",
metavar="TOKEN",
type=str,
default=os.getenv("SIGSTORE_IDENTITY_TOKEN"),
help="the OIDC identity token to use",
)
_add_shared_oidc_options(oidc_options)
output_options = sign.add_argument_group("Output options")
output_options.add_argument(
"--no-default-files",
action="store_true",
default=_boolify_env("SIGSTORE_NO_DEFAULT_FILES"),
help="Don't emit the default output files ({input}.sigstore.json)",
)
output_options.add_argument(
"--signature",
"--output-signature",
metavar="FILE",
type=Path,
default=os.getenv("SIGSTORE_OUTPUT_SIGNATURE"),
help=(
"Write a single signature to the given file; does not work with multiple input files"
),
)
output_options.add_argument(
"--certificate",
"--output-certificate",
metavar="FILE",
type=Path,
default=os.getenv("SIGSTORE_OUTPUT_CERTIFICATE"),
help=(
"Write a single certificate to the given file; does not work with multiple input files"
),
)
output_options.add_argument(
"--bundle",
metavar="FILE",
type=Path,
default=os.getenv("SIGSTORE_BUNDLE"),
help=(
"Write a single Sigstore bundle to the given file; does not work with multiple input "
"files"
),
)
output_options.add_argument(
"--output-directory",
metavar="DIR",
type=Path,
default=os.getenv("SIGSTORE_OUTPUT_DIRECTORY"),
help=(
"Write default outputs to the given directory (conflicts with --signature, --certificate"
", --bundle)"
),
)
output_options.add_argument(
"--overwrite",
action="store_true",
default=_boolify_env("SIGSTORE_OVERWRITE"),
help="Overwrite preexisting signature and certificate outputs, if present",
)
sign.add_argument(
"files",
metavar="FILE",
type=Path,
nargs="+",
help="The file to sign",
)
# `sigstore verify`
verify = subcommands.add_parser(
"verify",
help="verify one or more inputs",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
verify_subcommand = verify.add_subparsers(
required=True,
dest="verify_subcommand",
metavar="COMMAND",
help="the kind of verification to perform",
)
# `sigstore verify identity`
verify_identity = verify_subcommand.add_parser(
"identity",
help="verify against a known identity and identity provider",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
input_options = verify_identity.add_argument_group("Verification inputs")
_add_shared_verify_input_options(input_options)
verification_options = verify_identity.add_argument_group("Verification options")
_add_shared_verification_options(verification_options)
verification_options.add_argument(
"--cert-identity",
metavar="IDENTITY",
type=str,
default=os.getenv("SIGSTORE_CERT_IDENTITY"),
help="The identity to check for in the certificate's Subject Alternative Name",
required=True,
)
verification_options.add_argument(
"--cert-oidc-issuer",
metavar="URL",
type=str,
default=os.getenv("SIGSTORE_CERT_OIDC_ISSUER"),
help="The OIDC issuer URL to check for in the certificate's OIDC issuer extension",
required=True,
)
# `sigstore verify github`
verify_github = verify_subcommand.add_parser(
"github",
help="verify against GitHub Actions-specific claims",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
input_options = verify_github.add_argument_group("Verification inputs")
_add_shared_verify_input_options(input_options)
verification_options = verify_github.add_argument_group("Verification options")
_add_shared_verification_options(verification_options)
verification_options.add_argument(
"--cert-identity",
metavar="IDENTITY",
type=str,
default=os.getenv("SIGSTORE_CERT_IDENTITY"),
help="The identity to check for in the certificate's Subject Alternative Name",
)
verification_options.add_argument(
"--trigger",
dest="workflow_trigger",
metavar="EVENT",
type=str,
default=os.getenv("SIGSTORE_VERIFY_GITHUB_WORKFLOW_TRIGGER"),
help="The GitHub Actions event name that triggered the workflow",
)
verification_options.add_argument(
"--sha",
dest="workflow_sha",
metavar="SHA",
type=str,
default=os.getenv("SIGSTORE_VERIFY_GITHUB_WORKFLOW_SHA"),
help="The `git` commit SHA that the workflow run was invoked with",
)
verification_options.add_argument(
"--name",
dest="workflow_name",
metavar="NAME",
type=str,
default=os.getenv("SIGSTORE_VERIFY_GITHUB_WORKFLOW_NAME"),
help="The name of the workflow that was triggered",
)
verification_options.add_argument(
"--repository",
dest="workflow_repository",
metavar="REPO",
type=str,
default=os.getenv("SIGSTORE_VERIFY_GITHUB_WORKFLOW_REPOSITORY"),
help="The repository slug that the workflow was triggered under",
)
verification_options.add_argument(
"--ref",
dest="workflow_ref",
metavar="REF",
type=str,
default=os.getenv("SIGSTORE_VERIFY_GITHUB_WORKFLOW_REF"),
help="The `git` ref that the workflow was invoked with",
)
# `sigstore get-identity-token`
get_identity_token = subcommands.add_parser(
"get-identity-token",
help="retrieve and return a Sigstore-compatible OpenID Connect token",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
_add_shared_oidc_options(get_identity_token)
# `sigstore trust-instance`
trust_instance = subcommands.add_parser(
"trust-instance",
help="Initialize trust for a Sigstore instance",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
trust_instance.add_argument(
"root",
metavar="ROOT",
type=Path,
help="The TUF root metadata for the instance",
)
# `sigstore plumbing`
plumbing = subcommands.add_parser(
"plumbing",
help="developer-only plumbing operations",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
plumbing_subcommands = plumbing.add_subparsers(
required=True,
dest="plumbing_subcommand",
metavar="COMMAND",
help="the operation to perform",
)
# `sigstore plumbing fix-bundle`
fix_bundle = plumbing_subcommands.add_parser(
"fix-bundle",
help="fix (and optionally upgrade) older bundle formats",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
fix_bundle.add_argument(
"--bundle",
metavar="FILE",
type=Path,
required=True,
help=("The bundle to fix and/or upgrade"),
)
fix_bundle.add_argument(
"--upgrade-version",
action="store_true",
help="Upgrade the bundle to the latest bundle spec version",
)
fix_bundle.add_argument(
"--in-place",
action="store_true",
help="Overwrite the input bundle with its fix instead of emitting to stdout",
)
# `sigstore plumbing update-trust-root`
plumbing_subcommands.add_parser(
"update-trust-root",
help="update the local trust root to the latest version via TUF",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
parents=[parent_parser],
)
return parser
def main(args: list[str] | None = None) -> None:
if not args:
args = sys.argv[1:]
parser = _parser()
args = parser.parse_args(args)
# Configure logging upfront, so that we don't miss anything.
if args.verbose >= 1:
_package_logger.setLevel("DEBUG")
if args.verbose >= 2:
logging.getLogger().setLevel("DEBUG")
_logger.debug(f"parsed arguments {args}")
# Stuff the parser back into our namespace, so that we can use it for
# error handling later.
args._parser = parser
try:
if args.subcommand == "sign":
_sign(args)
elif args.subcommand == "attest":
_attest(args)
elif args.subcommand == "verify":
if args.verify_subcommand == "identity":
_verify_identity(args)
elif args.verify_subcommand == "github":
_verify_github(args)
elif args.subcommand == "get-identity-token":
_get_identity_token(args)
elif args.subcommand == "trust-instance":
_trust_instance(args)
elif args.subcommand == "plumbing":
if args.plumbing_subcommand == "fix-bundle":
_fix_bundle(args)
elif args.plumbing_subcommand == "update-trust-root":
_update_trust_root(args)
else:
_invalid_arguments(args, f"Unknown subcommand: {args.subcommand}")
except Error as e:
e.log_and_exit(_logger, args.verbose >= 1)
def _trust_instance(args: argparse.Namespace) -> None:
"""
Initialize trust for a Sigstore instance
"""
root: Path = args.root
instance: str | None = args.instance
if not root.is_file():
_invalid_arguments(args, f"Input must be a file: {root}")
if instance is None:
_invalid_arguments(args, "trust-instance requires '--instance URL'")
# ClientTrustConfig construction verifies the root is valid, and
# stores it in the local metadata store for future use
_ = ClientTrustConfig.from_tuf(instance, bootstrap_root=root)
def _get_identity_token(args: argparse.Namespace) -> None:
"""
Output the OIDC authentication token
"""
identity = _get_identity(args, _get_trust_config(args))
if identity:
print(identity)
else:
_invalid_arguments(args, "No identity token supplied or detected!")
def _sign_file_threaded(
signer: Signer,
predicate_type: str | None,
predicate: dict[str, Any] | None,
file: Path,
outputs: SigningOutputs,
) -> None:
"""sign method to be called from signing thread"""
_logger.debug(f"signing for {file.name}")
with file.open(mode="rb") as io:
# The input can be indefinitely large, so we perform a streaming
# digest and sign the prehash rather than buffering it fully.
digest = sha256_digest(io)
try:
if predicate is None:
result = signer.sign_artifact(input_=digest)
else:
subject = Subject(name=file.name, digest={"sha256": digest.digest.hex()})
statement_builder = StatementBuilder(
subjects=[subject],
predicate_type=predicate_type,
predicate=predicate,
)
result = signer.sign_dsse(statement_builder.build())
except ExpiredIdentity as exp_identity:
_logger.error("Signature failed: identity token has expired")
raise exp_identity
except ExpiredCertificate as exp_certificate:
_logger.error("Signature failed: Fulcio signing certificate has expired")
raise exp_certificate
_logger.info(
f"Transparency log entry created at index: {result.log_entry._inner.log_index}"
)
if outputs.signature is not None:
signature = base64.b64encode(result.signature).decode()
with outputs.signature.open(mode="w", encoding="utf-8") as io:
print(signature, file=io)
if outputs.certificate is not None:
cert_pem = signer._signing_cert().public_bytes(Encoding.PEM).decode()
with outputs.certificate.open(mode="w", encoding="utf-8") as io:
print(cert_pem, file=io)
if outputs.bundle is not None:
with outputs.bundle.open(mode="w", encoding="utf-8") as io:
print(result.to_json(), file=io)
def _sign_common(
args: argparse.Namespace, output_map: OutputMap, predicate: dict[str, Any] | None
) -> None:
"""
Signing logic for both `sigstore sign` and `sigstore attest`
Both `sign` and `attest` share the same signing logic, the only change is
whether they sign over a DSSE envelope or a hashedrekord.
This function differentiates between the two using the `predicate` argument. If
present, it will generate an in-toto statement and wrap it in a DSSE envelope. If
not, it will use a hashedrekord.
"""
# Select the signing context to use.
trust_config = _get_trust_config(args)
signing_ctx = SigningContext.from_trust_config(trust_config)
# The order of precedence for identities is as follows:
#
# 1) Explicitly supplied identity token
# 2) Ambient credential detected in the environment, unless disabled
# 3) Interactive OAuth flow
identity: IdentityToken | None
if args.identity_token:
identity = IdentityToken(args.identity_token, args.oidc_client_id)
else:
identity = _get_identity(args, trust_config)
if not identity:
_invalid_arguments(args, "No identity token supplied or detected!")
# Not all commands provide --predicate-type
predicate_type = getattr(args, "predicate_type", None)
with signing_ctx.signer(identity) as signer:
print("Using ephemeral certificate:")
cert_pem = signer._signing_cert().public_bytes(Encoding.PEM).decode()
print(cert_pem)
# sign in threads: this is relevant for especially Rekor v2 as otherwise we wait
# for log inclusion for each signature separately
with futures.ThreadPoolExecutor() as executor:
jobs = [
executor.submit(
_sign_file_threaded,
signer,
predicate_type,
predicate,
file,
outputs,
)
for file, outputs in output_map.items()
]
for job in futures.as_completed(jobs):
job.result()
for file, outputs in output_map.items():
if outputs.signature is not None:
print(f"Signature written to {outputs.signature}")
if outputs.certificate is not None:
print(f"Certificate written to {outputs.certificate}")
if outputs.bundle is not None:
print(f"Sigstore bundle written to {outputs.bundle}")
def _attest(args: argparse.Namespace) -> None:
predicate_path = args.predicate
if not predicate_path.is_file():
_invalid_arguments(args, f"Predicate must be a file: {predicate_path}")
try:
with open(predicate_path, "r", encoding="utf-8") as f:
predicate = json.load(f)
# We do a basic sanity check using our Pydantic models to see if the
# contents of the predicate file match the specified predicate type.
# Since most of the predicate fields are optional, this only checks that
# the fields that are present and correctly spelled have the expected
# type.
if args.predicate_type == PredicateType.SLSA_v0_2:
SLSAPredicateV0_2.model_validate(predicate)
elif args.predicate_type == PredicateType.SLSA_v1_0:
SLSAPredicateV1_0.model_validate(predicate)
else:
_invalid_arguments(
args,
f'Unsupported predicate type "{args.predicate_type}". Predicate type must be one of: {list(PredicateType)}',
)
except (ValidationError, json.JSONDecodeError) as e:
_invalid_arguments(
args, f'Unable to parse predicate of type "{args.predicate_type}": {e}'
)
# Build up the map of inputs -> outputs ahead of any signing operations,
# so that we can fail early if overwriting without `--overwrite`.
output_map: OutputMap = {}
for file in args.files:
if not file.is_file():
_invalid_arguments(args, f"Input must be a file: {file}")
bundle = args.bundle
output_dir = file.parent
if not bundle:
bundle = output_dir / f"{file.name}.sigstore.json"
if bundle and bundle.exists() and not args.overwrite:
_invalid_arguments(
args,
f"Refusing to overwrite outputs without --overwrite: {bundle}",
)
output_map[file] = SigningOutputs(bundle=bundle)
# We sign the contents of the predicate file, rather than signing the Pydantic
# model's JSON dump. This is because doing a JSON -> Model -> JSON roundtrip might
# change the original predicate if it doesn't match exactly our Pydantic model
# (e.g.: if it has extra fields).
_sign_common(args, output_map=output_map, predicate=predicate)
def _sign(args: argparse.Namespace) -> None:
has_sig = bool(args.signature)
has_crt = bool(args.certificate)
has_bundle = bool(args.bundle)
# `--no-default-files` has no effect on `--bundle`, but we forbid it because
# it indicates user confusion.
if args.no_default_files and has_bundle:
_invalid_arguments(
args, "--no-default-files may not be combined with --bundle."
)
# Fail if `--signature` or `--certificate` is specified *and* we have more
# than one input.
if (has_sig or has_crt or has_bundle) and len(args.files) > 1:
_invalid_arguments(
args,
"Error: --signature, --certificate, and --bundle can't be used with "
"explicit outputs for multiple inputs.",
)
if args.output_directory and (has_sig or has_crt or has_bundle):
_invalid_arguments(
args,
"Error: --signature, --certificate, and --bundle can't be used with "
"an explicit output directory.",
)
# Fail if either `--signature` or `--certificate` is specified, but not both.
if has_sig ^ has_crt:
_invalid_arguments(
args, "Error: --signature and --certificate must be used together."
)
# Build up the map of inputs -> outputs ahead of any signing operations,
# so that we can fail early if overwriting without `--overwrite`.
output_map: OutputMap = {}
for file in args.files:
if not file.is_file():
_invalid_arguments(args, f"Input must be a file: {file}")
sig, cert, bundle = (
args.signature,
args.certificate,
args.bundle,
)
output_dir = args.output_directory or file.parent
if output_dir.exists() and not output_dir.is_dir():
_invalid_arguments(
args, f"Output directory exists and is not a directory: {output_dir}"
)
output_dir.mkdir(parents=True, exist_ok=True)
if not bundle and not args.no_default_files:
bundle = output_dir / f"{file.name}.sigstore.json"
if not args.overwrite:
extants = []
if sig and sig.exists():
extants.append(str(sig))
if cert and cert.exists():
extants.append(str(cert))
if bundle and bundle.exists():
extants.append(str(bundle))
if extants:
_invalid_arguments(
args,
"Refusing to overwrite outputs without --overwrite: "
f"{', '.join(extants)}",
)
output_map[file] = SigningOutputs(
signature=sig, certificate=cert, bundle=bundle
)
_sign_common(args, output_map=output_map, predicate=None)
def _collect_verification_state(
args: argparse.Namespace,
) -> tuple[Verifier, list[tuple[Path | Hashed, Hashed, Bundle]]]:
"""
Performs CLI functionality common across all `sigstore verify` subcommands.
Returns a tuple of the active verifier instance and a list of `(path, hashed, bundle)`
tuples, where `path` is the filename for display purposes, `hashed` is the
pre-hashed input to the file being verified and `bundle` is the `Bundle` to verify with.
"""
# Fail if --certificate, --signature, or --bundle is specified, and we
# have more than one input.
if (args.certificate or args.signature or args.bundle) and len(
args.files_or_digest
) > 1:
_invalid_arguments(
args,
"--certificate, --signature, or --bundle can only be used "
"with a single input file or digest",
)
# Fail if `--certificate` or `--signature` is used with `--bundle`.
if args.bundle and (args.certificate or args.signature):
_invalid_arguments(
args, "--bundle cannot be used with --certificate or --signature"
)
# Fail if digest input is not used with `--bundle` or both `--certificate` and `--signature`.
if any(isinstance(x, Hashed) for x in args.files_or_digest):
if not args.bundle and not (args.certificate and args.signature):
_invalid_arguments(
args,
"verifying a digest input (sha256:*) needs either --bundle or both --certificate and --signature",
)
# Fail if `--certificate` or `--signature` is used with `--offline`.
if args.offline and (args.certificate or args.signature):
_invalid_arguments(
args, "--offline cannot be used with --certificate or --signature"
)
# The converse of `sign`: we build up an expected input map and check
# that we have everything so that we can fail early.
input_map: dict[Path | Hashed, VerificationMaterials] = {}
for file in (f for f in args.files_or_digest if isinstance(f, Path)):
if not file.is_file():
_invalid_arguments(args, f"Input must be a file: {file}")
sig, cert, bundle = (
args.signature,
args.certificate,
args.bundle,
)
if sig is None:
sig = file.parent / f"{file.name}.sig"
if cert is None:
cert = file.parent / f"{file.name}.crt"
if bundle is None:
# NOTE(ww): If the user hasn't specified a bundle via `--bundle` and
# `{input}.sigstore.json` doesn't exist, then we try `{input}.sigstore`
# for backwards compatibility.
legacy_default_bundle = file.parent / f"{file.name}.sigstore"
bundle = file.parent / f"{file.name}.sigstore.json"
if not bundle.is_file() and legacy_default_bundle.is_file():
if not cert.is_file() or not sig.is_file():
# NOTE(ww): Only show this warning if bare materials
# are not provided, since bare materials take precedence over
# a .sigstore bundle.
_logger.warning(
f"{file}: {legacy_default_bundle} should be named {bundle}. "
"Support for discovering 'bare' .sigstore inputs will be deprecated in "
"a future release."
)
bundle = legacy_default_bundle
elif bundle.is_file() and legacy_default_bundle.is_file():
# Don't allow the user to implicitly verify `{input}.sigstore.json` if
# `{input}.sigstore` is also present, since this implies user confusion.
_invalid_arguments(
args,
f"Conflicting inputs: {bundle} and {legacy_default_bundle}",
)
missing = []
if args.signature or args.certificate:
if not sig.is_file():
missing.append(str(sig))
if not cert.is_file():
missing.append(str(cert))
input_map[file] = VerificationUnbundledMaterials(
certificate=cert, signature=sig
)
else:
# If a user hasn't explicitly supplied `--signature` or `--certificate`,
# we expect a bundle either supplied via `--bundle` or with the
# default `{input}.sigstore(.json)?` name.
if not bundle.is_file():
missing.append(str(bundle))
input_map[file] = VerificationBundledMaterials(bundle=bundle)
if missing:
_invalid_arguments(
args,
f"Missing verification materials for {(file)}: {', '.join(missing)}",
)
if not input_map:
if len(args.files_or_digest) != 1:
# This should never happen, since if `input_map` is empty that means there
# were no file inputs, and therefore exactly one digest input should be
# present.
_invalid_arguments(
args, "Internal error: Found multiple digests in CLI arguments"
)
hashed = args.files_or_digest[0]
sig, cert, bundle = (
args.signature,
args.certificate,
args.bundle,
)
missing = []
if args.signature or args.certificate:
if not sig.is_file():
missing.append(str(sig))
if not cert.is_file():
missing.append(str(cert))
input_map[hashed] = VerificationUnbundledMaterials(
certificate=cert, signature=sig
)
else:
# If a user hasn't explicitly supplied `--signature` or `--certificate`,
# we expect a bundle supplied via `--bundle`
if not bundle.is_file():
missing.append(str(bundle))
input_map[hashed] = VerificationBundledMaterials(bundle=bundle)
if missing:
_invalid_arguments(
args,
f"Missing verification materials for {(hashed)}: {', '.join(missing)}",
)
trust_config = _get_trust_config(args)
verifier = Verifier(trusted_root=trust_config.trusted_root)
all_materials = []
for file_or_hashed, materials in input_map.items():
if isinstance(file_or_hashed, Path):
with file_or_hashed.open(mode="rb") as io:
hashed = sha256_digest(io)
else:
hashed = file_or_hashed
if isinstance(materials, VerificationBundledMaterials):
# Load the bundle
_logger.debug(f"Using bundle from: {materials.bundle}")
bundle_bytes = materials.bundle.read_bytes()
bundle = Bundle.from_json(bundle_bytes)
else:
# Load the signing certificate
_logger.debug(f"Using certificate from: {materials.certificate}")
cert = load_pem_x509_certificate(materials.certificate.read_bytes())
# Load the signature
_logger.debug(f"Using signature from: {materials.signature}")
b64_signature = materials.signature.read_text(encoding="utf-8")
signature = base64.b64decode(b64_signature)
# When using "detached" materials, we *must* retrieve the log
# entry from the online log.
# TODO: This should be abstracted somewhere much better.
log_entry = verifier._rekor.log.entries.retrieve.post(
_hashedrekord_from_parts(cert, signature, hashed)
)
if log_entry is None:
_invalid_arguments(
args,
f"No matching log entry for {file_or_hashed}'s verification materials",
)
bundle = Bundle.from_parts(cert, signature, log_entry)
_logger.debug(f"Verifying contents from: {file_or_hashed}")
all_materials.append((file_or_hashed, hashed, bundle))
return (verifier, all_materials)
def _verify_identity(args: argparse.Namespace) -> None:
verifier, materials = _collect_verification_state(args)
for file_or_digest, hashed, bundle in materials:
policy_ = policy.Identity(
identity=args.cert_identity,
issuer=args.cert_oidc_issuer,
)
try:
statement = _verify_common(verifier, hashed, bundle, policy_)
print(f"OK: {file_or_digest}", file=sys.stderr)
if statement is not None:
print(statement._contents.decode())
except Error as exc:
if isinstance(exc, CertValidationError):
_logger.warning(
"A certificate chain was not valid, are you using the correct Sigstore instance?"
)
_logger.error(f"FAIL: {file_or_digest}")
exc.log_and_exit(_logger, args.verbose >= 1)
def _verify_github(args: argparse.Namespace) -> None:
inner_policies: list[policy.VerificationPolicy] = []
# We require at least one of `--cert-identity` or `--repository`,
# to minimize the risk of user confusion about what's being verified.
if not (args.cert_identity or args.workflow_repository):
_invalid_arguments(args, "--cert-identity or --repository is required")
# No matter what the user configures above, we require the OIDC issuer to
# be GitHub Actions.
inner_policies.append(
policy.OIDCIssuer("https://token.actions.githubusercontent.com")
)
if args.cert_identity:
inner_policies.append(
policy.Identity(
identity=args.cert_identity,
# We always explicitly check the issuer below, so configuring
# it here is unnecessary.
issuer=None,
)
)
if args.workflow_trigger:
inner_policies.append(policy.GitHubWorkflowTrigger(args.workflow_trigger))
if args.workflow_sha:
inner_policies.append(policy.GitHubWorkflowSHA(args.workflow_sha))
if args.workflow_name:
inner_policies.append(policy.GitHubWorkflowName(args.workflow_name))
if args.workflow_repository:
inner_policies.append(policy.GitHubWorkflowRepository(args.workflow_repository))
if args.workflow_ref:
inner_policies.append(policy.GitHubWorkflowRef(args.workflow_ref))
policy_ = policy.AllOf(inner_policies)
verifier, materials = _collect_verification_state(args)
for file_or_digest, hashed, bundle in materials:
try:
statement = _verify_common(verifier, hashed, bundle, policy_)
print(f"OK: {file_or_digest}", file=sys.stderr)
if statement is not None:
print(statement._contents)
except Error as exc:
if isinstance(exc, CertValidationError):
_logger.warning(
"A certificate chain was not valid, are you using the correct Sigstore instance?"
)
_logger.error(f"FAIL: {file_or_digest}")
exc.log_and_exit(_logger, args.verbose >= 1)
def _verify_common(
verifier: Verifier,
hashed: Hashed,
bundle: Bundle,
policy_: policy.VerificationPolicy,
) -> dsse.Statement | None:
"""
Common verification handling.
This dispatches to either artifact or DSSE verification, depending on
`bundle`'s inner type.
If verifying a DSSE envelope, return the wrapped in-toto statement if
verification succeeds
"""
# If the bundle specifies a DSSE envelope, perform DSSE verification
# and assert that the inner payload is an in-toto statement bound
# to a subject matching the input's digest.
if bundle._dsse_envelope:
type_, payload = verifier.verify_dsse(bundle=bundle, policy=policy_)
if type_ != dsse.Envelope._TYPE:
raise VerificationError(f"expected JSON payload for DSSE, got {type_}")
stmt = dsse.Statement(payload)
if not stmt._matches_digest(hashed):
raise VerificationError(
f"in-toto statement has no subject for digest {hashed.digest.hex()}"
)
return stmt
else:
verifier.verify_artifact(
input_=hashed,
bundle=bundle,
policy=policy_,
)
return None
def _get_trust_config(args: argparse.Namespace) -> ClientTrustConfig:
"""
Return the client trust configuration (Sigstore service URLs, key material and lifetimes)
The configuration may come from explicit argument (--trust-config) or from the TUF
repository of the used Sigstore instance (--staging or --instance).
"""
# Not all commands provide --offline
offline = getattr(args, "offline", False)
if args.trust_config:
trust_config = ClientTrustConfig.from_json(args.trust_config.read_text())
elif args.instance:
trust_config = ClientTrustConfig.from_tuf(args.instance, offline=offline)
elif args.staging:
trust_config = ClientTrustConfig.staging(offline=offline)
else:
trust_config = ClientTrustConfig.production(offline=offline)
# Enforce rekor version if --rekor-version is used
trust_config.force_tlog_version = getattr(args, "rekor_version", None)
return trust_config
def _get_identity(
args: argparse.Namespace, trust_config: ClientTrustConfig
) -> IdentityToken | None:
token = None
if not args.oidc_disable_ambient_providers:
token = detect_credential(args.oidc_client_id)
# Happy path: we've detected an ambient credential, so we can return early.
if token:
return IdentityToken(token, args.oidc_client_id)
if args.oidc_issuer is not None:
issuer = Issuer(args.oidc_issuer)
else:
issuer = Issuer(trust_config.signing_config.get_oidc_url())
if args.oidc_client_secret is None:
args.oidc_client_secret = "" # nosec: B105
token = issuer.identity_token(
client_id=args.oidc_client_id,
client_secret=args.oidc_client_secret,
force_oob=args.oauth_force_oob,
)
return token
def _fix_bundle(args: argparse.Namespace) -> None:
# NOTE: We could support `--trusted-root` here in the future,
# for custom Rekor instances.
rekor = RekorClient.staging() if args.staging else RekorClient.production()
raw_bundle = RawBundle.from_json(args.bundle.read_bytes())
if len(raw_bundle.verification_material.tlog_entries) != 1:
_fatal("unfixable bundle: must have exactly one log entry")
# Some old versions of sigstore-python (1.x) produce malformed
# bundles where the inclusion proof is present but without
# its checkpoint. We fix these by retrieving the
gitextract_rwb714tf/
├── .gitattributes
├── .github/
│ ├── actions/
│ │ └── upload-coverage/
│ │ └── action.yml
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── check-embedded-root.yml
│ ├── ci.yml
│ ├── conformance.yml
│ ├── cross-os.yml
│ ├── cross-version-verify.yaml
│ ├── depsreview.yml
│ ├── docs.yml
│ ├── lint.yml
│ ├── pin-requirements.yml
│ ├── release.yml
│ ├── requirements.yml
│ ├── scorecards-analysis.yml
│ └── staging-tests.yml
├── .gitignore
├── CHANGELOG.md
├── CODEOWNERS
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── cloudbuild.yaml
├── docs/
│ ├── advanced/
│ │ ├── custom_trust.md
│ │ └── offline.md
│ ├── api/
│ │ ├── errors.md
│ │ ├── hashes.md
│ │ ├── index.md
│ │ ├── models.md
│ │ ├── oidc.md
│ │ ├── sign.md
│ │ └── verify/
│ │ ├── policy.md
│ │ └── verifier.md
│ ├── index.md
│ ├── installation.md
│ ├── policy.md
│ ├── scripts/
│ │ └── gen_ref_pages.py
│ ├── signing.md
│ ├── stylesheets/
│ │ └── custom.css
│ └── verify.md
├── install/
│ └── requirements.in
├── mkdocs.yml
├── pyproject.toml
├── sigstore/
│ ├── __init__.py
│ ├── __main__.py
│ ├── _cli.py
│ ├── _internal/
│ │ ├── __init__.py
│ │ ├── fulcio/
│ │ │ ├── __init__.py
│ │ │ └── client.py
│ │ ├── key_details.py
│ │ ├── merkle.py
│ │ ├── oidc/
│ │ │ ├── __init__.py
│ │ │ └── oauth.py
│ │ ├── rekor/
│ │ │ ├── __init__.py
│ │ │ ├── checkpoint.py
│ │ │ ├── client.py
│ │ │ └── client_v2.py
│ │ ├── sct.py
│ │ ├── timestamp.py
│ │ ├── trust.py
│ │ └── tuf.py
│ ├── _store/
│ │ ├── __init__.py
│ │ ├── https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/
│ │ │ ├── root.json
│ │ │ ├── signing_config.v0.2.json
│ │ │ └── trusted_root.json
│ │ └── https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/
│ │ ├── root.json
│ │ ├── signing_config.v0.2.json
│ │ └── trusted_root.json
│ ├── _utils.py
│ ├── dsse/
│ │ ├── __init__.py
│ │ └── _predicate.py
│ ├── errors.py
│ ├── hashes.py
│ ├── models.py
│ ├── oidc.py
│ ├── py.typed
│ ├── sign.py
│ └── verify/
│ ├── __init__.py
│ ├── policy.py
│ └── verifier.py
└── test/
├── assets/
│ ├── a.dsse.staging-rekor-v2.txt
│ ├── a.dsse.staging-rekor-v2.txt.sigstore.json
│ ├── a.txt
│ ├── a.txt.crt
│ ├── a.txt.sig
│ ├── b.txt
│ ├── b.txt.crt
│ ├── b.txt.sig
│ ├── bad.txt
│ ├── bad.txt.crt
│ ├── bad.txt.sig
│ ├── bundle.txt
│ ├── bundle.txt.crt
│ ├── bundle.txt.sig
│ ├── bundle.txt.sigstore
│ ├── bundle_cve_2022_36056.txt
│ ├── bundle_cve_2022_36056.txt.sigstore
│ ├── bundle_invalid_version.txt
│ ├── bundle_invalid_version.txt.sigstore
│ ├── bundle_no_cert_v1.txt
│ ├── bundle_no_cert_v1.txt.sigstore
│ ├── bundle_no_checkpoint.txt
│ ├── bundle_no_checkpoint.txt.bundle
│ ├── bundle_no_checkpoint.txt.crt
│ ├── bundle_no_checkpoint.txt.sigstore
│ ├── bundle_no_log_entry.txt
│ ├── bundle_no_log_entry.txt.sigstore
│ ├── bundle_v3.txt
│ ├── bundle_v3.txt.sigstore
│ ├── bundle_v3_alt.txt
│ ├── bundle_v3_alt.txt.sigstore
│ ├── bundle_v3_github.whl
│ ├── bundle_v3_github.whl.sigstore
│ ├── bundle_v3_no_signed_time.txt
│ ├── bundle_v3_no_signed_time.txt.sigstore.json
│ ├── c.txt
│ ├── c.txt.crt
│ ├── c.txt.sig
│ ├── integration/
│ │ ├── Python-3.12.5.tgz.sigstore
│ │ ├── a.txt
│ │ ├── attest/
│ │ │ ├── slsa_predicate_v0_2.json
│ │ │ └── slsa_predicate_v1_0.json
│ │ ├── b.txt
│ │ ├── bundle_v3.txt
│ │ ├── bundle_v3.txt.sigstore
│ │ └── c.txt
│ ├── offline-rekor.txt
│ ├── offline-rekor.txt.crt
│ ├── offline-rekor.txt.sig
│ ├── signing_config/
│ │ ├── signingconfig-only-v1-rekor.v2.json
│ │ └── signingconfig.v2.json
│ ├── staging-rekor-v2.txt
│ ├── staging-rekor-v2.txt.sigstore.json
│ ├── staging-tuf/
│ │ ├── 16.snapshot.json
│ │ ├── 17.targets.json
│ │ ├── targets/
│ │ │ ├── 0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem
│ │ │ ├── 0f395087486ba318321eda478d847962b1dd89846c7dc6e95752a6b110669393.signing_config.v0.2.json
│ │ │ ├── 1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub
│ │ │ ├── 7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub
│ │ │ ├── 782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem
│ │ │ └── ed6a9cf4e7c2e3297a4b5974fce0d17132f03c63512029d7aa3a402b43acab49.trusted_root.json
│ │ └── timestamp.json
│ ├── trust_config/
│ │ ├── config.badtype.json
│ │ └── config.v1.json
│ ├── trusted_root/
│ │ ├── certificate_authority.empty.json
│ │ ├── certificate_authority.json
│ │ ├── certificate_authority.missingroot.json
│ │ ├── trustedroot.badtype.json
│ │ ├── trustedroot.v1.json
│ │ └── trustedroot.v1.local_tlog_ed25519_rekor-tiles.json
│ ├── tsa/
│ │ ├── bundle.duplicate.sigstore
│ │ ├── bundle.many_timestamp.sigstore
│ │ ├── bundle.txt
│ │ ├── bundle.txt.late_timestamp.sigstore
│ │ ├── bundle.txt.sigstore
│ │ ├── ca.json
│ │ ├── issue1482-message
│ │ ├── issue1482-timestamp-with-no-cert
│ │ └── trust_config.json
│ └── x509/
│ ├── bogus-intermediate-with-eku.pem
│ ├── bogus-intermediate.pem
│ ├── bogus-leaf-invalid-eku.pem
│ ├── bogus-leaf-invalid-ku.pem
│ ├── bogus-leaf-missing-eku.pem
│ ├── bogus-leaf.pem
│ ├── bogus-root-invalid-ku.pem
│ ├── bogus-root-missing-ku.pem
│ ├── bogus-root-noncritical-bc.pem
│ ├── bogus-root.pem
│ ├── build-testcases.py
│ ├── nonroot-privkey.pem
│ └── root-privkey.pem
├── conftest.py
├── integration/
│ ├── cli/
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_attest.py
│ │ ├── test_plumbing.py
│ │ ├── test_sign.py
│ │ └── test_verify.py
│ └── sigstore-python-conformance
└── unit/
├── __init__.py
├── conftest.py
├── internal/
│ ├── __init__.py
│ ├── fulcio/
│ │ ├── __init__.py
│ │ └── test_client.py
│ ├── oidc/
│ │ ├── __init__.py
│ │ └── test_issuer.py
│ ├── rekor/
│ │ ├── __init__.py
│ │ └── test_client_v2.py
│ ├── test_key_details.py
│ ├── test_sct.py
│ ├── test_timestamping.py
│ └── test_trust.py
├── test_dsse.py
├── test_hashes.py
├── test_models.py
├── test_oidc.py
├── test_session_reuse.py
├── test_sign.py
├── test_store.py
├── test_utils.py
├── test_version.py
└── verify/
├── __init__.py
├── test_policy.py
└── test_verifier.py
SYMBOL INDEX (569 symbols across 49 files)
FILE: docs/scripts/gen_ref_pages.py
function main (line 24) | def main(args: argparse.Namespace) -> None:
FILE: sigstore/_cli.py
class SigningOutputs (line 75) | class SigningOutputs:
class VerificationUnbundledMaterials (line 82) | class VerificationUnbundledMaterials:
class VerificationBundledMaterials (line 88) | class VerificationBundledMaterials:
function _fatal (line 100) | def _fatal(message: str) -> NoReturn:
function _invalid_arguments (line 108) | def _invalid_arguments(args: argparse.Namespace, message: str) -> NoReturn:
function _boolify_env (line 117) | def _boolify_env(envvar: str) -> bool:
function _add_shared_verify_input_options (line 138) | def _add_shared_verify_input_options(group: argparse._ArgumentGroup) -> ...
function _add_shared_verification_options (line 189) | def _add_shared_verification_options(group: argparse._ArgumentGroup) -> ...
function _add_shared_oidc_options (line 198) | def _add_shared_oidc_options(
function _parser (line 239) | def _parser() -> argparse.ArgumentParser:
function main (line 626) | def main(args: list[str] | None = None) -> None:
function _trust_instance (line 670) | def _trust_instance(args: argparse.Namespace) -> None:
function _get_identity_token (line 686) | def _get_identity_token(args: argparse.Namespace) -> None:
function _sign_file_threaded (line 697) | def _sign_file_threaded(
function _sign_common (line 748) | def _sign_common(
function _attest (line 812) | def _attest(args: argparse.Namespace) -> None:
function _sign (line 867) | def _sign(args: argparse.Namespace) -> None:
function _collect_verification_state (line 947) | def _collect_verification_state(
function _verify_identity (line 1139) | def _verify_identity(args: argparse.Namespace) -> None:
function _verify_github (line 1163) | def _verify_github(args: argparse.Namespace) -> None:
function _verify_common (line 1216) | def _verify_common(
function _get_trust_config (line 1254) | def _get_trust_config(args: argparse.Namespace) -> ClientTrustConfig:
function _get_identity (line 1279) | def _get_identity(
function _fix_bundle (line 1307) | def _fix_bundle(args: argparse.Namespace) -> None:
function _update_trust_root (line 1346) | def _update_trust_root(args: argparse.Namespace) -> None:
FILE: sigstore/_internal/fulcio/client.py
class ExpiredCertificate (line 46) | class ExpiredCertificate(Exception):
class FulcioCertificateSigningResponse (line 51) | class FulcioCertificateSigningResponse:
class FulcioTrustBundleResponse (line 59) | class FulcioTrustBundleResponse:
class FulcioClientError (line 65) | class FulcioClientError(Exception):
class _Endpoint (line 73) | class _Endpoint(ABC):
method __init__ (line 74) | def __init__(self, url: str, session: requests.Session) -> None:
function _serialize_cert_request (line 79) | def _serialize_cert_request(req: CertificateSigningRequest) -> str:
class FulcioSigningCert (line 88) | class FulcioSigningCert(_Endpoint):
method post (line 93) | def post(
class FulcioTrustBundle (line 137) | class FulcioTrustBundle(_Endpoint):
method get (line 142) | def get(self) -> FulcioTrustBundleResponse:
class FulcioClient (line 161) | class FulcioClient:
method __init__ (line 164) | def __init__(self, url: str) -> None:
method __del__ (line 175) | def __del__(self) -> None:
method signing_cert (line 182) | def signing_cert(self) -> FulcioSigningCert:
method trust_bundle (line 191) | def trust_bundle(self) -> FulcioTrustBundle:
FILE: sigstore/_internal/key_details.py
function _get_key_details (line 24) | def _get_key_details(certificate: Certificate) -> PublicKeyDetails:
FILE: sigstore/_internal/merkle.py
function _decomp_inclusion_proof (line 40) | def _decomp_inclusion_proof(index: int, size: int) -> tuple[int, int]:
function _chain_inner (line 55) | def _chain_inner(seed: bytes, hashes: list[bytes], log_index: int) -> by...
function _chain_border_right (line 71) | def _chain_border_right(seed: bytes, hashes: list[bytes]) -> bytes:
function _hash_children (line 82) | def _hash_children(lhs: bytes, rhs: bytes) -> bytes:
function _hash_leaf (line 88) | def _hash_leaf(leaf: bytes) -> bytes:
function verify_merkle_inclusion (line 94) | def verify_merkle_inclusion(entry: TransparencyLogEntry) -> None:
FILE: sigstore/_internal/oidc/oauth.py
class _OAuthFlow (line 104) | class _OAuthFlow:
method __init__ (line 105) | def __init__(self, client_id: str, client_secret: str, issuer: Issuer):
method __enter__ (line 117) | def __enter__(self) -> _OAuthRedirectServer:
method __exit__ (line 122) | def __exit__(
class _OAuthRedirectHandler (line 132) | class _OAuthRedirectHandler(http.server.BaseHTTPRequestHandler):
method log_message (line 136) | def log_message(self, format: str, *_args: Any) -> None:
method do_GET (line 139) | def do_GET(self) -> None:
class _OAuthSession (line 181) | class _OAuthSession:
method __init__ (line 182) | def __init__(self, client_id: str, client_secret: str, issuer: Issuer):
method state (line 195) | def state(self) -> str:
method code_challenge (line 199) | def code_challenge(self) -> str:
method auth_endpoint (line 208) | def auth_endpoint(self, redirect_uri: str) -> str:
method _auth_params (line 221) | def _auth_params(self, redirect_uri: str) -> dict[str, Any]:
class _OAuthRedirectServer (line 234) | class _OAuthRedirectServer(http.server.HTTPServer):
method __init__ (line 235) | def __init__(self, client_id: str, client_secret: str, issuer: Issuer)...
method base_uri (line 242) | def base_uri(self) -> str:
method auth_request_path (line 248) | def auth_request_path(self) -> str:
method redirect_path (line 253) | def redirect_path(self) -> str:
method redirect_uri (line 257) | def redirect_uri(self) -> str:
method auth_endpoint (line 265) | def auth_endpoint(self) -> str:
method enable_oob (line 268) | def enable_oob(self) -> None:
method is_oob (line 272) | def is_oob(self) -> bool:
FILE: sigstore/_internal/rekor/__init__.py
class RekorClientError (line 43) | class RekorClientError(Exception):
method __init__ (line 48) | def __init__(self, http_error: requests.HTTPError):
class RekorLogSubmitter (line 64) | class RekorLogSubmitter(ABC):
method create_entry (line 72) | def create_entry(
method _build_hashed_rekord_request (line 83) | def _build_hashed_rekord_request(
method _build_dsse_request (line 93) | def _build_dsse_request(
function _hashedrekord_from_parts (line 103) | def _hashedrekord_from_parts(
FILE: sigstore/_internal/rekor/checkpoint.py
class RekorSignature (line 38) | class RekorSignature:
class LogCheckpoint (line 52) | class LogCheckpoint(BaseModel):
method from_text (line 70) | def from_text(cls, text: str) -> LogCheckpoint:
method to_text (line 94) | def to_text(self) -> str:
class SignedNote (line 105) | class SignedNote:
method from_text (line 114) | def from_text(cls, text: str) -> SignedNote:
method verify (line 166) | def verify(self, rekor_keyring: RekorKeyring, key_id: KeyID) -> None:
class SignedCheckpoint (line 191) | class SignedCheckpoint:
method from_text (line 200) | def from_text(cls, text: str) -> SignedCheckpoint:
function verify_checkpoint (line 210) | def verify_checkpoint(rekor_keyring: RekorKeyring, entry: TransparencyLo...
FILE: sigstore/_internal/rekor/client.py
class RekorLogInfo (line 51) | class RekorLogInfo:
method from_response (line 63) | def from_response(cls, dict_: dict[str, Any]) -> RekorLogInfo:
class _Endpoint (line 76) | class _Endpoint(ABC):
method __init__ (line 77) | def __init__(self, url: str, session: requests.Session) -> None:
class RekorLog (line 84) | class RekorLog(_Endpoint):
method get (line 89) | def get(self) -> RekorLogInfo:
method entries (line 101) | def entries(self) -> RekorEntries:
class RekorEntries (line 109) | class RekorEntries(_Endpoint):
method get (line 114) | def get(
method post (line 138) | def post(
method retrieve (line 159) | def retrieve(self) -> RekorEntriesRetrieve:
class RekorEntriesRetrieve (line 166) | class RekorEntriesRetrieve(_Endpoint):
method post (line 171) | def post(
class RekorClient (line 217) | class RekorClient(RekorLogSubmitter):
method __init__ (line 220) | def __init__(self, url: str) -> None:
method production (line 228) | def production(cls) -> RekorClient:
method staging (line 237) | def staging(cls) -> RekorClient:
method _session (line 244) | def _session(self) -> requests.Session:
method log (line 261) | def log(self) -> RekorLog:
method create_entry (line 268) | def create_entry(self, request: EntryRequestBody) -> TransparencyLogEn...
method _build_hashed_rekord_request (line 276) | def _build_hashed_rekord_request( # type: ignore[override]
method _build_dsse_request (line 304) | def _build_dsse_request( # type: ignore[override]
FILE: sigstore/_internal/rekor/client_v2.py
class RekorV2Client (line 47) | class RekorV2Client(RekorLogSubmitter):
method __init__ (line 54) | def __init__(self, base_url: str) -> None:
method _session (line 62) | def _session(self) -> requests.Session:
method create_entry (line 78) | def create_entry(self, payload: EntryRequestBody) -> TransparencyLogEn...
method _build_hashed_rekord_request (line 106) | def _build_hashed_rekord_request(
method _build_dsse_request (line 136) | def _build_dsse_request(
FILE: sigstore/_internal/sct.py
function _pack_signed_entry (line 48) | def _pack_signed_entry(
function _pack_digitally_signed (line 91) | def _pack_digitally_signed(
function _is_preissuer (line 129) | def _is_preissuer(issuer: Certificate) -> bool:
function _get_issuer_cert (line 139) | def _get_issuer_cert(chain: list[Certificate]) -> Certificate:
function _get_signed_certificate_timestamp (line 146) | def _get_signed_certificate_timestamp(
function _cert_is_ca (line 170) | def _cert_is_ca(cert: Certificate) -> bool:
function verify_sct (line 180) | def verify_sct(
FILE: sigstore/_internal/timestamp.py
class TimestampSource (line 37) | class TimestampSource(enum.Enum):
class TimestampVerificationResult (line 45) | class TimestampVerificationResult:
class TimestampError (line 56) | class TimestampError(Exception):
class TimestampAuthorityClient (line 64) | class TimestampAuthorityClient:
method __init__ (line 67) | def __init__(self, url: str) -> None:
method _session (line 75) | def _session(self) -> requests.Session:
method request_timestamp (line 90) | def request_timestamp(self, signature: bytes) -> TimeStampResponse:
FILE: sigstore/_internal/trust.py
class Key (line 58) | class Key:
method __init__ (line 80) | def __init__(self, public_key: common_v1.PublicKey) -> None:
method verify (line 111) | def verify(self, signature: bytes, data: bytes) -> None:
class Keyring (line 145) | class Keyring:
method __init__ (line 150) | def __init__(self, public_keys: list[common_v1.PublicKey] = []):
method verify (line 163) | def verify(self, *, key_id: KeyID, signature: bytes, data: bytes) -> N...
class KeyringPurpose (line 197) | class KeyringPurpose(str, Enum):
method __str__ (line 205) | def __str__(self) -> str:
class CertificateAuthority (line 210) | class CertificateAuthority:
method __init__ (line 215) | def __init__(self, inner: trustroot_v1.CertificateAuthority):
method from_json (line 226) | def from_json(cls, path: str) -> CertificateAuthority:
method _verify (line 233) | def _verify(self) -> None:
method validity_period_start (line 246) | def validity_period_start(self) -> datetime:
method validity_period_end (line 253) | def validity_period_end(self) -> datetime | None:
method certificates (line 259) | def certificates(self, *, allow_expired: bool) -> list[Certificate]:
FILE: sigstore/_internal/tuf.py
function _get_dirs (line 40) | def _get_dirs(url: str) -> tuple[Path, Path]:
class TrustUpdater (line 58) | class TrustUpdater:
method __init__ (line 69) | def __init__(
method get_trusted_root_path (line 132) | def get_trusted_root_path(self) -> str:
method get_signing_config_path (line 155) | def get_signing_config_path(self) -> str:
FILE: sigstore/_utils.py
function load_pem_public_key (line 70) | def load_pem_public_key(
function load_der_public_key (line 96) | def load_der_public_key(
function base64_encode_pem_cert (line 120) | def base64_encode_pem_cert(cert: Certificate) -> B64Str:
function cert_der_to_pem (line 130) | def cert_der_to_pem(der: bytes) -> str:
function key_id (line 143) | def key_id(key: PublicKey) -> KeyID:
function sha256_digest (line 157) | def sha256_digest(
function _sha256_streaming (line 179) | def _sha256_streaming(io: IO[bytes]) -> bytes:
function read_embedded (line 212) | def read_embedded(name: str, url: str) -> bytes:
function cert_is_ca (line 222) | def cert_is_ca(cert: Certificate) -> bool:
function cert_is_root_ca (line 286) | def cert_is_root_ca(cert: Certificate) -> bool:
function cert_is_leaf (line 316) | def cert_is_leaf(cert: Certificate) -> bool:
function is_timerange_valid (line 360) | def is_timerange_valid(period: TimeRange | None, *, allow_expired: bool)...
FILE: sigstore/dsse/__init__.py
class Subject (line 53) | class Subject(BaseModel):
class _Statement (line 62) | class _Statement(BaseModel):
class Statement (line 75) | class Statement:
method __init__ (line 86) | def __init__(self, contents: bytes | _Statement) -> None:
method _matches_digest (line 104) | def _matches_digest(self, digest: Hashed) -> bool:
method _pae (line 123) | def _pae(self) -> bytes:
class StatementBuilder (line 131) | class StatementBuilder:
method __init__ (line 136) | def __init__(
method subjects (line 149) | def subjects(self, subjects: list[Subject]) -> StatementBuilder:
method predicate_type (line 156) | def predicate_type(self, predicate_type: str) -> StatementBuilder:
method predicate (line 163) | def predicate(self, predicate: dict[str, Any]) -> StatementBuilder:
method build (line 170) | def build(self) -> Statement:
class InvalidEnvelope (line 187) | class InvalidEnvelope(Error):
class Envelope (line 193) | class Envelope:
method __init__ (line 204) | def __init__(self, inner: _Envelope) -> None:
method _verify (line 212) | def _verify(self) -> None:
method _from_json (line 225) | def _from_json(cls, contents: bytes | str) -> Envelope:
method to_json (line 230) | def to_json(self) -> str:
method __eq__ (line 236) | def __eq__(self, other: object) -> bool:
method signature (line 245) | def signature(self) -> bytes:
function _pae (line 250) | def _pae(type_: str, body: bytes) -> bytes:
function _sign (line 263) | def _sign(key: ec.EllipticCurvePrivateKey, stmt: Statement) -> Envelope:
function _verify (line 281) | def _verify(key: ec.EllipticCurvePublicKey, evp: Envelope) -> bytes:
FILE: sigstore/dsse/_predicate.py
class PredicateType (line 36) | class PredicateType(str, enum.Enum):
class Predicate (line 57) | class Predicate(BaseModel):
class _SLSAConfigBase (line 65) | class _SLSAConfigBase(BaseModel):
class BuilderV0_1 (line 76) | class BuilderV0_1(_SLSAConfigBase):
class ConfigSource (line 84) | class ConfigSource(_SLSAConfigBase):
class Invocation (line 94) | class Invocation(_SLSAConfigBase):
class Completeness (line 104) | class Completeness(_SLSAConfigBase):
class Material (line 114) | class Material(_SLSAConfigBase):
class Metadata (line 123) | class Metadata(_SLSAConfigBase):
class SLSAPredicateV0_2 (line 135) | class SLSAPredicateV0_2(Predicate, _SLSAConfigBase):
class ResourceDescriptor (line 151) | class ResourceDescriptor(_SLSAConfigBase):
method check_required_fields (line 165) | def check_required_fields(self: Self) -> Self:
class BuilderV1_0 (line 177) | class BuilderV1_0(_SLSAConfigBase):
class BuildMetadata (line 187) | class BuildMetadata(_SLSAConfigBase):
class RunDetails (line 197) | class RunDetails(_SLSAConfigBase):
class BuildDefinition (line 207) | class BuildDefinition(_SLSAConfigBase):
class SLSAPredicateV1_0 (line 218) | class SLSAPredicateV1_0(Predicate, _SLSAConfigBase):
FILE: sigstore/errors.py
class Error (line 25) | class Error(Exception):
method diagnostics (line 28) | def diagnostics(self) -> str:
method log_and_exit (line 33) | def log_and_exit(self, logger: Logger, raise_error: bool = False) -> N...
class NetworkError (line 52) | class NetworkError(Error):
method diagnostics (line 55) | def diagnostics(self) -> str:
class TUFError (line 78) | class TUFError(Error):
method __init__ (line 81) | def __init__(self, message: str):
method diagnostics (line 91) | def diagnostics(self) -> str:
class MetadataError (line 106) | class MetadataError(Error):
method diagnostics (line 109) | def diagnostics(self) -> str:
class RootError (line 114) | class RootError(Error):
method diagnostics (line 117) | def diagnostics(self) -> str:
class VerificationError (line 125) | class VerificationError(Error):
class CertValidationError (line 131) | class CertValidationError(VerificationError):
FILE: sigstore/hashes.py
class Hashed (line 28) | class Hashed(BaseModel, frozen=True):
method _as_hashedrekord_algorithm (line 43) | def _as_hashedrekord_algorithm(self) -> rekor_types.hashedrekord.Algor...
method _as_prehashed (line 51) | def _as_prehashed(self) -> Prehashed:
method __str__ (line 59) | def __str__(self) -> str:
FILE: sigstore/models.py
class TransparencyLogEntry (line 78) | class TransparencyLogEntry:
method __init__ (line 83) | def __init__(self, inner: _TransparencyLogEntry) -> None:
method _validate (line 92) | def _validate(self) -> None:
method __eq__ (line 104) | def __eq__(self, value: object) -> bool:
method _from_v1_response (line 116) | def _from_v1_response(cls, dict_: dict[str, Any]) -> TransparencyLogEn...
method _encode_canonical (line 170) | def _encode_canonical(self) -> bytes:
method _verify_set (line 194) | def _verify_set(self, keyring: RekorKeyring) -> None:
method _verify (line 216) | def _verify(self, keyring: RekorKeyring) -> None:
class TimestampVerificationData (line 242) | class TimestampVerificationData:
method __init__ (line 249) | def __init__(self, inner: _TimestampVerificationData) -> None:
method _verify (line 254) | def _verify(self) -> None:
method rfc3161_timestamps (line 272) | def rfc3161_timestamps(self) -> list[TimeStampResponse]:
method from_json (line 277) | def from_json(cls, raw: str | bytes) -> TimestampVerificationData:
class VerificationMaterial (line 285) | class VerificationMaterial:
method __init__ (line 290) | def __init__(self, inner: _VerificationMaterial) -> None:
method timestamp_verification_data (line 295) | def timestamp_verification_data(self) -> TimestampVerificationData | N...
class InvalidBundle (line 307) | class InvalidBundle(Error):
method diagnostics (line 312) | def diagnostics(self) -> str:
class IncompatibleEntry (line 328) | class IncompatibleEntry(InvalidBundle):
method diagnostics (line 333) | def diagnostics(self) -> str:
class Bundle (line 347) | class Bundle:
class BundleType (line 352) | class BundleType(str, Enum):
method __str__ (line 362) | def __str__(self) -> str:
method __init__ (line 366) | def __init__(self, inner: _Bundle) -> None:
method _verify (line 376) | def _verify(self) -> None:
method signing_certificate (line 497) | def signing_certificate(self) -> Certificate:
method log_entry (line 502) | def log_entry(self) -> TransparencyLogEntry:
method _dsse_envelope (line 510) | def _dsse_envelope(self) -> dsse.Envelope | None:
method signature (line 521) | def signature(self) -> bytes:
method verification_material (line 533) | def verification_material(self) -> VerificationMaterial:
method from_json (line 540) | def from_json(cls, raw: bytes | str) -> Bundle:
method to_json (line 550) | def to_json(self) -> str:
method _to_parts (line 556) | def _to_parts(
method from_parts (line 574) | def from_parts(
method _from_parts (line 587) | def _from_parts(
class SigningConfig (line 635) | class SigningConfig:
class SigningConfigType (line 640) | class SigningConfigType(str, Enum):
method __str__ (line 647) | def __str__(self) -> str:
method __init__ (line 651) | def __init__(
method from_file (line 698) | def from_file(
method _get_valid_services (line 707) | def _get_valid_services(
method get_tlogs (line 753) | def get_tlogs(self) -> list[RekorLogSubmitter]:
method get_fulcio (line 771) | def get_fulcio(self) -> FulcioClient:
method get_oidc_url (line 777) | def get_oidc_url(self) -> str:
method get_tsas (line 786) | def get_tsas(self) -> list[TimestampAuthorityClient]:
class TrustedRoot (line 793) | class TrustedRoot:
class TrustedRootType (line 798) | class TrustedRootType(str, Enum):
method __str__ (line 805) | def __str__(self) -> str:
method __init__ (line 809) | def __init__(self, inner: trustroot_v1.TrustedRoot):
method _verify (line 818) | def _verify(self) -> None:
method from_file (line 831) | def from_file(
method _get_tlog_keys (line 839) | def _get_tlog_keys(
method rekor_keyring (line 855) | def rekor_keyring(self, purpose: KeyringPurpose) -> RekorKeyring:
method ct_keyring (line 865) | def ct_keyring(self, purpose: KeyringPurpose) -> CTKeyring:
method get_fulcio_certs (line 874) | def get_fulcio_certs(self) -> list[Certificate]:
method get_timestamp_authorities (line 889) | def get_timestamp_authorities(self) -> list[CertificateAuthority]:
class ClientTrustConfig (line 903) | class ClientTrustConfig:
class ClientTrustConfigType (line 908) | class ClientTrustConfigType(str, Enum):
method __str__ (line 915) | def __str__(self) -> str:
method from_json (line 920) | def from_json(cls, raw: str) -> ClientTrustConfig:
method production (line 928) | def production(
method staging (line 940) | def staging(
method from_tuf (line 952) | def from_tuf(
method __init__ (line 982) | def __init__(self, inner: trustroot_v1.ClientTrustConfig) -> None:
method trusted_root (line 992) | def trusted_root(self) -> TrustedRoot:
method signing_config (line 999) | def signing_config(self) -> SigningConfig:
FILE: sigstore/oidc.py
class _OpenIDConfiguration (line 48) | class _OpenIDConfiguration(BaseModel):
class ExpiredIdentity (line 60) | class ExpiredIdentity(Exception):
class IdentityToken (line 64) | class IdentityToken:
method __init__ (line 70) | def __init__(self, raw_token: str, client_id: str = _DEFAULT_CLIENT_ID...
method in_validity_period (line 160) | def in_validity_period(self) -> bool:
method identity (line 177) | def identity(self) -> str:
method issuer (line 189) | def issuer(self) -> str:
method federated_issuer (line 196) | def federated_issuer(self) -> str:
method __str__ (line 217) | def __str__(self) -> str:
class IssuerError (line 226) | class IssuerError(Exception):
class Issuer (line 234) | class Issuer:
method __init__ (line 239) | def __init__(self, base_url: str) -> None:
method identity_token (line 272) | def identity_token( # nosec: B107
class IdentityError (line 361) | class IdentityError(Error):
method raise_from_id (line 367) | def raise_from_id(cls, exc: id.IdentityError) -> NoReturn:
method diagnostics (line 371) | def diagnostics(self) -> str:
function detect_credential (line 410) | def detect_credential(client_id: str = _DEFAULT_CLIENT_ID) -> str | None:
FILE: sigstore/sign.py
class Signer (line 77) | class Signer:
method __init__ (line 82) | def __init__(
method _private_key (line 112) | def _private_key(self) -> ec.EllipticCurvePrivateKey:
method _signing_cert (line 119) | def _signing_cert(
method _finalize_sign (line 177) | def _finalize_sign(
method sign_dsse (line 204) | def sign_dsse(
method sign_artifact (line 227) | def sign_artifact(
class SigningContext (line 270) | class SigningContext:
method __init__ (line 275) | def __init__(
method from_trust_config (line 298) | def from_trust_config(cls, trust_config: ClientTrustConfig) -> Signing...
method signer (line 313) | def signer(
FILE: sigstore/verify/policy.py
class _SingleX509ExtPolicy (line 69) | class _SingleX509ExtPolicy(ABC):
method __init__ (line 80) | def __init__(self, value: str) -> None:
method verify (line 87) | def verify(self, cert: Certificate) -> None:
class _SingleX509ExtPolicyV2 (line 111) | class _SingleX509ExtPolicyV2(_SingleX509ExtPolicy):
method verify (line 118) | def verify(self, cert: Certificate) -> None:
class OIDCIssuer (line 142) | class OIDCIssuer(_SingleX509ExtPolicy):
class GitHubWorkflowTrigger (line 151) | class GitHubWorkflowTrigger(_SingleX509ExtPolicy):
class GitHubWorkflowSHA (line 160) | class GitHubWorkflowSHA(_SingleX509ExtPolicy):
class GitHubWorkflowName (line 169) | class GitHubWorkflowName(_SingleX509ExtPolicy):
class GitHubWorkflowRepository (line 178) | class GitHubWorkflowRepository(_SingleX509ExtPolicy):
class GitHubWorkflowRef (line 187) | class GitHubWorkflowRef(_SingleX509ExtPolicy):
class OIDCIssuerV2 (line 196) | class OIDCIssuerV2(_SingleX509ExtPolicyV2):
class OIDCBuildSignerURI (line 208) | class OIDCBuildSignerURI(_SingleX509ExtPolicyV2):
class OIDCBuildSignerDigest (line 217) | class OIDCBuildSignerDigest(_SingleX509ExtPolicyV2):
class OIDCRunnerEnvironment (line 226) | class OIDCRunnerEnvironment(_SingleX509ExtPolicyV2):
class OIDCSourceRepositoryURI (line 235) | class OIDCSourceRepositoryURI(_SingleX509ExtPolicyV2):
class OIDCSourceRepositoryDigest (line 244) | class OIDCSourceRepositoryDigest(_SingleX509ExtPolicyV2):
class OIDCSourceRepositoryRef (line 253) | class OIDCSourceRepositoryRef(_SingleX509ExtPolicyV2):
class OIDCSourceRepositoryIdentifier (line 262) | class OIDCSourceRepositoryIdentifier(_SingleX509ExtPolicyV2):
class OIDCSourceRepositoryOwnerURI (line 271) | class OIDCSourceRepositoryOwnerURI(_SingleX509ExtPolicyV2):
class OIDCSourceRepositoryOwnerIdentifier (line 280) | class OIDCSourceRepositoryOwnerIdentifier(_SingleX509ExtPolicyV2):
class OIDCBuildConfigURI (line 289) | class OIDCBuildConfigURI(_SingleX509ExtPolicyV2):
class OIDCBuildConfigDigest (line 298) | class OIDCBuildConfigDigest(_SingleX509ExtPolicyV2):
class OIDCBuildTrigger (line 307) | class OIDCBuildTrigger(_SingleX509ExtPolicyV2):
class OIDCRunInvocationURI (line 316) | class OIDCRunInvocationURI(_SingleX509ExtPolicyV2):
class OIDCSourceRepositoryVisibility (line 325) | class OIDCSourceRepositoryVisibility(_SingleX509ExtPolicyV2):
class VerificationPolicy (line 335) | class VerificationPolicy(Protocol):
method verify (line 342) | def verify(self, cert: Certificate) -> None:
class AnyOf (line 350) | class AnyOf:
method __init__ (line 357) | def __init__(self, children: list[VerificationPolicy]):
method verify (line 363) | def verify(self, cert: Certificate) -> None:
class AllOf (line 381) | class AllOf:
method __init__ (line 389) | def __init__(self, children: list[VerificationPolicy]):
method verify (line 396) | def verify(self, cert: Certificate) -> None:
class UnsafeNoOp (line 411) | class UnsafeNoOp:
method verify (line 423) | def verify(self, cert: Certificate) -> None:
class Identity (line 433) | class Identity:
method __init__ (line 445) | def __init__(self, *, identity: str, issuer: str | None = None):
method verify (line 456) | def verify(self, cert: Certificate) -> None:
FILE: sigstore/verify/verifier.py
class Verifier (line 70) | class Verifier:
method __init__ (line 75) | def __init__(self, *, trusted_root: TrustedRoot):
method production (line 94) | def production(cls, *, offline: bool = False) -> Verifier:
method staging (line 108) | def staging(cls, *, offline: bool = False) -> Verifier:
method _verify_signed_timestamp (line 121) | def _verify_signed_timestamp(
method _verify_timestamp_authority (line 168) | def _verify_timestamp_authority(
method _establish_time (line 200) | def _establish_time(self, bundle: Bundle) -> list[TimestampVerificatio...
method _verify_chain_at_time (line 245) | def _verify_chain_at_time(
method _verify_common_signing_cert (line 279) | def _verify_common_signing_cert(
method verify_dsse (line 388) | def verify_dsse(
method verify_artifact (line 450) | def verify_artifact(
function _validate_dsse_v001_entry_body (line 517) | def _validate_dsse_v001_entry_body(bundle: Bundle) -> None:
function _validate_dsse_v002_entry_body (line 557) | def _validate_dsse_v002_entry_body(bundle: Bundle) -> None:
function _validate_hashedrekord_v001_entry_body (line 593) | def _validate_hashedrekord_v001_entry_body(
function _validate_hashedrekord_v002_entry_body (line 614) | def _validate_hashedrekord_v002_entry_body(
function _v2_verifier_from_certificate (line 648) | def _v2_verifier_from_certificate(certificate: Certificate) -> v2.verifi...
FILE: test/assets/x509/build-testcases.py
function _keypair (line 41) | def _keypair(priv_key_file: Path):
function _builder (line 57) | def _builder() -> x509.CertificateBuilder:
function _finalize (line 82) | def _finalize(
function _dump (line 89) | def _dump(cert: x509.Certificate, filename: Path):
function bogus_root (line 98) | def bogus_root() -> x509.Certificate:
function bogus_root_noncritical_bc (line 125) | def bogus_root_noncritical_bc() -> x509.Certificate:
function bogus_root_missing_ku (line 153) | def bogus_root_missing_ku() -> x509.Certificate:
function bogus_root_invalid_ku (line 167) | def bogus_root_invalid_ku() -> x509.Certificate:
function bogus_intermediate (line 195) | def bogus_intermediate() -> x509.Certificate:
function bogus_intermediate_with_eku (line 223) | def bogus_intermediate_with_eku() -> x509.Certificate:
function bogus_leaf (line 259) | def bogus_leaf() -> x509.Certificate:
function bogus_leaf_invalid_ku (line 291) | def bogus_leaf_invalid_ku() -> x509.Certificate:
function bogus_leaf_invalid_eku (line 324) | def bogus_leaf_invalid_eku() -> x509.Certificate:
function bogus_leaf_missing_eku (line 357) | def bogus_leaf_missing_eku() -> x509.Certificate:
FILE: test/conftest.py
function asset (line 32) | def asset():
function _has_oidc_id (line 39) | def _has_oidc_id():
function _has_timestamp_authority_configured (line 72) | def _has_timestamp_authority_configured() -> bool:
function pytest_addoption (line 79) | def pytest_addoption(parser):
function pytest_runtest_setup (line 92) | def pytest_runtest_setup(item):
function pytest_configure (line 118) | def pytest_configure(config):
FILE: test/integration/cli/conftest.py
function asset_integration (line 23) | def asset_integration(asset):
function sigstore (line 31) | def sigstore() -> Callable:
FILE: test/integration/cli/test_attest.py
function get_cli_params (line 24) | def get_cli_params(
function test_attest_success_default_output_bundle (line 57) | def test_attest_success_default_output_bundle(
function test_attest_success_custom_output_bundle (line 89) | def test_attest_success_custom_output_bundle(
function test_attest_overwrite_existing_bundle (line 115) | def test_attest_overwrite_existing_bundle(
function test_attest_invalid_predicate_type (line 154) | def test_attest_invalid_predicate_type(capsys, sigstore, asset_integrati...
function test_attest_mismatching_predicate (line 178) | def test_attest_mismatching_predicate(capsys, sigstore, asset_integratio...
function test_attest_missing_predicate (line 202) | def test_attest_missing_predicate(capsys, sigstore, asset_integration, t...
function test_attest_invalid_json_predicate (line 226) | def test_attest_invalid_json_predicate(capsys, sigstore, asset_integrati...
FILE: test/integration/cli/test_plumbing.py
function test_fix_bundle_fixes_missing_checkpoint (line 25) | def test_fix_bundle_fixes_missing_checkpoint(capsys, sigstore, asset_int...
function test_fix_bundle_upgrades_bundle (line 67) | def test_fix_bundle_upgrades_bundle(capsys, sigstore, asset_integration):
FILE: test/integration/cli/test_sign.py
function get_cli_params (line 23) | def get_cli_params(
function test_sign_success_default_output_bundle (line 58) | def test_sign_success_default_output_bundle(
function test_sign_success_multiple_artifacts (line 90) | def test_sign_success_multiple_artifacts(capsys, sigstore, asset_integra...
function test_sign_success_multiple_artifacts_rekor_v2 (line 125) | def test_sign_success_multiple_artifacts_rekor_v2(
function test_sign_success_custom_outputs (line 167) | def test_sign_success_custom_outputs(capsys, sigstore, asset_integration...
function test_sign_success_custom_output_dir (line 194) | def test_sign_success_custom_output_dir(capsys, sigstore, asset_integrat...
function test_sign_success_no_default_files (line 215) | def test_sign_success_no_default_files(capsys, sigstore, asset_integrati...
function test_sign_overwrite_existing_bundle (line 241) | def test_sign_overwrite_existing_bundle(capsys, sigstore, asset_integrat...
function test_sign_fails_with_default_files_and_bundle_options (line 279) | def test_sign_fails_with_default_files_and_bundle_options(
function test_sign_fails_with_multiple_inputs_and_custom_output (line 301) | def test_sign_fails_with_multiple_inputs_and_custom_output(
function test_sign_fails_with_output_dir_and_custom_output_files (line 346) | def test_sign_fails_with_output_dir_and_custom_output_files(
function test_sign_fails_without_both_output_cert_and_signature (line 394) | def test_sign_fails_without_both_output_cert_and_signature(
FILE: test/integration/cli/test_verify.py
function test_regression_verify_legacy_bundle (line 18) | def test_regression_verify_legacy_bundle(capsys, caplog, asset_integrati...
FILE: test/unit/conftest.py
function x509_testcase (line 52) | def x509_testcase(asset):
function tuf_asset (line 61) | def tuf_asset():
function signing_materials (line 93) | def signing_materials(asset) -> Callable[[str, RekorClient], tuple[Path,...
function signing_bundle (line 118) | def signing_bundle(asset) -> Callable[[str], tuple[Path, Bundle]]:
function null_policy (line 132) | def null_policy():
function mock_staging_tuf (line 141) | def mock_staging_tuf(monkeypatch, tuf_dirs):
function tuf_dirs (line 177) | def tuf_dirs(monkeypatch, tmp_path):
function sign_ctx_and_ident_for_env (line 188) | def sign_ctx_and_ident_for_env(
function staging (line 218) | def staging() -> tuple[type[SigningContext], type[Verifier], IdentityTok...
function dummy_jwt (line 239) | def dummy_jwt():
function tsa_url (line 247) | def tsa_url():
FILE: test/unit/internal/oidc/test_issuer.py
function test_fail_init_url (line 23) | def test_fail_init_url():
function test_init_url (line 29) | def test_init_url():
function test_get_identity_token_bad_code (line 34) | def test_get_identity_token_bad_code(monkeypatch):
function test_identity_token_csrf_protection (line 41) | def test_identity_token_csrf_protection():
FILE: test/unit/internal/rekor/test_client_v2.py
function test_rekor_v2_create_entry_dsse (line 25) | def test_rekor_v2_create_entry_dsse(staging):
function test_rekor_v2_create_entry_hashed_rekord (line 57) | def test_rekor_v2_create_entry_hashed_rekord(staging):
FILE: test/unit/internal/test_key_details.py
function test_get_key_details (line 80) | def test_get_key_details(mock_certificate):
function delayed_crypto_mock (line 88) | def delayed_crypto_mock(mock_func, error_msg):
class DummyCurve (line 102) | class DummyCurve(ec.EllipticCurve):
method key_size (line 106) | def key_size(self):
method group_order (line 110) | def group_order(self):
function test_get_key_details_unsupported (line 192) | def test_get_key_details_unsupported(mock_certificate, error_msg):
FILE: test/unit/internal/test_sct.py
function test_pack_digitally_signed_precertificate (line 34) | def test_pack_digitally_signed_precertificate(precert_bytes_len):
FILE: test/unit/internal/test_timestamping.py
class TestTimestampAuthorityClient (line 22) | class TestTimestampAuthorityClient:
method test_sign_request (line 23) | def test_sign_request(self, tsa_url: str):
method test_sign_request_invalid_url (line 35) | def test_sign_request_invalid_url(self):
method test_sign_request_invalid_request (line 40) | def test_sign_request_invalid_request(self, tsa_url):
method test_invalid_response (line 45) | def test_invalid_response(self, tsa_url, monkeypatch):
FILE: test/unit/internal/test_trust.py
class TestCertificateAuthority (line 57) | class TestCertificateAuthority:
method test_good (line 58) | def test_good(self, asset):
method test_missing_root (line 66) | def test_missing_root(self, asset):
class TestSigningConfig (line 72) | class TestSigningConfig:
method test_good (line 73) | def test_good(self, asset):
method test_good_only_v1_rekor (line 98) | def test_good_only_v1_rekor(self, asset):
method test_get_valid_services (line 183) | def test_get_valid_services(self, services, versions, config, expected...
method test_get_valid_services_fail (line 198) | def test_get_valid_services_fail(self, services, versions, config):
class TestTrustedRoot (line 203) | class TestTrustedRoot:
method test_good (line 211) | def test_good(self, asset, file):
method test_bad_media_type (line 232) | def test_bad_media_type(self, asset):
function test_trust_root_tuf_offline (line 245) | def test_trust_root_tuf_offline(mock_staging_tuf, tuf_dirs):
function test_is_timerange_valid (line 267) | def test_is_timerange_valid():
function test_trust_root_tuf_instance_error (line 296) | def test_trust_root_tuf_instance_error():
function test_trust_root_tuf_ctfe_keys_error (line 302) | def test_trust_root_tuf_ctfe_keys_error(monkeypatch):
function test_trust_root_fulcio_certs_error (line 309) | def test_trust_root_fulcio_certs_error(tuf_asset, monkeypatch):
class TestClientTrustConfig (line 318) | class TestClientTrustConfig:
method test_good (line 319) | def test_good(self, asset):
method test_bad_media_type (line 326) | def test_bad_media_type(self, asset):
FILE: test/unit/test_dsse.py
class TestEnvelope (line 24) | class TestEnvelope:
method test_roundtrip (line 25) | def test_roundtrip(self):
method test_missing_signature (line 47) | def test_missing_signature(self):
method test_empty_signature (line 59) | def test_empty_signature(self):
method test_multiple_signatures (line 73) | def test_multiple_signatures(self):
FILE: test/unit/test_hashes.py
class TestHashes (line 22) | class TestHashes:
method test_hashed_repr (line 33) | def test_hashed_repr(self, algorithm, digest):
FILE: test/unit/test_models.py
class TestTransparencyLogEntry (line 32) | class TestTransparencyLogEntry:
method test_missing_inclusion_proof (line 34) | def test_missing_inclusion_proof(self, integrated_time: int):
method test_logentry_roundtrip (line 65) | def test_logentry_roundtrip(self, signing_bundle):
class TestTimestampVerificationData (line 76) | class TestTimestampVerificationData:
method test_valid_timestamp (line 81) | def test_valid_timestamp(self, asset):
method test_no_timestamp (line 96) | def test_no_timestamp(self, asset):
method test_invalid_timestamp (line 104) | def test_invalid_timestamp(self, asset):
class TestVerificationMaterial (line 110) | class TestVerificationMaterial:
method test_valid_verification_material (line 115) | def test_valid_verification_material(self, asset):
class TestBundle (line 124) | class TestBundle:
method test_invalid_bundle_version (line 129) | def test_invalid_bundle_version(self, signing_bundle):
method test_invalid_empty_cert_chain (line 133) | def test_invalid_empty_cert_chain(self, signing_bundle):
method test_invalid_no_log_entry (line 139) | def test_invalid_no_log_entry(self, signing_bundle):
method test_verification_materials_offline_no_checkpoint (line 145) | def test_verification_materials_offline_no_checkpoint(self, signing_bu...
method test_bundle_roundtrip (line 151) | def test_bundle_roundtrip(self, signing_bundle):
method test_bundle_missing_signed_time (line 160) | def test_bundle_missing_signed_time(self, signing_bundle):
class TestKnownBundleTypes (line 168) | class TestKnownBundleTypes:
method test_str (line 169) | def test_str(self):
FILE: test/unit/test_oidc.py
class TestIdentityToken (line 22) | class TestIdentityToken:
method test_invalid_jwt (line 23) | def test_invalid_jwt(self):
method test_missing_iss (line 29) | def test_missing_iss(self, dummy_jwt):
method test_missing_aud (line 46) | def test_missing_aud(self, dummy_jwt):
method test_invalid_aud (line 64) | def test_invalid_aud(self, dummy_jwt, aud):
method test_missing_iat (line 82) | def test_missing_iat(self, dummy_jwt):
method test_invalid_iat (line 100) | def test_invalid_iat(self, dummy_jwt, iat):
method test_missing_nbf_ok (line 118) | def test_missing_nbf_ok(self, dummy_jwt):
method test_invalid_nbf (line 132) | def test_invalid_nbf(self, dummy_jwt):
method test_missing_exp (line 151) | def test_missing_exp(self, dummy_jwt):
method test_invalid_exp (line 168) | def test_invalid_exp(self, dummy_jwt):
method test_missing_identity_claim (line 190) | def test_missing_identity_claim(self, dummy_jwt, iss):
method test_invalid_federated_claims (line 210) | def test_invalid_federated_claims(self, dummy_jwt, fed):
method test_ok (line 251) | def test_ok(self, dummy_jwt, iss, identity_claim, identity_value, fed_...
FILE: test/unit/test_session_reuse.py
function test_rekor_v1_session_reuse_public_api (line 23) | def test_rekor_v1_session_reuse_public_api():
function test_rekor_v2_session_reuse_public_api (line 39) | def test_rekor_v2_session_reuse_public_api():
function test_timestamp_client_session_reuse_public_api (line 62) | def test_timestamp_client_session_reuse_public_api():
FILE: test/unit/test_sign.py
function test_sign_rekor_entry_consistent (line 34) | def test_sign_rekor_entry_consistent(request, sign_ctx_and_ident_for_env):
function test_sign_with_staging (line 59) | def test_sign_with_staging(staging, null_policy):
function test_sct_verify_keyring_lookup_error (line 75) | def test_sct_verify_keyring_lookup_error(sign_ctx_and_ident_for_env, mon...
function test_sct_verify_keyring_error (line 94) | def test_sct_verify_keyring_error(sign_ctx_and_ident_for_env, monkeypatch):
function test_identity_proof_fallback_claim (line 114) | def test_identity_proof_fallback_claim(sign_ctx_and_ident_for_env, monke...
function test_sign_prehashed (line 132) | def test_sign_prehashed(staging):
function test_sign_dsse (line 157) | def test_sign_dsse(staging):
FILE: test/unit/test_store.py
function test_store_reads_root_json (line 29) | def test_store_reads_root_json(env):
function test_store_reads_targets_json (line 41) | def test_store_reads_targets_json(env):
FILE: test/unit/test_utils.py
function test_key_id (line 28) | def test_key_id():
function test_sha256_streaming (line 68) | def test_sha256_streaming(size):
function test_load_pem_public_key_format (line 77) | def test_load_pem_public_key_format():
function test_load_pem_public_key_serialization (line 85) | def test_load_pem_public_key_serialization(monkeypatch):
function test_cert_is_ca (line 109) | def test_cert_is_ca(x509_testcase, testcase, valid):
function test_cert_is_ca_invalid_states (line 123) | def test_cert_is_ca_invalid_states(x509_testcase, testcase):
function test_cert_is_root_ca (line 139) | def test_cert_is_root_ca(x509_testcase, testcase, valid):
function test_cert_is_leaf (line 155) | def test_cert_is_leaf(x509_testcase, testcase, valid):
function test_cert_is_leaf_invalid_states (line 170) | def test_cert_is_leaf_invalid_states(x509_testcase, testcase):
function test_cert_is_leaf_invalid_version (line 180) | def test_cert_is_leaf_invalid_version(helper):
FILE: test/unit/test_version.py
function test_version (line 18) | def test_version():
FILE: test/unit/verify/test_policy.py
class TestVerificationPolicy (line 25) | class TestVerificationPolicy:
method test_does_not_init (line 26) | def test_does_not_init(self):
class TestUnsafeNoOp (line 31) | class TestUnsafeNoOp:
method test_succeeds (line 32) | def test_succeeds(self, monkeypatch):
class TestAnyOf (line 45) | class TestAnyOf:
method test_trivially_false (line 46) | def test_trivially_false(self):
method test_fails_no_children_match (line 52) | def test_fails_no_children_match(self, signing_bundle):
method test_succeeds (line 64) | def test_succeeds(self, signing_bundle):
class TestAllOf (line 80) | class TestAllOf:
method test_trivially_false (line 81) | def test_trivially_false(self):
method test_certificate_extension_not_found (line 87) | def test_certificate_extension_not_found(self):
method test_fails_not_all_children_match (line 103) | def test_fails_not_all_children_match(self, signing_bundle):
method test_succeeds (line 122) | def test_succeeds(self, signing_bundle):
class TestIdentity (line 140) | class TestIdentity:
method test_fails_no_san_match (line 141) | def test_fails_no_san_match(self, signing_bundle):
class TestSingleExtPolicy (line 155) | class TestSingleExtPolicy:
method test_succeeds (line 156) | def test_succeeds(self, signing_bundle):
FILE: test/unit/verify/test_verifier.py
function test_verifier_production (line 34) | def test_verifier_production():
function test_verifier_staging (line 40) | def test_verifier_staging():
function test_verifier_one_verification (line 46) | def test_verifier_one_verification(signing_materials, null_policy):
function test_verifier_inconsistent_log_entry (line 55) | def test_verifier_inconsistent_log_entry(signing_bundle, null_policy):
function test_verifier_digest_mismatch (line 68) | def test_verifier_digest_mismatch(signing_bundle, null_policy):
function test_verifier_multiple_verifications (line 82) | def test_verifier_multiple_verifications(signing_materials, null_policy):
function test_verifier_bundle_artifact (line 97) | def test_verifier_bundle_artifact(signing_bundle, null_policy, filename):
function test_verifier_bundle_dsse (line 109) | def test_verifier_bundle_dsse(signing_bundle, null_policy, filename):
function test_verifier_bundle_offline (line 119) | def test_verifier_bundle_offline(signing_bundle, null_policy, filename):
function test_verifier_email_identity (line 127) | def test_verifier_email_identity(signing_materials):
function test_verifier_uri_identity (line 144) | def test_verifier_uri_identity(signing_materials):
function test_verifier_policy_check (line 163) | def test_verifier_policy_check(signing_materials):
function test_verifier_fail_expiry (line 180) | def test_verifier_fail_expiry(signing_materials, null_policy, monkeypatch):
function test_verifier_dsse_roundtrip (line 201) | def test_verifier_dsse_roundtrip(staging):
class TestVerifierWithTimestamp (line 228) | class TestVerifierWithTimestamp:
method verifier (line 230) | def verifier(self, asset) -> Verifier:
method test_verifier_verify_timestamp (line 237) | def test_verifier_verify_timestamp(self, verifier, asset, null_policy,...
method test_verifier_no_validity_end (line 248) | def test_verifier_no_validity_end(self, verifier, asset, null_policy):
method test_verifier_verify_no_inclusion_promise_and_integrated_time (line 268) | def test_verifier_verify_no_inclusion_promise_and_integrated_time(
method test_verifier_without_timestamp (line 289) | def test_verifier_without_timestamp(
method test_verifier_too_many_timestamp (line 300) | def test_verifier_too_many_timestamp(self, verifier, asset, null_policy):
method test_verifier_duplicate_timestamp (line 310) | def test_verifier_duplicate_timestamp(self, verifier, asset, null_poli...
method test_verifier_outside_validity_range (line 318) | def test_verifier_outside_validity_range(
method test_verifier_rfc3161_error (line 345) | def test_verifier_rfc3161_error(
method test_verifier_no_authorities (line 369) | def test_verifier_no_authorities(self, asset, null_policy):
method test_late_timestamp (line 380) | def test_late_timestamp(self, caplog, verifier, asset, null_policy, mo...
method test_verifier_not_enough_timestamp (line 403) | def test_verifier_not_enough_timestamp(
method test_verify_signed_timestamp_regression (line 418) | def test_verify_signed_timestamp_regression(self, asset):
Condensed preview — 207 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (937K chars).
[
{
"path": ".gitattributes",
"chars": 224,
"preview": "# These directories contain TUF and other assets that are either digested\n# or sized-checked so CRLF normalization break"
},
{
"path": ".github/actions/upload-coverage/action.yml",
"chars": 1117,
"preview": "# Derived from <https://github.com/pyca/cryptography/blob/SOME_REF/.github/actions/upload-coverage/action.yml>\n# Origina"
},
{
"path": ".github/dependabot.yml",
"chars": 568,
"preview": "version: 2\n\nupdates:\n - package-ecosystem: pip\n directory: /\n schedule:\n interval: daily\n\n - package-ecosys"
},
{
"path": ".github/pull_request_template.md",
"chars": 1755,
"preview": "<!--\nThanks for opening a pull request! Please do not just delete this text. The three fields below are mandatory.\n\nPle"
},
{
"path": ".github/workflows/check-embedded-root.yml",
"chars": 2172,
"preview": "name: Check embedded root\n\non:\n workflow_dispatch:\n schedule:\n - cron: '13 13 * * 3'\n\njobs:\n check-embedded-root:\n"
},
{
"path": ".github/workflows/ci.yml",
"chars": 5286,
"preview": "name: CI\n\non:\n push:\n branches:\n - main\n - series/*\n pull_request:\n schedule:\n - cron: \"0 11 * * *\"\n "
},
{
"path": ".github/workflows/conformance.yml",
"chars": 1878,
"preview": "name: Conformance Tests\n\non:\n push:\n branches:\n - main\n workflow_dispatch:\n pull_request:\n schedule:\n - c"
},
{
"path": ".github/workflows/cross-os.yml",
"chars": 3178,
"preview": "# Copyright 2025 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": ".github/workflows/cross-version-verify.yaml",
"chars": 4244,
"preview": "# Copyright 2025 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": ".github/workflows/depsreview.yml",
"chars": 851,
"preview": "#\n# Copyright 2022 The Sigstore Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
},
{
"path": ".github/workflows/docs.yml",
"chars": 1465,
"preview": "name: Documentation\n\non:\n push:\n branches:\n - main\n\npermissions: {}\n\njobs:\n build:\n runs-on: ubuntu-latest\n"
},
{
"path": ".github/workflows/lint.yml",
"chars": 2673,
"preview": "name: Lint\n\non:\n push:\n branches:\n - main\n pull_request:\n workflow_dispatch:\n\npermissions: {}\n\njobs:\n lint:\n"
},
{
"path": ".github/workflows/pin-requirements.yml",
"chars": 5158,
"preview": "name: Pin Requirements\n\non:\n workflow_dispatch:\n inputs:\n tag:\n description: Tag to pin dependencies aga"
},
{
"path": ".github/workflows/release.yml",
"chars": 5531,
"preview": "name: Release\n\non:\n release:\n types:\n - published\n\npermissions: {}\n\njobs:\n build:\n name: Build and sign art"
},
{
"path": ".github/workflows/requirements.yml",
"chars": 1277,
"preview": "name: Test requirements.txt\n\non:\n push:\n branches:\n - main\n workflow_call:\n inputs:\n ref:\n desc"
},
{
"path": ".github/workflows/scorecards-analysis.yml",
"chars": 2019,
"preview": "name: Scorecards supply-chain security\non:\n # Only the default branch is supported.\n workflow_dispatch: # Manual\n bra"
},
{
"path": ".github/workflows/staging-tests.yml",
"chars": 2848,
"preview": "name: Staging Instance Tests\n\non:\n push:\n branches:\n - main\n schedule:\n - cron: \"0 */8 * * *\"\n\npermissions:"
},
{
"path": ".gitignore",
"chars": 406,
"preview": ".cache/\nenv/\npip-wheel-metadata/\n*.egg-info/\n__pycache__/\n.coverage*\nhtml/\ndist/\n.python-version\nbuild\n\n# Ignore some fi"
},
{
"path": "CHANGELOG.md",
"chars": 33987,
"preview": "# Changelog\n\nAll notable changes to `sigstore-python` will be documented in this file.\n\nThe format is based on [Keep a C"
},
{
"path": "CODEOWNERS",
"chars": 166,
"preview": "@sigstore/codeowners-sigstore-python\n\n# The CODEOWNERS are managed via a GitHub team, but the current list is (in alphab"
},
{
"path": "CONTRIBUTING.md",
"chars": 5071,
"preview": "Contributing to sigstore\n========================\n\nThank you for your interest in contributing to `sigstore`!\n\nThe infor"
},
{
"path": "LICENSE",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 5586,
"preview": "SHELL := /bin/bash\n\nPY_MODULE := sigstore\n\nALL_PY_SRCS := $(shell find $(PY_MODULE) -name '*.py') \\\n\t$(shell find test -"
},
{
"path": "README.md",
"chars": 15737,
"preview": "sigstore-python\n===============\n\n<!--- @begin-badges@ --->\n[;\n# you may not "
},
{
"path": "docs/signing.md",
"chars": 5522,
"preview": "# Signing\n\n!!! warning\n\n By default signing an artifact creates a public record in `Rekor` which is publicly availabl"
},
{
"path": "docs/stylesheets/custom.css",
"chars": 193,
"preview": "/* From https://github.com/sigstore/community/blob/main/artwork/Sigstore_BrandGuide_March2023.pdf */\n:root {\n --md-prim"
},
{
"path": "docs/verify.md",
"chars": 3612,
"preview": "# Verifying\n\n## Generic identities\n\nThis is the most common verification done with `sigstore`, and therefore\nthe one you"
},
{
"path": "install/requirements.in",
"chars": 16,
"preview": "sigstore==4.2.0\n"
},
{
"path": "mkdocs.yml",
"chars": 2142,
"preview": "# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json\n\nsite_name: sigstore-python\nsite"
},
{
"path": "pyproject.toml",
"chars": 3977,
"preview": "[build-system]\nrequires = [\"flit_core >=3.2,<4\"]\nbuild-backend = \"flit_core.buildapi\"\n\n[project]\nname = \"sigstore\"\ndynam"
},
{
"path": "sigstore/__init__.py",
"chars": 955,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/__main__.py",
"chars": 726,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_cli.py",
"chars": 48073,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/__init__.py",
"chars": 935,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/fulcio/__init__.py",
"chars": 834,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/fulcio/client.py",
"chars": 6018,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/key_details.py",
"chars": 3472,
"preview": "# Copyright 2025 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/merkle.py",
"chars": 4312,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/oidc/__init__.py",
"chars": 653,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/oidc/oauth.py",
"chars": 16379,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/rekor/__init__.py",
"chars": 3557,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/rekor/checkpoint.py",
"chars": 7378,
"preview": "# Copyright 2023 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/rekor/client.py",
"chars": 10875,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/rekor/client_v2.py",
"chars": 5491,
"preview": "# Copyright 2025 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/sct.py",
"chars": 8385,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/timestamp.py",
"chars": 3957,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/trust.py",
"chars": 8573,
"preview": "# Copyright 2023 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_internal/tuf.py",
"chars": 6776,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_store/__init__.py",
"chars": 1247,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/root.json",
"chars": 4219,
"preview": "{\n \"signatures\": [\n {\n \"keyid\": \"aa61e09f6af7662ac686cf0c6364079f63d3e7a86836684eeced93eace3acd81\",\n \"sig\": \"304502"
},
{
"path": "sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/signing_config.v0.2.json",
"chars": 1703,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.signingconfig.v0.2+json\",\n \"caUrls\": [\n {\n \"url\": \"https://fulci"
},
{
"path": "sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/trusted_root.json",
"chars": 7756,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.trustedroot+json;version=0.1\",\n \"tlogs\": [\n {\n \"baseUrl\": \"https"
},
{
"path": "sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/root.json",
"chars": 5490,
"preview": "{\n \"signatures\": [\n {\n \"keyid\": \"e71a54d543835ba86adad9460379c7641fb8726d164ea766801a1c522aba7ea2\",\n \"sig\": \"304602"
},
{
"path": "sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/signing_config.v0.2.json",
"chars": 1034,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.signingconfig.v0.2+json\",\n \"caUrls\": [\n {\n \"url\": \"https://fulci"
},
{
"path": "sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/trusted_root.json",
"chars": 6787,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.trustedroot+json;version=0.1\",\n \"tlogs\": [\n {\n \"baseUrl\": \"https"
},
{
"path": "sigstore/_utils.py",
"chars": 12291,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/dsse/__init__.py",
"chars": 9306,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/dsse/_predicate.py",
"chars": 5726,
"preview": "# Copyright 2024 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/errors.py",
"chars": 3743,
"preview": "# Copyright 2023 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/hashes.py",
"chars": 1967,
"preview": "# Copyright 2023 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/models.py",
"chars": 34521,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/oidc.py",
"chars": 15386,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "sigstore/sign.py",
"chars": 11202,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/verify/__init__.py",
"chars": 1325,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/verify/policy.py",
"chars": 15247,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "sigstore/verify/verifier.py",
"chars": 28168,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/assets/a.dsse.staging-rekor-v2.txt",
"chars": 104,
"preview": "DO NOT MODIFY ME!\n\nthis is \"a.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/a.dsse.staging-rekor-v2.txt.sigstore.json",
"chars": 7127,
"preview": "{\"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\", \"verificationMaterial\": {\"certificate\": {\"rawBytes\": \"MII"
},
{
"path": "test/assets/a.txt",
"chars": 104,
"preview": "DO NOT MODIFY ME!\n\nthis is \"a.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/a.txt.crt",
"chars": 1620,
"preview": "-----BEGIN CERTIFICATE-----\nMIIEfjCCBAWgAwIBAgIUf/SsSCcPgO7o0yKoONG3Vz/RdzIwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmU"
},
{
"path": "test/assets/a.txt.sig",
"chars": 141,
"preview": "MGUCMQDVGmInKk9wYEfCmnp+kPnLYM/P5B9FXR8Ec7AoLRrq+qExIWS9gcg0GPPYbFkqX7gCMAsGbuVHKJedWNF6vnV4J+3p8u8MhKvBTP+gBVeSZU1CuvUL"
},
{
"path": "test/assets/b.txt",
"chars": 104,
"preview": "DO NOT MODIFY ME!\n\nthis is \"b.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/b.txt.crt",
"chars": 1620,
"preview": "-----BEGIN CERTIFICATE-----\nMIIEfzCCBAWgAwIBAgIUXTEFeZc82/onXSK7L3gOx5ZryQYwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmU"
},
{
"path": "test/assets/b.txt.sig",
"chars": 141,
"preview": "MGYCMQCI3sfFtuJ+nyZEhK7HrJNE9OczNsXAJDOoE25rjLMb1sy8uSRdAEz9FORDSW9g6OsCMQCbroOxfpnr77LkvVZqbdRvnAaa3ZJBWXSnz1EiYnJ3OWBW"
},
{
"path": "test/assets/bad.txt",
"chars": 112,
"preview": "DO NOT MODIFY ME!\n\nthis is \"bad.txt\", a fiddled with input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/bad.txt.crt",
"chars": 1620,
"preview": "-----BEGIN CERTIFICATE-----\nMIIEfjCCBAWgAwIBAgIUf/SsSCcPgO7o0yKoONG3Vz/RdzIwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmU"
},
{
"path": "test/assets/bad.txt.sig",
"chars": 141,
"preview": "MGUCMQDVGmInKk9wYEfCmnp+kPnLYM/P5B9FXR8Ec7AoLRrq+qExIWS9gcg0GPPYbFkqX7gCMAsGbuVHKJedWNF6vnV4J+3p8u8MhKvBTP+gBVeSZU1CuvUL"
},
{
"path": "test/assets/bundle.txt",
"chars": 109,
"preview": "DO NOT MODIFY ME!\n\nthis is \"bundle.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/bundle.txt.crt",
"chars": 1067,
"preview": "-----BEGIN CERTIFICATE-----\nMIIC5zCCAmygAwIBAgIUJ3vpewdf6e91rgjqCqagstF4qn8wCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmU"
},
{
"path": "test/assets/bundle.txt.sig",
"chars": 141,
"preview": "MGUCMQCOOJqTY6XWgB64izK2WVP07b0SG9M5WPCwKhfTPwMvtsgUi8KeRGwQkvvLYbKHdqUCMEbOXFG0NMqEQxWVb6rmGnexdADuGf6Jl8qAC8tn67p3QfVo"
},
{
"path": "test/assets/bundle.txt.sigstore",
"chars": 6060,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle+json;version=0.1\",\n \"verificationMaterial\": {\n \"x509Ce"
},
{
"path": "test/assets/bundle_cve_2022_36056.txt",
"chars": 322,
"preview": "DO NOT MODIFY ME!\n\nthis is \"bundle_cve_2022_36056.txt\", a sample input for sigstore-python's unit tests.\n\nthis has a cor"
},
{
"path": "test/assets/bundle_cve_2022_36056.txt.sigstore",
"chars": 5980,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\",\n \"verificationMaterial\": {\n \"certificate\":"
},
{
"path": "test/assets/bundle_invalid_version.txt",
"chars": 225,
"preview": "DO NOT MODIFY ME!\n\nthis is \"bundle_invalid_versions.txt\", a sample input for sigstore-python's unit tests.\n\nthis has a c"
},
{
"path": "test/assets/bundle_invalid_version.txt.sigstore",
"chars": 6027,
"preview": "{\n \"mediaType\": \"this is completely wrong\",\n \"verificationMaterial\": {\n \"certificate\": {\n \"rawBy"
},
{
"path": "test/assets/bundle_no_cert_v1.txt",
"chars": 117,
"preview": "DO NOT MODIFY ME!\n\nthis is \"bundle_no_cert.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/bundle_no_cert_v1.txt.sigstore",
"chars": 4688,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle+json;version=0.1\",\n \"verificationMaterial\": {\n \"x509Ce"
},
{
"path": "test/assets/bundle_no_checkpoint.txt",
"chars": 109,
"preview": "DO NOT MODIFY ME!\n\nthis is \"bundle.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/bundle_no_checkpoint.txt.bundle",
"chars": 0,
"preview": ""
},
{
"path": "test/assets/bundle_no_checkpoint.txt.crt",
"chars": 141,
"preview": "MGUCMArXoJGZeHwbgH1sCqhkv2f2J9XntOwIP1MrcXoqBsU3AAyeyB/1ggizV6ScbQFPtQIxAIoH4b4PCIbqufTc6UG4eTchZgYh5hW8m4BOkhbCEiCzKsaZ"
},
{
"path": "test/assets/bundle_no_checkpoint.txt.sigstore",
"chars": 4719,
"preview": "{\"mediaType\": \"application/vnd.dev.sigstore.bundle+json;version=0.2\", \"verificationMaterial\": {\"x509CertificateChain\": {"
},
{
"path": "test/assets/bundle_no_log_entry.txt",
"chars": 122,
"preview": "DO NOT MODIFY ME!\n\nthis is \"bundle_no_log_entry.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME"
},
{
"path": "test/assets/bundle_no_log_entry.txt.sigstore",
"chars": 1410,
"preview": "{\"mediaType\": \"application/vnd.dev.sigstore.bundle+json;version=0.1\", \"verificationMaterial\": {\"x509CertificateChain\": {"
},
{
"path": "test/assets/bundle_v3.txt",
"chars": 109,
"preview": "DO NOT MODIFY ME!\n\nthis is the input for bundle_v3, which tests support for \"v3\" bundles.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/bundle_v3.txt.sigstore",
"chars": 5760,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\",\n \"verificationMaterial\": {\n \"certificate\":"
},
{
"path": "test/assets/bundle_v3_alt.txt",
"chars": 156,
"preview": "DO NOT MODIFY ME!\n\nthis is the input for bundle_v3_alt, which tests support for \"v3\" bundles\nwith the older (\"alternate\""
},
{
"path": "test/assets/bundle_v3_alt.txt.sigstore",
"chars": 5911,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle+json;version=0.3\",\n \"verificationMaterial\": {\n \"certif"
},
{
"path": "test/assets/bundle_v3_github.whl.sigstore",
"chars": 10040,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle+json;version=0.2\",\n \"verificationMaterial\": {\n \"x509Ce"
},
{
"path": "test/assets/bundle_v3_no_signed_time.txt",
"chars": 164,
"preview": "DO NOT MODIFY ME!\n\nthis is the input for bundle_v3_no_signed_time, which ensures clients reject\nbundles that don't have "
},
{
"path": "test/assets/bundle_v3_no_signed_time.txt.sigstore.json",
"chars": 5534,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\",\n \"verificationMaterial\": {\n \"certificate\":"
},
{
"path": "test/assets/c.txt",
"chars": 104,
"preview": "DO NOT MODIFY ME!\n\nthis is \"c.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/c.txt.crt",
"chars": 1363,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDwTCCA0igAwIBAgIUdXPCI40ren/SEkqxmHcCc6lIV7MwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmU"
},
{
"path": "test/assets/c.txt.sig",
"chars": 141,
"preview": "MGUCMAQYRaYOdZEOT3C3WP22sC9+2euiFGYbC4VNefWVL31+MAL7oKMWsHsBwh1ngjTZHAIxALuUf+mzlACBqYUSTTwl3LFIGUGl8g3Z6wkTMsqdI1NrtHj0"
},
{
"path": "test/assets/integration/Python-3.12.5.tgz.sigstore",
"chars": 5293,
"preview": "{\"mediaType\": \"application/vnd.dev.sigstore.bundle+json;version=0.1\", \"verificationMaterial\": {\"x509CertificateChain\": {"
},
{
"path": "test/assets/integration/a.txt",
"chars": 104,
"preview": "DO NOT MODIFY ME!\n\nthis is \"a.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/integration/attest/slsa_predicate_v0_2.json",
"chars": 15971,
"preview": "{\n \"builder\": {\n \"id\": \"https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_ge"
},
{
"path": "test/assets/integration/attest/slsa_predicate_v1_0.json",
"chars": 1210,
"preview": "{\n \"buildDefinition\": {\n \"buildType\": \"https://actions.github.io/buildtypes/workflow/v1\",\n \"externalPar"
},
{
"path": "test/assets/integration/b.txt",
"chars": 104,
"preview": "DO NOT MODIFY ME!\n\nthis is \"b.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/integration/bundle_v3.txt",
"chars": 109,
"preview": "DO NOT MODIFY ME!\n\nthis is the input for bundle_v3, which tests support for \"v3\" bundles.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/integration/bundle_v3.txt.sigstore",
"chars": 5760,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\",\n \"verificationMaterial\": {\n \"certificate\":"
},
{
"path": "test/assets/integration/c.txt",
"chars": 104,
"preview": "DO NOT MODIFY ME!\n\nthis is \"c.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/offline-rekor.txt",
"chars": 116,
"preview": "DO NOT MODIFY ME!\n\nthis is \"offline-rekor.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/offline-rekor.txt.crt",
"chars": 1620,
"preview": "-----BEGIN CERTIFICATE-----\nMIIEfzCCBAWgAwIBAgIULV3qds9Z1Ar1hOpW+/9ULyl1LgwwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmU"
},
{
"path": "test/assets/offline-rekor.txt.sig",
"chars": 141,
"preview": "MGUCMQCkHC+iuvTo9H1E4ygqCvSq+dAxbqO9Grg12GJDlRe0hMO+TdE/cn2KRB7VGonN0EMCMBvtIkjcIcbBSV0H8pPmpsZiH/OxWc5J7jyEJLERq/M71Gam"
},
{
"path": "test/assets/signing_config/signingconfig-only-v1-rekor.v2.json",
"chars": 1242,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.signingconfig.v0.2+json\",\n \"caUrls\": [\n {\n \"url\": \"https://fulci"
},
{
"path": "test/assets/signing_config/signingconfig.v2.json",
"chars": 1429,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.signingconfig.v0.2+json\",\n \"caUrls\": [\n {\n \"url\": \"https://fulci"
},
{
"path": "test/assets/staging-rekor-v2.txt",
"chars": 119,
"preview": "DO NOT MODIFY ME!\n\nthis is \"staging-rekor-v2.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/staging-rekor-v2.txt.sigstore.json",
"chars": 5627,
"preview": "{\"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\", \"verificationMaterial\": {\"certificate\": {\"rawBytes\": \"MII"
},
{
"path": "test/assets/staging-tuf/16.snapshot.json",
"chars": 498,
"preview": "{\n \"signatures\": [\n {\n \"keyid\": \"c3479007e861445ce5dc109d9661ed77b35bbc0e3f161852c46114266fc2daa4\",\n \"sig\": \"304402"
},
{
"path": "test/assets/staging-tuf/17.targets.json",
"chars": 3988,
"preview": "{\n \"signatures\": [\n {\n \"keyid\": \"aa61e09f6af7662ac686cf0c6364079f63d3e7a86836684eeced93eace3acd81\",\n \"sig\": \"304502"
},
{
"path": "test/assets/staging-tuf/targets/0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem",
"chars": 741,
"preview": "-----BEGIN CERTIFICATE-----\nMIIB9jCCAXugAwIBAgITDdEJvluliE0AzYaIE4jTMdnFTzAKBggqhkjOPQQDAzAq\nMRUwEwYDVQQKEwxzaWdzdG9yZS5"
},
{
"path": "test/assets/staging-tuf/targets/0f395087486ba318321eda478d847962b1dd89846c7dc6e95752a6b110669393.signing_config.v0.2.json",
"chars": 1021,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.signingconfig.v0.2+json\",\n \"caUrls\": [\n {\n \"url\": \"https://fulci"
},
{
"path": "test/assets/staging-tuf/targets/1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub",
"chars": 178,
"preview": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDODRU688UYGuy54mNUlaEBiQdTE9\nnYLr0lg6RXowI/QV/RE1azBn4Eg5"
},
{
"path": "test/assets/staging-tuf/targets/7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub",
"chars": 178,
"preview": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8gEDKNme8AnXuPBgHjrtXdS6miHq\nc24CRblNEOFpiJRngeq8Ko73Y+K1"
},
{
"path": "test/assets/staging-tuf/targets/782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem",
"chars": 790,
"preview": "-----BEGIN CERTIFICATE-----\nMIICGTCCAaCgAwIBAgITJta/okfgHvjabGm1BOzuhrwA1TAKBggqhkjOPQQDAzAq\nMRUwEwYDVQQKEwxzaWdzdG9yZS5"
},
{
"path": "test/assets/staging-tuf/targets/ed6a9cf4e7c2e3297a4b5974fce0d17132f03c63512029d7aa3a402b43acab49.trusted_root.json",
"chars": 6824,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.trustedroot+json;version=0.1\",\n \"tlogs\": [\n {\n \"baseUrl\": \"https"
},
{
"path": "test/assets/staging-tuf/timestamp.json",
"chars": 450,
"preview": "{\n \"signatures\": [\n {\n \"keyid\": \"c3479007e861445ce5dc109d9661ed77b35bbc0e3f161852c46114266fc2daa4\",\n \"sig\": \"304602"
},
{
"path": "test/assets/trust_config/config.badtype.json",
"chars": 10058,
"preview": "{\n \"mediaType\": \"bad-media-type\",\n \"trustedRoot\": {\n \"mediaType\": \"application/vnd.dev.sigstore.trustedroot"
},
{
"path": "test/assets/trust_config/config.v1.json",
"chars": 10370,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.clienttrustconfig.v0.1+json\",\n \"trustedRoot\": {\n \"mediaType\":"
},
{
"path": "test/assets/trusted_root/certificate_authority.empty.json",
"chars": 246,
"preview": "{\n \"subject\": {\n \"organization\": \"GitHub, Inc.\",\n \"commonName\": \"Internal Services Root\"\n },\n \"certChain\": {\n "
},
{
"path": "test/assets/trusted_root/certificate_authority.json",
"chars": 2394,
"preview": "{\n \"subject\": {\n \"organization\": \"GitHub, Inc.\",\n \"commonName\": \"Internal Services Root\"\n },\n \"certChain\": {\n "
},
{
"path": "test/assets/trusted_root/certificate_authority.missingroot.json",
"chars": 1682,
"preview": "{\n \"subject\": {\n \"organization\": \"GitHub, Inc.\",\n \"commonName\": \"Internal Services Root\"\n },\n \"certChain\": {\n "
},
{
"path": "test/assets/trusted_root/trustedroot.badtype.json",
"chars": 7745,
"preview": "{\n \"mediaType\": \"bad-media-type\",\n \"tlogs\": [\n {\n \"baseUrl\": \"https://rekor.sigstore.dev\",\n "
},
{
"path": "test/assets/trusted_root/trustedroot.v1.json",
"chars": 7788,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.trustedroot+json;version=0.1\",\n \"tlogs\": [\n {\n \"ba"
},
{
"path": "test/assets/trusted_root/trustedroot.v1.local_tlog_ed25519_rekor-tiles.json",
"chars": 7704,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.trustedroot+json;version=0.1\",\n \"tlogs\": [\n {\n \"ba"
},
{
"path": "test/assets/tsa/bundle.duplicate.sigstore",
"chars": 10518,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\",\n \"verificationMaterial\": {\n \"certificate\": {\n "
},
{
"path": "test/assets/tsa/bundle.many_timestamp.sigstore",
"chars": 61360,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\",\n \"verificationMaterial\": {\n \"certificate\": {\n "
},
{
"path": "test/assets/tsa/bundle.txt",
"chars": 109,
"preview": "DO NOT MODIFY ME!\n\nthis is \"bundle.txt\", a sample input for sigstore-python's unit tests.\n\nDO NOT MODIFY ME!\n"
},
{
"path": "test/assets/tsa/bundle.txt.late_timestamp.sigstore",
"chars": 6949,
"preview": "{\"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\", \"verificationMaterial\": {\"certificate\": {\"rawBytes\": \"MII"
},
{
"path": "test/assets/tsa/bundle.txt.sigstore",
"chars": 8729,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.bundle.v0.3+json\",\n \"verificationMaterial\": {\n \"certificate\": {\n "
},
{
"path": "test/assets/tsa/ca.json",
"chars": 2154,
"preview": "{\n \"subject\": {\n \"organization\": \"local\",\n \"commonName\": \"Test TSA Timestamping\"\n },\n \"certChain\": {\n \"certi"
},
{
"path": "test/assets/tsa/trust_config.json",
"chars": 11670,
"preview": "{\n \"mediaType\": \"application/vnd.dev.sigstore.clienttrustconfig.v0.1+json\",\n \"trustedRoot\": {\n \"mediaType\": \"applic"
},
{
"path": "test/assets/x509/bogus-intermediate-with-eku.pem",
"chars": 1143,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDHjCCAgagAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-intermediate.pem",
"chars": 1115,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDCTCCAfGgAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-leaf-invalid-eku.pem",
"chars": 1135,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDGDCCAgCgAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-leaf-invalid-ku.pem",
"chars": 1131,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDFzCCAf+gAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-leaf-missing-eku.pem",
"chars": 1107,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDAzCCAeugAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-leaf.pem",
"chars": 1135,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDGDCCAgCgAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-root-invalid-ku.pem",
"chars": 1111,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDBjCCAe6gAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-root-missing-ku.pem",
"chars": 1090,
"preview": "-----BEGIN CERTIFICATE-----\nMIIC+TCCAeGgAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-root-noncritical-bc.pem",
"chars": 1107,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDAzCCAeugAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/bogus-root.pem",
"chars": 1111,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDBjCCAe6gAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln\nc3RvcmUtcHl0aG9uLWJvZ3VzLWN"
},
{
"path": "test/assets/x509/build-testcases.py",
"chars": 11590,
"preview": "#!/usr/bin/env python\n\n# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"Li"
},
{
"path": "test/assets/x509/nonroot-privkey.pem",
"chars": 1704,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC402QfBePPiHp0\nbYH6D9w2K+z1lWHQc1YoSPEd1Jp"
},
{
"path": "test/assets/x509/root-privkey.pem",
"chars": 1704,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJGnUWsZb/91FD\nwpxo4z2JoPZVQnDj8fnXcm7EL/t"
},
{
"path": "test/conftest.py",
"chars": 4468,
"preview": "# Copyright 2024 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/integration/cli/__init__.py",
"chars": 585,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/integration/cli/conftest.py",
"chars": 982,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/integration/cli/test_attest.py",
"chars": 8098,
"preview": "# Copyright 2024 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/integration/cli/test_plumbing.py",
"chars": 3464,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/integration/cli/test_sign.py",
"chars": 13112,
"preview": "# Copyright 2024 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/integration/cli/test_verify.py",
"chars": 1917,
"preview": "# Copyright 2024 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/integration/sigstore-python-conformance",
"chars": 5259,
"preview": "#!/usr/bin/env python3\n\n\"\"\"\nA wrapper to convert `sigstore-conformance` CLI protocol invocations to match `sigstore-pyth"
},
{
"path": "test/unit/__init__.py",
"chars": 585,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/conftest.py",
"chars": 7732,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/__init__.py",
"chars": 585,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/fulcio/__init__.py",
"chars": 585,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/fulcio/test_client.py",
"chars": 586,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/oidc/__init__.py",
"chars": 585,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/oidc/test_issuer.py",
"chars": 3567,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/rekor/__init__.py",
"chars": 585,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/rekor/test_client_v2.py",
"chars": 2138,
"preview": "# Copyright 2025 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/test_key_details.py",
"chars": 6628,
"preview": "# Copyright 2025 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/test_sct.py",
"chars": 1934,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/test_timestamping.py",
"chars": 2010,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/internal/test_trust.py",
"chars": 12456,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/test_dsse.py",
"chars": 2716,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/test_hashes.py",
"chars": 1357,
"preview": "# Copyright 2024 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/test_models.py",
"chars": 7426,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/test_oidc.py",
"chars": 8429,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/test_session_reuse.py",
"chars": 2876,
"preview": "# Copyright 2026 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/test_sign.py",
"chars": 5998,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/test_store.py",
"chars": 1207,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
},
{
"path": "test/unit/test_utils.py",
"chars": 5906,
"preview": "# Copyright 2022 The Sigstore Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not "
}
]
// ... and 7 more files (download for full content)
About this extraction
This page contains the full source code of the sigstore/sigstore-python GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 207 files (869.0 KB), approximately 316.0k tokens, and a symbol index with 569 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.