├── sigstore ├── py.typed ├── _internal │ ├── oidc │ │ └── __init__.py │ ├── fulcio │ │ └── __init__.py │ ├── __init__.py │ ├── key_details.py │ ├── rekor │ │ └── __init__.py │ ├── timestamp.py │ └── merkle.py ├── __main__.py ├── __init__.py ├── _store │ ├── https%3A%2F%2Ftuf-repo-cdn.sigstore.dev │ │ └── signing_config.v0.2.json │ ├── __init__.py │ └── https%3A%2F%2Ftuf-repo-cdn.sigstage.dev │ │ ├── signing_config.v0.2.json │ │ └── root.json ├── verify │ └── __init__.py ├── hashes.py └── errors.py ├── docs ├── api │ ├── oidc.md │ ├── sign.md │ ├── errors.md │ ├── hashes.md │ ├── models.md │ ├── verify │ │ ├── policy.md │ │ └── verifier.md │ └── index.md ├── assets │ └── images │ │ ├── logo.png │ │ └── favicon.png ├── stylesheets │ └── custom.css ├── index.md ├── installation.md ├── advanced │ ├── offline.md │ └── custom_trust.md ├── scripts │ └── gen_ref_pages.py └── verify.md ├── install ├── .python-version └── requirements.in ├── test ├── assets │ ├── bundle_no_checkpoint.txt.bundle │ ├── bundle_v3_github.whl │ ├── tsa │ │ ├── issue1482-message │ │ ├── bundle.txt │ │ ├── issue1482-timestamp-with-no-cert │ │ └── ca.json │ ├── a.txt │ ├── b.txt │ ├── c.txt │ ├── bad.txt │ ├── bundle.txt │ ├── integration │ │ ├── a.txt │ │ ├── b.txt │ │ ├── c.txt │ │ ├── bundle_v3.txt │ │ ├── attest │ │ │ └── slsa_predicate_v1_0.json │ │ └── Python-3.12.5.tgz.sigstore │ ├── bundle_v3.txt │ ├── a.dsse.staging-rekor-v2.txt │ ├── a.txt.sig │ ├── b.txt.sig │ ├── bad.txt.sig │ ├── bundle_no_checkpoint.txt │ ├── c.txt.sig │ ├── offline-rekor.txt │ ├── bundle.txt.sig │ ├── bundle_no_cert_v1.txt │ ├── staging-rekor-v2.txt │ ├── bundle_no_log_entry.txt │ ├── offline-rekor.txt.sig │ ├── bundle_no_checkpoint.txt.crt │ ├── bundle_v3_alt.txt │ ├── bundle_v3_no_signed_time.txt │ ├── bundle_invalid_version.txt │ ├── staging-tuf │ │ ├── targets │ │ │ ├── 1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub │ │ │ ├── 7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub │ │ │ ├── 0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem │ │ │ ├── 782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem │ │ │ └── 0f395087486ba318321eda478d847962b1dd89846c7dc6e95752a6b110669393.signing_config.v0.2.json │ │ ├── timestamp.json │ │ ├── 16.snapshot.json │ │ └── 17.targets.json │ ├── trusted_root │ │ ├── certificate_authority.empty.json │ │ ├── certificate_authority.missingroot.json │ │ └── certificate_authority.json │ ├── bundle_cve_2022_36056.txt │ ├── bundle.txt.crt │ ├── x509 │ │ ├── bogus-root-missing-ku.pem │ │ ├── bogus-root.pem │ │ ├── bogus-leaf-missing-eku.pem │ │ ├── bogus-intermediate.pem │ │ ├── bogus-root-invalid-ku.pem │ │ ├── bogus-root-noncritical-bc.pem │ │ ├── bogus-leaf.pem │ │ ├── bogus-leaf-invalid-ku.pem │ │ ├── bogus-leaf-invalid-eku.pem │ │ ├── bogus-intermediate-with-eku.pem │ │ ├── root-privkey.pem │ │ └── nonroot-privkey.pem │ ├── bundle_no_log_entry.txt.sigstore │ ├── c.txt.crt │ ├── signing_config │ │ ├── signingconfig-only-v1-rekor.v2.json │ │ └── signingconfig.v2.json │ ├── a.txt.crt │ ├── b.txt.crt │ ├── bad.txt.crt │ ├── offline-rekor.txt.crt │ ├── bundle_no_checkpoint.txt.sigstore │ ├── bundle_no_cert_v1.txt.sigstore │ ├── staging-rekor-v2.txt.sigstore.json │ ├── bundle_v3_no_signed_time.txt.sigstore.json │ └── bundle_v3.txt.sigstore ├── unit │ ├── __init__.py │ ├── internal │ │ ├── __init__.py │ │ ├── fulcio │ │ │ ├── __init__.py │ │ │ └── test_client.py │ │ ├── oidc │ │ │ ├── __init__.py │ │ │ └── test_issuer.py │ │ ├── rekor │ │ │ ├── __init__.py │ │ │ └── test_client_v2.py │ │ ├── test_sct.py │ │ └── test_timestamping.py │ ├── verify │ │ └── __init__.py │ ├── test_version.py │ ├── test_store.py │ ├── test_hashes.py │ └── test_dsse.py ├── integration │ ├── cli │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_verify.py │ │ └── test_plumbing.py │ └── sigstore-python-conformance └── conftest.py ├── CODEOWNERS ├── .gitattributes ├── cloudbuild.yaml ├── .gitignore ├── COPYRIGHT.txt ├── .github ├── dependabot.yml ├── workflows │ ├── depsreview.yml │ ├── requirements.yml │ ├── docs.yml │ ├── conformance.yml │ ├── scorecards-analysis.yml │ ├── check-embedded-root.yml │ ├── staging-tests.yml │ ├── lint.yml │ ├── cross-os.yml │ └── cross-version-verify.yaml ├── actions │ └── upload-coverage │ │ └── action.yml └── pull_request_template.md ├── mkdocs.yml └── pyproject.toml /sigstore/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/api/oidc.md: -------------------------------------------------------------------------------- 1 | :::sigstore.oidc 2 | -------------------------------------------------------------------------------- /docs/api/sign.md: -------------------------------------------------------------------------------- 1 | :::sigstore.sign 2 | -------------------------------------------------------------------------------- /install/.python-version: -------------------------------------------------------------------------------- 1 | 3.9.16 2 | -------------------------------------------------------------------------------- /docs/api/errors.md: -------------------------------------------------------------------------------- 1 | :::sigstore.errors 2 | -------------------------------------------------------------------------------- /docs/api/hashes.md: -------------------------------------------------------------------------------- 1 | :::sigstore.hashes 2 | -------------------------------------------------------------------------------- /docs/api/models.md: -------------------------------------------------------------------------------- 1 | :::sigstore.models 2 | -------------------------------------------------------------------------------- /install/requirements.in: -------------------------------------------------------------------------------- 1 | sigstore==4.1.0 2 | -------------------------------------------------------------------------------- /test/assets/bundle_no_checkpoint.txt.bundle: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/api/verify/policy.md: -------------------------------------------------------------------------------- 1 | :::sigstore.verify.policy 2 | -------------------------------------------------------------------------------- /docs/api/verify/verifier.md: -------------------------------------------------------------------------------- 1 | :::sigstore.verify.verifier 2 | -------------------------------------------------------------------------------- /docs/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigstore/sigstore-python/HEAD/docs/assets/images/logo.png -------------------------------------------------------------------------------- /docs/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigstore/sigstore-python/HEAD/docs/assets/images/favicon.png -------------------------------------------------------------------------------- /test/assets/bundle_v3_github.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigstore/sigstore-python/HEAD/test/assets/bundle_v3_github.whl -------------------------------------------------------------------------------- /test/assets/tsa/issue1482-message: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigstore/sigstore-python/HEAD/test/assets/tsa/issue1482-message -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | !!! note 2 | 3 | The API reference is automatically generated from the docstrings 4 | 5 | :::sigstore 6 | -------------------------------------------------------------------------------- /test/assets/a.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "a.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/b.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "b.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/c.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "c.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/bad.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "bad.txt", a fiddled with input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/bundle.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "bundle.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/integration/a.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "a.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/integration/b.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "b.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/integration/c.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "c.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/bundle_v3.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is the input for bundle_v3, which tests support for "v3" bundles. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/tsa/bundle.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "bundle.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/tsa/issue1482-timestamp-with-no-cert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sigstore/sigstore-python/HEAD/test/assets/tsa/issue1482-timestamp-with-no-cert -------------------------------------------------------------------------------- /test/assets/a.dsse.staging-rekor-v2.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "a.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/a.txt.sig: -------------------------------------------------------------------------------- 1 | MGUCMQDVGmInKk9wYEfCmnp+kPnLYM/P5B9FXR8Ec7AoLRrq+qExIWS9gcg0GPPYbFkqX7gCMAsGbuVHKJedWNF6vnV4J+3p8u8MhKvBTP+gBVeSZU1CuvULwDfU15EDEwgitIBgiA== 2 | -------------------------------------------------------------------------------- /test/assets/b.txt.sig: -------------------------------------------------------------------------------- 1 | MGYCMQCI3sfFtuJ+nyZEhK7HrJNE9OczNsXAJDOoE25rjLMb1sy8uSRdAEz9FORDSW9g6OsCMQCbroOxfpnr77LkvVZqbdRvnAaa3ZJBWXSnz1EiYnJ3OWBWp+699o9b8u0AxiPnofI= 2 | -------------------------------------------------------------------------------- /test/assets/bad.txt.sig: -------------------------------------------------------------------------------- 1 | MGUCMQDVGmInKk9wYEfCmnp+kPnLYM/P5B9FXR8Ec7AoLRrq+qExIWS9gcg0GPPYbFkqX7gCMAsGbuVHKJedWNF6vnV4J+3p8u8MhKvBTP+gBVeSZU1CuvULwDfU15EDEwgitIBgiA== 2 | -------------------------------------------------------------------------------- /test/assets/bundle_no_checkpoint.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "bundle.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/c.txt.sig: -------------------------------------------------------------------------------- 1 | MGUCMAQYRaYOdZEOT3C3WP22sC9+2euiFGYbC4VNefWVL31+MAL7oKMWsHsBwh1ngjTZHAIxALuUf+mzlACBqYUSTTwl3LFIGUGl8g3Z6wkTMsqdI1NrtHj0rVpcWA1DIO4GhGOM5w== 2 | -------------------------------------------------------------------------------- /test/assets/integration/bundle_v3.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is the input for bundle_v3, which tests support for "v3" bundles. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/offline-rekor.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "offline-rekor.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/bundle.txt.sig: -------------------------------------------------------------------------------- 1 | MGUCMQCOOJqTY6XWgB64izK2WVP07b0SG9M5WPCwKhfTPwMvtsgUi8KeRGwQkvvLYbKHdqUCMEbOXFG0NMqEQxWVb6rmGnexdADuGf6Jl8qAC8tn67p3QfVoXzMvFA61PzxwVwvb8g== 2 | -------------------------------------------------------------------------------- /test/assets/bundle_no_cert_v1.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "bundle_no_cert.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/staging-rekor-v2.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "staging-rekor-v2.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/bundle_no_log_entry.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "bundle_no_log_entry.txt", a sample input for sigstore-python's unit tests. 4 | 5 | DO NOT MODIFY ME! 6 | -------------------------------------------------------------------------------- /test/assets/offline-rekor.txt.sig: -------------------------------------------------------------------------------- 1 | MGUCMQCkHC+iuvTo9H1E4ygqCvSq+dAxbqO9Grg12GJDlRe0hMO+TdE/cn2KRB7VGonN0EMCMBvtIkjcIcbBSV0H8pPmpsZiH/OxWc5J7jyEJLERq/M71GamZOor9xx5x83L8Dg2HA== 2 | -------------------------------------------------------------------------------- /test/assets/bundle_no_checkpoint.txt.crt: -------------------------------------------------------------------------------- 1 | MGUCMArXoJGZeHwbgH1sCqhkv2f2J9XntOwIP1MrcXoqBsU3AAyeyB/1ggizV6ScbQFPtQIxAIoH4b4PCIbqufTc6UG4eTchZgYh5hW8m4BOkhbCEiCzKsaZ0Trg8+Hm1N8egtVgYw== 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | @sigstore/codeowners-sigstore-python 2 | 3 | # The CODEOWNERS are managed via a GitHub team, but the current list is (in alphabetical order): 4 | 5 | # di 6 | # tetsuo-cpp 7 | # woodruffw 8 | -------------------------------------------------------------------------------- /test/assets/bundle_v3_alt.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is the input for bundle_v3_alt, which tests support for "v3" bundles 4 | with the older ("alternate") v3 media type. 5 | 6 | DO NOT MODIFY ME! 7 | -------------------------------------------------------------------------------- /test/assets/bundle_v3_no_signed_time.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is the input for bundle_v3_no_signed_time, which ensures clients reject 4 | bundles that don't have a source of signed time. 5 | 6 | DO NOT MODIFY ME! 7 | -------------------------------------------------------------------------------- /docs/stylesheets/custom.css: -------------------------------------------------------------------------------- 1 | /* From https://github.com/sigstore/community/blob/main/artwork/Sigstore_BrandGuide_March2023.pdf */ 2 | :root { 3 | --md-primary-fg-color: #2e2f71; 4 | --md-primary-bg-color: #f9f7ef; 5 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # These directories contain TUF and other assets that are either digested 2 | # or sized-checked so CRLF normalization breaks them. 3 | sigstore/_store/** binary diff=text 4 | test/assets/** binary diff=text 5 | test/assets/x509/** -binary 6 | -------------------------------------------------------------------------------- /test/assets/bundle_invalid_version.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "bundle_invalid_versions.txt", a sample input for sigstore-python's unit tests. 4 | 5 | this has a corresponding bundle that is valid, *except* that the bundle's 6 | media type is nonsense. 7 | 8 | DO NOT MODIFY ME! 9 | -------------------------------------------------------------------------------- /test/assets/staging-tuf/targets/1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959.rekor.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDODRU688UYGuy54mNUlaEBiQdTE9 3 | nYLr0lg6RXowI/QV/RE1azBn4Eg5/2uTOMbhB1/gfcHzijzFi9Tk+g1Prg== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test/assets/staging-tuf/targets/7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6.ctfe_2022_2.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8gEDKNme8AnXuPBgHjrtXdS6miHq 3 | c24CRblNEOFpiJRngeq8Ko73Y+K18yRYVf1DXD4AVLwvKyzdNdl5n0jUSQ== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test/assets/trusted_root/certificate_authority.empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "subject": { 3 | "organization": "GitHub, Inc.", 4 | "commonName": "Internal Services Root" 5 | }, 6 | "certChain": { 7 | "certificates": [] 8 | }, 9 | "validFor": { 10 | "start": "2023-04-14T00:00:00.000Z", 11 | "end": "2024-04-14T00:00:00.000Z" 12 | } 13 | } -------------------------------------------------------------------------------- /test/assets/bundle_cve_2022_36056.txt: -------------------------------------------------------------------------------- 1 | DO NOT MODIFY ME! 2 | 3 | this is "bundle_cve_2022_36056.txt", a sample input for sigstore-python's unit tests. 4 | 5 | this has a corresponding bundle that is valid, *except* that the included log entry 6 | is from a *valid but unrelated* bundle (specifically, for an identical input 7 | signed immediately after this one). 8 | 9 | DO NOT MODIFY ME! 10 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | # Install dependencies 3 | - name: python 4 | entrypoint: python 5 | args: ["-m", "pip", "install", ".", "--user"] 6 | 7 | # Sign with ambient GCP credentials 8 | - name: python 9 | entrypoint: python 10 | args: ["-m", "sigstore", "sign", "README.md"] 11 | env: 12 | - "GOOGLE_SERVICE_ACCOUNT_NAME=sigstore-python-test@projectsigstore.iam.gserviceaccount.com" 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | env/ 3 | pip-wheel-metadata/ 4 | *.egg-info/ 5 | __pycache__/ 6 | .coverage* 7 | html/ 8 | dist/ 9 | .python-version 10 | build 11 | 12 | # Ignore some file types that may litter the root directory 13 | *.txt 14 | *.crt 15 | *.sig 16 | *.pem 17 | *.sh 18 | *.pub 19 | *.rekor 20 | *.sigstore 21 | *.sigstore.json 22 | 23 | # Don't ignore these files when we intend to include them 24 | !sigstore/_store/*.crt 25 | !sigstore/_store/*.pem 26 | !sigstore/_store/*.pub 27 | !test/assets/** 28 | !test/assets/staging-tuf/** 29 | -------------------------------------------------------------------------------- /test/assets/staging-tuf/timestamp.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "c3479007e861445ce5dc109d9661ed77b35bbc0e3f161852c46114266fc2daa4", 5 | "sig": "3046022100fedb5a3d1a3c461c1337d7535edca8012fb0ab8da31315dbdf22b7f38f76973e022100a87967789d2d2942919dcc4f33def8ee74745f577ff0ef5479cc9f573842e8de" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "timestamp", 10 | "expires": "2025-07-29T13:28:44Z", 11 | "meta": { 12 | "snapshot.json": { 13 | "version": 16 14 | } 15 | }, 16 | "spec_version": "1.0", 17 | "version": 353 18 | } 19 | } -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 The Sigstore Authors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /test/assets/staging-tuf/16.snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "c3479007e861445ce5dc109d9661ed77b35bbc0e3f161852c46114266fc2daa4", 5 | "sig": "304402202733036a5044a3257392cb6737c80d1972aa2bce8e7194fac23e3d0b939e83ce0220797111c4aa47094278a2997d727c728fcda795b02b8ec803e2265fdac9614a21" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "snapshot", 10 | "expires": "2035-06-11T11:54:57Z", 11 | "meta": { 12 | "registry.npmjs.org.json": { 13 | "version": 5 14 | }, 15 | "targets.json": { 16 | "version": 17 17 | } 18 | }, 19 | "spec_version": "1.0", 20 | "version": 16 21 | } 22 | } -------------------------------------------------------------------------------- /test/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /test/integration/cli/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /test/unit/internal/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /test/unit/verify/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /test/unit/internal/fulcio/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /test/unit/internal/oidc/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /test/unit/internal/rekor/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /test/unit/internal/fulcio/test_client.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: pip 5 | directory: / 6 | schedule: 7 | interval: daily 8 | 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: daily 13 | open-pull-requests-limit: 99 14 | rebase-strategy: "disabled" 15 | groups: 16 | actions: 17 | patterns: 18 | - "*" 19 | 20 | - package-ecosystem: github-actions 21 | directory: .github/actions/upload-coverage/ 22 | schedule: 23 | interval: daily 24 | open-pull-requests-limit: 99 25 | rebase-strategy: "disabled" 26 | groups: 27 | actions: 28 | patterns: 29 | - "*" 30 | -------------------------------------------------------------------------------- /sigstore/_internal/oidc/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Internal OIDC and OAuth functionality for sigstore-python. 17 | """ 18 | -------------------------------------------------------------------------------- /test/unit/test_version.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import sigstore 16 | 17 | 18 | def test_version(): 19 | assert isinstance(sigstore.__version__, str) 20 | -------------------------------------------------------------------------------- /sigstore/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | The `python -m sigstore` entrypoint. 17 | """ 18 | 19 | if __name__ == "__main__": # pragma: no cover 20 | from sigstore._cli import main 21 | 22 | main() 23 | -------------------------------------------------------------------------------- /test/assets/staging-tuf/targets/0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1.fulcio.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB9jCCAXugAwIBAgITDdEJvluliE0AzYaIE4jTMdnFTzAKBggqhkjOPQQDAzAq 3 | MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIy 4 | MDMyNTE2NTA0NloXDTMyMDMyMjE2NTA0NVowKjEVMBMGA1UEChMMc2lnc3RvcmUu 5 | ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMo9 6 | BUNk9QIYisYysC24+2OytoV72YiLonYcqR3yeVnYziPt7Xv++CYE8yoCTiwedUEC 7 | CWKOcvQKRCJZb9ht4Hzy+VvBx36hK+C6sECCSR0x6pPSiz+cTk1f788ZjBlUZaNj 8 | MGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP9C 9 | Mrpofas6cK/cDNQa4j6Hj2ZlMB8GA1UdIwQYMBaAFP9CMrpofas6cK/cDNQa4j6H 10 | j2ZlMAoGCCqGSM49BAMDA2kAMGYCMQD+kojuzMwztNay9Ibzjuk//ZL5m6T2OCsm 11 | 45l1lY004pcb984L926BowodoirFMcMCMQDIJtFHhP/1D3a+M3dAGomOb6O4CmTr 12 | y3TTPbPsAFnv22YA0Y+P21NVoxKDjdu0tkw= 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /test/assets/staging-tuf/targets/782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b.fulcio_intermediate.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGTCCAaCgAwIBAgITJta/okfgHvjabGm1BOzuhrwA1TAKBggqhkjOPQQDAzAq 3 | MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIy 4 | MDQxNDIxMzg0MFoXDTMyMDMyMjE2NTA0NVowNzEVMBMGA1UEChMMc2lnc3RvcmUu 5 | ZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwdjAQBgcqhkjOPQIB 6 | BgUrgQQAIgNiAASosAySWJQ/tK5r8T5aHqavk0oI+BKQbnLLdmOMRXHQF/4Hx9Kt 7 | NfpcdjH9hNKQSBxSlLFFN3tvFCco0qFBzWYwZtsYsBe1l91qYn/9VHFTaEVwYQWI 8 | JEEvrs0fvPuAqjajezB5MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEF 9 | BQcDAzASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRxhjCmFHxib/n31vQF 10 | Gn9f/+tvrDAfBgNVHSMEGDAWgBT/QjK6aH2rOnCv3AzUGuI+h49mZTAKBggqhkjO 11 | PQQDAwNnADBkAjAM1lbKkcqQlE/UspMTbWNo1y2TaJ44tx3l/FJFceTSdDZ+0W1O 12 | HHeU4twie/lq8XgCMHQxgEv26xNNiAGyPXbkYgrDPvbOqp0UeWX4mJnLSrBr3aN/ 13 | KX1SBrKQu220FmVL0Q== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /.github/workflows/depsreview.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 The Sigstore Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | name: 'Dependency Review' 16 | on: [pull_request] 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | dependency-review: 23 | name: License and Vulnerability Scan 24 | uses: sigstore/community/.github/workflows/reusable-dependency-review.yml@9b1b5aca605f92ec5b1bf3681b1e61b3dbc420cc 25 | -------------------------------------------------------------------------------- /sigstore/_internal/fulcio/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | APIs for interacting with Fulcio. 17 | """ 18 | 19 | from .client import ( 20 | ExpiredCertificate, 21 | FulcioCertificateSigningResponse, 22 | FulcioClient, 23 | ) 24 | 25 | __all__ = [ 26 | "ExpiredCertificate", 27 | "FulcioCertificateSigningResponse", 28 | "FulcioClient", 29 | ] 30 | -------------------------------------------------------------------------------- /sigstore/_internal/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | sigstore-python's internal APIs. 17 | 18 | Everything in these APIs is considered internal and unstable, and is not 19 | subject to any stability guarantees. 20 | """ 21 | 22 | from requests import __version__ as requests_version 23 | 24 | from sigstore import __version__ as sigstore_version 25 | 26 | USER_AGENT = f"sigstore-python/{sigstore_version} (python-requests/{requests_version})" 27 | -------------------------------------------------------------------------------- /sigstore/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | The `sigstore` Python APIs. 17 | 18 | For command-line usage of `sigstore`, refer to the `sigstore` 19 | [README](https://github.com/sigstore/sigstore-python). 20 | 21 | Otherwise, here are some quick starting points: 22 | 23 | * `sigstore.verify`: verifying of Sigstore signatures, 24 | including flexible policy control 25 | * `sigstore.sign`: creation of Sigstore signatures 26 | """ 27 | 28 | __version__ = "4.1.0" 29 | -------------------------------------------------------------------------------- /test/integration/cli/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from pathlib import Path 15 | from typing import Callable 16 | 17 | import pytest 18 | 19 | from sigstore._cli import main 20 | 21 | 22 | @pytest.fixture 23 | def asset_integration(asset): 24 | def _asset(name: str) -> Path: 25 | return asset(f"integration/{name}") 26 | 27 | return _asset 28 | 29 | 30 | @pytest.fixture(scope="function") 31 | def sigstore() -> Callable: 32 | def _sigstore(*args: str): 33 | main(list(args)) 34 | 35 | return _sigstore 36 | -------------------------------------------------------------------------------- /test/assets/bundle.txt.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC5zCCAmygAwIBAgIUJ3vpewdf6e91rgjqCqagstF4qn8wCgYIKoZIzj0EAwMw 3 | NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl 4 | cm1lZGlhdGUwHhcNMjMwNDI2MDAyMTA4WhcNMjMwNDI2MDAzMTA4WjAAMHYwEAYH 5 | KoZIzj0CAQYFK4EEACIDYgAE2sd6+lOBcn5MXtnbwca7zcwpprl7GUZiKTO9IWpA 6 | UfVTtx+BXGHQCRwsFy/d7dLlf4hurIqhzMD5yaC2kcU9/8c9G55JyBXF8Dx5SQm9 7 | y2rPWFIdm29Ql9A3I3yyEFyPo4IBbjCCAWowDgYDVR0PAQH/BAQDAgeAMBMGA1Ud 8 | JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBTlaUfjpiXGhBP3hOCW0JJZDSPxgzAf 9 | BgNVHSMEGDAWgBRxhjCmFHxib/n31vQFGn9f/+tvrDAYBgNVHREBAf8EDjAMgQph 10 | QHRueS50b3duMCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dp 11 | bi9vYXV0aDAuBgorBgEEAYO/MAEIBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dp 12 | bi9vYXV0aDCBigYKKwYBBAHWeQIEAgR8BHoAeAB2ACswvNxoiMni4dgmKV50H0g5 13 | MZYC8pwzy15DQP6yrIZ6AAABh7rveBsAAAQDAEcwRQIhAKOZPMN9Q9qO1HXigHBP 14 | t+Ic16yy2Zgv2KQ23i5WLj16AiAzrFpuayGXdoK+hYePl9dEeXjG/vB2jK/E3sEs 15 | IrXtETAKBggqhkjOPQQDAwNpADBmAjEAgmhg80mI/Scr0isBnD5FYXZ8WxA8tnBB 16 | Pmdf4aNGForGazGXaFQVPXgBVPv+YGI/AjEA0QzPC5dHD/WWXW2GbEC4dpwFk8OG 17 | RkiExMOy/+CqabbVg+/lx1N9VGBTlUTft45d 18 | -----END CERTIFICATE----- 19 | 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-root-missing-ku.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+TCCAeGgAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRp1FrGW//dRQ8KcaOM9 6 | iaD2VUJw4/H513JuxC/7aQ+7WPfBLQ4wLytoJH9E7eSTrAPjh7lilbYVvL2TFLCJ 7 | kiaGuOgvOpCN8uxC9ie/r+ui+YgexJwlMjwxfqx67WTXZJtC/GVS45ISfq6MkIwU 8 | tQWvTPXJnHl2epIXj4el6XIQWQL/koBgUlzbNrfdwZ1NpAm0jNDK44DjXDCwEy8U 9 | lTDMle1dAb4V80GlF5s87cauNp5sfghz05iqZiTSg4461v/EFH7TElAtuaLqa36P 10 | bo1OEIkeWyej8n60mFbwY2v4Frb9G9vQMY1gV7vLtetfJpgKKIj/iK/jbsrrhjsC 11 | RwIDAQABozEwLzAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTAPBgNVHRMB 12 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA038HUNVxomLhJ8zC1HQpR4fiY 13 | pMvxajYXW+h6wi4LS9TxWtxN86etDZWcc7BNYYqEtmn+TYdg3bpXW7uPMM0tpZ6f 14 | WUZ+yPGKJi6iyOpYHgJMIy7sbSMZHpkPUeMf9Ye8rILrmP8CfjxuT6cq9RpGDqXf 15 | +rltrXRzmTSecqEyjs9faxf57LE21+4Jpla3WA6fIzidKcMjbFQqqqUMu9OadXZO 16 | JZqFP18GThZToZs7pXKNlVvMwNNnCnyrn8WbL4j95IokNwzC7lI5opc+FOGUFEZh 17 | fnAByZ3AqmSFrcnE3+B5eSfupds4mcHnryqSP4/6xvd26aqs/qkmBJ+QkQO9 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-root.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDBjCCAe6gAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRp1FrGW//dRQ8KcaOM9 6 | iaD2VUJw4/H513JuxC/7aQ+7WPfBLQ4wLytoJH9E7eSTrAPjh7lilbYVvL2TFLCJ 7 | kiaGuOgvOpCN8uxC9ie/r+ui+YgexJwlMjwxfqx67WTXZJtC/GVS45ISfq6MkIwU 8 | tQWvTPXJnHl2epIXj4el6XIQWQL/koBgUlzbNrfdwZ1NpAm0jNDK44DjXDCwEy8U 9 | lTDMle1dAb4V80GlF5s87cauNp5sfghz05iqZiTSg4461v/EFH7TElAtuaLqa36P 10 | bo1OEIkeWyej8n60mFbwY2v4Frb9G9vQMY1gV7vLtetfJpgKKIj/iK/jbsrrhjsC 11 | RwIDAQABoz4wPDAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTAPBgNVHRMB 12 | Af8EBTADAQH/MAsGA1UdDwQEAwIChDANBgkqhkiG9w0BAQsFAAOCAQEAGxfbsceU 13 | WyuT59wIXqNjBd1HybiYEHZQw87o907ovdNZALLZ2URTPllIoNUiaxVa3VjG3ttj 14 | iVVDe1JLbS8/JaG+ZqQkHAorByM1tjxoIFiq+yaBYeg997etgFM1OVhbRNq744LE 15 | 2zPEeYTiokbQwDAeUtYRmo+9vK7gn7iNAb/pYOswMMtcGOZSj7ebvJQwkS5qDGMz 16 | zJ1pdkRpP0/kaLsZouaTsPiiJp3vV0QvVjOJKT765YF0pQCehl17JehDS6jgXhe5 17 | gqatlDDm7ALG4bCGbqnC4XLYXaEstD4UbrUEvQ5lnO2+jbgDaOoyC6pzGjvC3p7u 18 | BX8EoFOjwFfx0w== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-leaf-missing-eku.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDAzCCAeugAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNNkHwXjz4h6dG2B+g/c 6 | Nivs9ZVh0HNWKEjxHdSaZQ2CXVjnH91rEj7JTsEZLYsICvWjllYLDCfOmV8lfwOT 7 | FcPAUqysaA7SF1bH/8KqTAfaYrvrVQaMFxOxOfclmMBc2cN2GtcrLLZrnZuwZ40E 8 | 7Zchx5uCZ7njtKV2JL0cQIpjs+rnNVxqy158/Qf/AceC5c/5hI0WI9mw9uL7nzG4 9 | hSm/CWvd7fvQC6UV/Qt9BosteVIVB/3oYfstnwhr/8apmvq0z69iOf9/wUGPbKWp 10 | gawF0l+2Z8xMfvB25d49rXWItywteBlkPViE+ew2Ix7UAQZ55EPZzhhI7Pozou/g 11 | KQIDAQABozswOTAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTAMBgNVHRMB 12 | Af8EAjAAMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAAz58QW5XVZbg 13 | nzXKXhBYcbRRUqbw6edShLna8yzeB8acsuSwYz4sG4h41Q7opNBd3WhEn1dk6loo 14 | ZWM9NpG+t33LUgIjVsEUgt55kMB2DzBH7HHMsS7eGA7Qo/LX6tt3vX4bKG6HmHOI 15 | Gz7cPr8mRkO/EJHcJxTSRQ1uhQGXfjuBO5F2LSOsAUc8bP8VONJFk3lR/ZoON7qv 16 | +fGTYUp8qYlLQeANJHgywhTxWzcA0ew8j8+qDuTVsVxQUYqsA8m1TSHIPQXCo5gp 17 | YU7oyEtGt/ly6CDxVTEJVEZndP5roRhm3oYqCJIl9jDvPg7WTyxMtQ9boBzxVPix 18 | VRqHa9Q1tQ== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-intermediate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDCTCCAfGgAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNNkHwXjz4h6dG2B+g/c 6 | Nivs9ZVh0HNWKEjxHdSaZQ2CXVjnH91rEj7JTsEZLYsICvWjllYLDCfOmV8lfwOT 7 | FcPAUqysaA7SF1bH/8KqTAfaYrvrVQaMFxOxOfclmMBc2cN2GtcrLLZrnZuwZ40E 8 | 7Zchx5uCZ7njtKV2JL0cQIpjs+rnNVxqy158/Qf/AceC5c/5hI0WI9mw9uL7nzG4 9 | hSm/CWvd7fvQC6UV/Qt9BosteVIVB/3oYfstnwhr/8apmvq0z69iOf9/wUGPbKWp 10 | gawF0l+2Z8xMfvB25d49rXWItywteBlkPViE+ew2Ix7UAQZ55EPZzhhI7Pozou/g 11 | KQIDAQABo0EwPzAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTASBgNVHRMB 12 | Af8ECDAGAQH/AgEBMAsGA1UdDwQEAwIChDANBgkqhkiG9w0BAQsFAAOCAQEAIdCQ 13 | NkReQcTU3aUCKqWdwCeFswg83lFHchgrH6QGSEwNI+y4YnEeSDs4KoU9ptLfVCoG 14 | WWYaTnePnzjTcOjYs0439/c2zS936EmVJs6EO55LK2YFKGHzhZARnAKVkiwUcHuZ 15 | bCrV9M3/muZmEwMXszzZfniREMyQZcfqbJjZZURRdmdK+dmHwHNDecXtbPNxQdB3 16 | BRGtydZd5PH6ATR9zCz+ds3gRth+JFqzEYZH07BeHaCkRL80N7L8hH+E4+y0vVFJ 17 | sYfWMolbfPxEDRn6XQ/FROV3Kq2kSUIV09FBtE3b4aICB3ih78Xpzmvhh1g8rp7o 18 | oosP3AhvLCLjpruF/A== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-root-invalid-ku.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDBjCCAe6gAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRp1FrGW//dRQ8KcaOM9 6 | iaD2VUJw4/H513JuxC/7aQ+7WPfBLQ4wLytoJH9E7eSTrAPjh7lilbYVvL2TFLCJ 7 | kiaGuOgvOpCN8uxC9ie/r+ui+YgexJwlMjwxfqx67WTXZJtC/GVS45ISfq6MkIwU 8 | tQWvTPXJnHl2epIXj4el6XIQWQL/koBgUlzbNrfdwZ1NpAm0jNDK44DjXDCwEy8U 9 | lTDMle1dAb4V80GlF5s87cauNp5sfghz05iqZiTSg4461v/EFH7TElAtuaLqa36P 10 | bo1OEIkeWyej8n60mFbwY2v4Frb9G9vQMY1gV7vLtetfJpgKKIj/iK/jbsrrhjsC 11 | RwIDAQABoz4wPDAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTAPBgNVHRMB 12 | Af8EBTADAQH/MAsGA1UdDwQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAnPQAk1ng 13 | m/XwyfFe6uvz7PaNz6wryeWWDlEM4ON48Rrf/QhIsG3NU2/ftQUPv5CcOp7R9Xho 14 | qrC1YsJNypL/BPA4kdheDBp4HLmmX05FyXEG3l2WCGqC1/ZS3Ye4k9WmnsSCWL85 15 | YImLXhBk9kwpYfPE2PMq5gqHVEKvRZGv+KzdHqt1LJKHUdgE/OtdFya8Af4N2BbB 16 | mRAPnC2jYIbApEVpM/kG9ANPZQteSGHsJSfWM2uZcbTpeNL55nVE1oXYGcXDbjUl 17 | AdYbgBiAk1mzmpGz2HMwLWhy5qK4d01dZvQOQZD+FjwqHNG2uHJQkP+qCeVOdRIC 18 | tdz2zF8ucjf3DA== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-root-noncritical-bc.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDAzCCAeugAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRp1FrGW//dRQ8KcaOM9 6 | iaD2VUJw4/H513JuxC/7aQ+7WPfBLQ4wLytoJH9E7eSTrAPjh7lilbYVvL2TFLCJ 7 | kiaGuOgvOpCN8uxC9ie/r+ui+YgexJwlMjwxfqx67WTXZJtC/GVS45ISfq6MkIwU 8 | tQWvTPXJnHl2epIXj4el6XIQWQL/koBgUlzbNrfdwZ1NpAm0jNDK44DjXDCwEy8U 9 | lTDMle1dAb4V80GlF5s87cauNp5sfghz05iqZiTSg4461v/EFH7TElAtuaLqa36P 10 | bo1OEIkeWyej8n60mFbwY2v4Frb9G9vQMY1gV7vLtetfJpgKKIj/iK/jbsrrhjsC 11 | RwIDAQABozswOTAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTAMBgNVHRME 12 | BTADAQH/MAsGA1UdDwQEAwIChDANBgkqhkiG9w0BAQsFAAOCAQEAOZonwl/4au3b 13 | /WKLy3OL0WEXbwhl6S4i9PxwtTmXSAO6GhPSLIfMrTQlLyay9L40aQ95dZvnfo2Z 14 | LOoMMpYOfo15YmtuyEAK7syh+UZTT78UNCqlkc0gSNUed6WucWHrAS90+TbFo/1/ 15 | mFGgYjao7GR755MOTFGlwa2eeYV+bEwGd9k1vTycQIOP4gLyBACP8KUMAlTEHnK2 16 | 0ZJ6GIpHVz5LalYCicxe0COKgKXER3l5YsnSLI5hvupeSld2jvNklCf94xMBOn/1 17 | TsangKU8zZc4GmbPlb+OqxvXNOnMhCQlz3zj762eMsLI599vUA9g1tHAauf05ZN+ 18 | jQ9agtlATw== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-leaf.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDGDCCAgCgAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNNkHwXjz4h6dG2B+g/c 6 | Nivs9ZVh0HNWKEjxHdSaZQ2CXVjnH91rEj7JTsEZLYsICvWjllYLDCfOmV8lfwOT 7 | FcPAUqysaA7SF1bH/8KqTAfaYrvrVQaMFxOxOfclmMBc2cN2GtcrLLZrnZuwZ40E 8 | 7Zchx5uCZ7njtKV2JL0cQIpjs+rnNVxqy158/Qf/AceC5c/5hI0WI9mw9uL7nzG4 9 | hSm/CWvd7fvQC6UV/Qt9BosteVIVB/3oYfstnwhr/8apmvq0z69iOf9/wUGPbKWp 10 | gawF0l+2Z8xMfvB25d49rXWItywteBlkPViE+ew2Ix7UAQZ55EPZzhhI7Pozou/g 11 | KQIDAQABo1AwTjAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTAMBgNVHRMB 12 | Af8EAjAAMAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzANBgkqhkiG 13 | 9w0BAQsFAAOCAQEATrcFg1ZlHp95eTpHV0O5HP+XAbTh8OE55LkT1VYqDBd64THG 14 | jXvEwfDRiexWR5wkLltGJuHxmJq9avlSXBxObwbamSJu1YYiOumv1Bnxnf+wgrNY 15 | dz6KTeD18xNNASuDRIBoKoj6OF0wJigUMYJThXGCdke9fivMqV3JWtLzPM39cuu4 16 | yV72EFBEp3/cVD5rIKK7Zfl4KW/ybpOMtvXCIT9GwnI/BgDuGjHimofwtncqzwRT 17 | cC8w1w9OIloadHOosOxRaT2PGcAWtNhL/clBj9Wwoc3hO5WCpwHGm50tbqGVGkxy 18 | uYljP7EnJ12llW2UIB2so60SCqOH5cNv8N60Jw== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-leaf-invalid-ku.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDFzCCAf+gAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNNkHwXjz4h6dG2B+g/c 6 | Nivs9ZVh0HNWKEjxHdSaZQ2CXVjnH91rEj7JTsEZLYsICvWjllYLDCfOmV8lfwOT 7 | FcPAUqysaA7SF1bH/8KqTAfaYrvrVQaMFxOxOfclmMBc2cN2GtcrLLZrnZuwZ40E 8 | 7Zchx5uCZ7njtKV2JL0cQIpjs+rnNVxqy158/Qf/AceC5c/5hI0WI9mw9uL7nzG4 9 | hSm/CWvd7fvQC6UV/Qt9BosteVIVB/3oYfstnwhr/8apmvq0z69iOf9/wUGPbKWp 10 | gawF0l+2Z8xMfvB25d49rXWItywteBlkPViE+ew2Ix7UAQZ55EPZzhhI7Pozou/g 11 | KQIDAQABo08wTTAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTAMBgNVHRMB 12 | Af8EAjAAMAoGA1UdDwQDAwEAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3 13 | DQEBCwUAA4IBAQADtd93gD08PW06DnaOYbvpBnEcNYEIS83N0vVLYFkm1UIbr5Ln 14 | QwORmsBMwOK04IhbnOMBt1Kb5CymFQkyhLHA2Y0KFnG6BYXKnVWgNYWb2yz6CShq 15 | KHZ6cu1vp/rADApv1IECZVKb5cZk7fCLk735SBuN8ybGdD3z2y6EINovq0c57GBN 16 | FY/4zPEHkBMlc+Ki/cv2ZwhQ+cAC/sU+vArjb5CWk8S3XfTaAsV30a41jZdmE30W 17 | yq4l67M4okuIouR6hxQal3TbsdfVVQUcAxmY1iFIXYmvEE48xHN4y4OjtBBTFM8h 18 | 99ePUcR4QrtPrvTmHIAgNupAZI4TUFamrimh 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-leaf-invalid-eku.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDGDCCAgCgAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNNkHwXjz4h6dG2B+g/c 6 | Nivs9ZVh0HNWKEjxHdSaZQ2CXVjnH91rEj7JTsEZLYsICvWjllYLDCfOmV8lfwOT 7 | FcPAUqysaA7SF1bH/8KqTAfaYrvrVQaMFxOxOfclmMBc2cN2GtcrLLZrnZuwZ40E 8 | 7Zchx5uCZ7njtKV2JL0cQIpjs+rnNVxqy158/Qf/AceC5c/5hI0WI9mw9uL7nzG4 9 | hSm/CWvd7fvQC6UV/Qt9BosteVIVB/3oYfstnwhr/8apmvq0z69iOf9/wUGPbKWp 10 | gawF0l+2Z8xMfvB25d49rXWItywteBlkPViE+ew2Ix7UAQZ55EPZzhhI7Pozou/g 11 | KQIDAQABo1AwTjAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTAMBgNVHRMB 12 | Af8EAjAAMAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG 13 | 9w0BAQsFAAOCAQEAAftcMInllQxRHV3t3jVJN68M6Bm7rOt761yx3p3DOlP4VG1d 14 | 9ZpNQkFLHxDWRFcChuWbEsVMXxGj7hvISKFgyrumlE10Yfn72SGMMRbHQrIGFHd/ 15 | bV6Xlr2IBqZzvCj1ZgoTb3o7U3I7xl2BP1+ScYhSigfCEh6b0U8TwCzaPE8Rfxik 16 | 8dHpW042MzAyiu0NbwH0iOVyj71Fx05/Hb/abgf3k1MV+0pAGC9cEkAyEORNXUda 17 | 1GcJ60hsfwMf7pUf+3f8OcEsznN7gVSWQn79L2nNN/Uh76Tel7JmoXwm5DgIF3Cg 18 | fLcj2PQo9MTXVuXkf9uJhgvkhmlElHfygYaYKA== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /test/assets/x509/bogus-intermediate-with-eku.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDHjCCAgagAwIBAgICApowDQYJKoZIhvcNAQELBQAwJTEjMCEGA1UEAwwac2ln 3 | c3RvcmUtcHl0aG9uLWJvZ3VzLWNlcnQwIBcNMjMwMTAxMDAwMDAwWhgPMzAyMjA1 4 | MDQwMDAwMDBaMCUxIzAhBgNVBAMMGnNpZ3N0b3JlLXB5dGhvbi1ib2d1cy1jZXJ0 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuNNkHwXjz4h6dG2B+g/c 6 | Nivs9ZVh0HNWKEjxHdSaZQ2CXVjnH91rEj7JTsEZLYsICvWjllYLDCfOmV8lfwOT 7 | FcPAUqysaA7SF1bH/8KqTAfaYrvrVQaMFxOxOfclmMBc2cN2GtcrLLZrnZuwZ40E 8 | 7Zchx5uCZ7njtKV2JL0cQIpjs+rnNVxqy158/Qf/AceC5c/5hI0WI9mw9uL7nzG4 9 | hSm/CWvd7fvQC6UV/Qt9BosteVIVB/3oYfstnwhr/8apmvq0z69iOf9/wUGPbKWp 10 | gawF0l+2Z8xMfvB25d49rXWItywteBlkPViE+ew2Ix7UAQZ55EPZzhhI7Pozou/g 11 | KQIDAQABo1YwVDAcBgNVHREEFTATghFib2d1cy5leGFtcGxlLmNvbTASBgNVHRMB 12 | Af8ECDAGAQH/AgEBMAsGA1UdDwQEAwIChDATBgNVHSUEDDAKBggrBgEFBQcDAzAN 13 | BgkqhkiG9w0BAQsFAAOCAQEAhLUXPPHJrVnd9G3OQ95uJSWdzTraRwht0wcGgQEX 14 | jpCXZ3j6ZCT8Q2NXuINujZ7UmZ4DUlqcCaf7GM+Ph2ofZ3u2MMMkkpgFwX4lCdPV 15 | 98OfiGvyEnj32HGQThrHmpPBNlxoqlat0YUfeiDtD4a+g29F1fdzxEhbHfi+E5Dq 16 | dX4JQmCFI6+z7YJa/OW42CUNzsyrINfdHnoVdeYSDXMobon4GVNS7Cc8ktiV/rPr 17 | rnetAKdeXTjlux5Bi6EASjqDqOY52TLJxltefTkCZZb3DKvAwKAlATpTdCxp2/Ey 18 | GTbtonT66FROHJer8cc+706dJKyfpcp/CJchlXNN1V8yhg== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /.github/actions/upload-coverage/action.yml: -------------------------------------------------------------------------------- 1 | # Derived from 2 | # Originally authored by the PyCA Cryptography maintainers, and licensed under 3 | # the terms of the BSD license: 4 | # 5 | 6 | name: Upload Coverage 7 | description: Upload coverage files 8 | 9 | runs: 10 | using: "composite" 11 | 12 | steps: 13 | # FIXME(jl): codecov has the option of including machine information in filename that would solve this unique naming 14 | # issue more completely. 15 | - run: | 16 | COVERAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid4())") 17 | echo "COVERAGE_UUID=${COVERAGE_UUID}" >> $GITHUB_OUTPUT 18 | if [ -f .coverage ]; then 19 | mv .coverage .coverage.${COVERAGE_UUID} 20 | fi 21 | id: coverage-uuid 22 | shell: bash 23 | - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 24 | with: 25 | name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }} 26 | include-hidden-files: 'true' 27 | path: | 28 | .coverage.* 29 | *.lcov 30 | if-no-files-found: ignore 31 | -------------------------------------------------------------------------------- /sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstore.dev/signing_config.v0.2.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", 3 | "caUrls": [ 4 | { 5 | "url": "https://fulcio.sigstore.dev", 6 | "majorApiVersion": 1, 7 | "validFor": { 8 | "start": "2022-04-13T20:06:15.000Z" 9 | }, 10 | "operator": "sigstore.dev" 11 | } 12 | ], 13 | "oidcUrls": [ 14 | { 15 | "url": "https://oauth2.sigstore.dev/auth", 16 | "majorApiVersion": 1, 17 | "validFor": { 18 | "start": "2022-04-13T20:06:15.000Z" 19 | }, 20 | "operator": "sigstore.dev" 21 | } 22 | ], 23 | "rekorTlogUrls": [ 24 | { 25 | "url": "https://rekor.sigstore.dev", 26 | "majorApiVersion": 1, 27 | "validFor": { 28 | "start": "2021-01-12T11:53:27.000Z" 29 | }, 30 | "operator": "sigstore.dev" 31 | } 32 | ], 33 | "tsaUrls": [ 34 | { 35 | "url": "https://timestamp.sigstore.dev/api/v1/timestamp", 36 | "majorApiVersion": 1, 37 | "validFor": { 38 | "start": "2025-07-04T00:00:00Z" 39 | }, 40 | "operator": "sigstore.dev" 41 | } 42 | ], 43 | "rekorTlogConfig": { 44 | "selector": "ANY" 45 | }, 46 | "tsaConfig": { 47 | "selector": "ANY" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/assets/staging-tuf/targets/0f395087486ba318321eda478d847962b1dd89846c7dc6e95752a6b110669393.signing_config.v0.2.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", 3 | "caUrls": [ 4 | { 5 | "url": "https://fulcio.sigstage.dev", 6 | "majorApiVersion": 1, 7 | "validFor": { 8 | "start": "2022-04-14T21:38:40Z" 9 | }, 10 | "operator": "sigstore.dev" 11 | } 12 | ], 13 | "oidcUrls": [ 14 | { 15 | "url": "https://oauth2.sigstage.dev/auth", 16 | "majorApiVersion": 1, 17 | "validFor": { 18 | "start": "2025-04-16T00:00:00Z" 19 | }, 20 | "operator": "sigstore.dev" 21 | } 22 | ], 23 | "rekorTlogUrls": [ 24 | { 25 | "url": "https://rekor.sigstage.dev", 26 | "majorApiVersion": 1, 27 | "validFor": { 28 | "start": "2021-01-12T11:53:27Z" 29 | }, 30 | "operator": "sigstore.dev" 31 | } 32 | ], 33 | "tsaUrls": [ 34 | { 35 | "url": "https://timestamp.sigstage.dev/api/v1/timestamp", 36 | "majorApiVersion": 1, 37 | "validFor": { 38 | "start": "2025-04-09T00:00:00Z" 39 | }, 40 | "operator": "sigstore.dev" 41 | } 42 | ], 43 | "rekorTlogConfig": { 44 | "selector": "ANY" 45 | }, 46 | "tsaConfig": { 47 | "selector": "ANY" 48 | } 49 | } -------------------------------------------------------------------------------- /sigstore/_store/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | An empty module, used to assist Python's resource machinery in embedding 17 | assets. 18 | """ 19 | 20 | 21 | # NOTE: This is arguably incorrect, since _store only contains non-Python files. 22 | # However, due to how `importlib.resources` is designed, only top-level resources 23 | # inside of packages or modules can be accessed, so this directory needs to be a 24 | # module in order for us to programmatically access the keys and root certs in it. 25 | # 26 | # Why do we bother with `importlib` at all? Because we might be installed as a 27 | # ZIP file or an Egg, which in turn means that our resource files don't actually 28 | # exist separately on disk. `importlib` is the only reliable way to access them. 29 | -------------------------------------------------------------------------------- /test/assets/integration/attest/slsa_predicate_v1_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildDefinition": { 3 | "buildType": "https://actions.github.io/buildtypes/workflow/v1", 4 | "externalParameters": { 5 | "workflow": { 6 | "ref": "refs/tags/1.21.0", 7 | "repository": "https://github.com/octo-org/octo-repo", 8 | "path": ".github/workflows/ci.yaml" 9 | } 10 | }, 11 | "internalParameters": { 12 | "github": { 13 | "event_name": "push", 14 | "repository_id": "000000000", 15 | "repository_owner_id": "0000000", 16 | "runner_environment": "github-hosted" 17 | } 18 | }, 19 | "resolvedDependencies": [ 20 | { 21 | "uri": "git+https://github.com/octo-org/octo-repo@refs/tags/1.21.0", 22 | "digest": { 23 | "gitCommit": "1ac93ce21ee526b36fd154b9058d97dfaa424c50" 24 | } 25 | } 26 | ] 27 | }, 28 | "runDetails": { 29 | "builder": { 30 | "id": "https://github.com/octo-org/octo-repo/.github/workflows/docker.yaml@refs/heads/development" 31 | }, 32 | "metadata": { 33 | "invocationId": "https://github.com/octo-org/octo-repo/actions/runs/10313983218/attempts/2" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /test/unit/test_store.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | 17 | import pytest 18 | 19 | from sigstore._utils import read_embedded 20 | 21 | 22 | @pytest.mark.parametrize( 23 | "env", 24 | [ 25 | "https://tuf-repo-cdn.sigstore.dev", 26 | "https://tuf-repo-cdn.sigstage.dev", 27 | ], 28 | ) 29 | def test_store_reads_root_json(env): 30 | root_json = read_embedded("root.json", env) 31 | assert json.loads(root_json) 32 | 33 | 34 | @pytest.mark.parametrize( 35 | "env", 36 | [ 37 | "https://tuf-repo-cdn.sigstore.dev", 38 | "https://tuf-repo-cdn.sigstage.dev", 39 | ], 40 | ) 41 | def test_store_reads_targets_json(env): 42 | trusted_root_json = read_embedded("trusted_root.json", env) 43 | assert json.loads(trusted_root_json) 44 | -------------------------------------------------------------------------------- /test/assets/bundle_no_log_entry.txt.sigstore: -------------------------------------------------------------------------------- 1 | {"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", "verificationMaterial": {"x509CertificateChain": {"certificates": [{"rawBytes": "MIICxDCCAkqgAwIBAgIUERCmd8PPVzGcAn7smRhiMQ1rjgAwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjMwMTMxMDA1NzM1WhcNMjMwMTMxMDEwNzM1WjAAMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAErDdWhJLHi4E8pL5I6PnYm3O50xBNghdCsXj/zPkrKCRmkyax+WoZq+UdbuuNgER4rIRimdWvFGP/CpQWA8jcYFXeFTWbDDhBxYFPs9KWjq/a6BF7iYaHwFQl+o0Oo9IOo4IBTDCCAUgwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBScV4LgdhmkHru8fcV23gZwzC+JjjAfBgNVHSMEGDAWgBTf0+nPViQRlvmo2OkoVaLGLhhkPzAqBgNVHREBAf8EIDAegRxhbGV4LmNhbWVyb25AdHJhaWxvZmJpdHMuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABhgVUSb8AAAQDAEYwRAIgUagdrzc6Zr0XHzBfkPPeB+kSln9BChTOS3XLlwy1SGQCICyjI9i0PujwHtSC5AsFcrTGiBc0KeopXmYqXRN7A2vfMAoGCCqGSM49BAMDA2gAMGUCMQCagTgf3TMQpXMSMc3jREF+E8j1XngvBfgBNzcd1bbBfSUl2fyv7HMETgBzLTht/bQCMGeSULPeevErK9Jb7jRGbMBmSTNXLIPebx1zAijj8tTBW91z7v9Bjb/AmytwpALVSA=="}]}, "tlogEntries": []}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "m0afyXYei5Mwc7YnJfvJi1ScMxQZne1xzIsobvLG+5s="}, "signature": "MGUCMHKDIXsY9G/pbwFY23mDx1aXSZVpasnQKES5pFWz1NxayS0+dt2edIdDPaLrSuBGuAIxAP9MADAHRBp2dBqpvo8O8u7VLOMU8JMt1eSMjwMzJPRNuGkO8dgpX+Yyy1452fitKA=="}} 2 | -------------------------------------------------------------------------------- /test/unit/internal/oidc/test_issuer.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import pytest 16 | 17 | from sigstore.oidc import IdentityError, Issuer, IssuerError 18 | 19 | 20 | @pytest.mark.online 21 | def test_fail_init_url(): 22 | with pytest.raises(IssuerError): 23 | Issuer("https://google.com") 24 | 25 | 26 | @pytest.mark.online 27 | def test_init_url(): 28 | Issuer("https://accounts.google.com") 29 | 30 | 31 | @pytest.mark.online 32 | def test_get_identity_token_bad_code(monkeypatch): 33 | # Send token request to oauth2.sigstage.dev but provide a bogus authorization code 34 | monkeypatch.setattr("builtins.input", lambda _: "hunter2") 35 | with pytest.raises(IdentityError, match=r"^Token request failed with .+$"): 36 | Issuer("https://oauth2.sigstage.dev/auth").identity_token(force_oob=True) 37 | -------------------------------------------------------------------------------- /test/assets/c.txt.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDwTCCA0igAwIBAgIUdXPCI40ren/SEkqxmHcCc6lIV7MwCgYIKoZIzj0EAwMw 3 | NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl 4 | cm1lZGlhdGUwHhcNMjIxMTAzMDc0NTM1WhcNMjIxMTAzMDc1NTM1WjAAMHYwEAYH 5 | KoZIzj0CAQYFK4EEACIDYgAELVUlqi4FjTw4mHzuyE8sOsK6mVvzOTv0EX7ot+aZ 6 | ftaf+ato9xuemqA69qARscFPwG15It1F9PVdKUOeJkTPjZC+lRHNAIeamJpilskz 7 | xqR6fisI7q72zHY8OhgMnSSHo4ICSjCCAkYwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud 8 | JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBREb7Dfm1g8gILV3K9rT9WSF7GnzzAf 9 | BgNVHSMEGDAWgBRxhjCmFHxib/n31vQFGn9f/+tvrDBmBgNVHREBAf8EXDBahlho 10 | dHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUvc2lnc3RvcmUtcHl0aG9uLy5naXRo 11 | dWIvd29ya2Zsb3dzL2NpLnltbEByZWZzL3B1bGwvMjg4L21lcmdlMDkGCisGAQQB 12 | g78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5j 13 | b20wGgYKKwYBBAGDvzABAgQMcHVsbF9yZXF1ZXN0MDYGCisGAQQBg78wAQMEKDNi 14 | ZTcyMzU2ZWY0NTE3YmI4ZTUwZjI5Njg4N2Y5YzU3ODZmOTAzMTYwEAYKKwYBBAGD 15 | vzABBAQCQ0kwJgYKKwYBBAGDvzABBQQYc2lnc3RvcmUvc2lnc3RvcmUtcHl0aG9u 16 | MCEGCisGAQQBg78wAQYEE3JlZnMvcHVsbC8yODgvbWVyZ2UwgYoGCisGAQQB1nkC 17 | BAIEfAR6AHgAdgArMLzcaIjJ4uHYJiledB9IOTGWAvKcM8teQ0D+sqyGegAAAYQ8 18 | c9igAAAEAwBHMEUCIQCn/JSbLxs0ds3Nycn0yINUQABeltbAmcYDFEn/sdm50gIg 19 | fm4lKdhXJoWHJRC8IS7MxYI3yR/oNzX6dntuqpHJ24YwCgYIKoZIzj0EAwMDZwAw 20 | ZAIwE0F3B/HgHn+ov6axOY0TMR/hv2DUVlC3qkGBQEEMtglf5qtT+a9g7aQ5g4pG 21 | of+JAjB+qUeUdSAyGPDK+5Ti6aROy0oAbwl+B3bH7QmmZ/i5M++PXIW4l4lcuAmA 22 | UkjTgLw= 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /.github/workflows/requirements.yml: -------------------------------------------------------------------------------- 1 | name: Test requirements.txt 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_call: 8 | inputs: 9 | ref: 10 | description: The branch, tag, or revision to test. 11 | type: string 12 | required: true 13 | pull_request: 14 | schedule: 15 | - cron: "0 12 * * *" 16 | 17 | permissions: {} 18 | 19 | jobs: 20 | test_requirements: 21 | name: requirements.txt / ${{ matrix.python_version }} 22 | runs-on: ubuntu-latest 23 | 24 | env: 25 | SIGSTORE_REF: ${{ inputs.ref }} 26 | strategy: 27 | matrix: 28 | python_version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] 29 | 30 | steps: 31 | - name: Populate reference from context 32 | if: ${{ env.SIGSTORE_REF == '' }} 33 | run: | 34 | echo "SIGSTORE_REF=${GITHUB_REF}" >> "${GITHUB_ENV}" 35 | 36 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 37 | with: 38 | ref: ${{ env.SIGSTORE_REF }} 39 | persist-credentials: false 40 | 41 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 42 | name: Install Python ${{ matrix.python_version }} 43 | with: 44 | python-version: ${{ matrix.python_version }} 45 | allow-prereleases: true 46 | cache: "pip" 47 | 48 | - name: Run test install 49 | run: python -m pip install -r install/requirements.txt 50 | -------------------------------------------------------------------------------- /test/unit/test_hashes.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import hashlib 15 | 16 | import pytest 17 | from sigstore_models.common.v1 import HashAlgorithm 18 | 19 | from sigstore.hashes import Hashed 20 | 21 | 22 | class TestHashes: 23 | @pytest.mark.parametrize( 24 | ("algorithm", "digest"), 25 | [ 26 | (HashAlgorithm.SHA2_256, hashlib.sha256(b"").hexdigest()), 27 | (HashAlgorithm.SHA2_384, hashlib.sha384(b"").hexdigest()), 28 | (HashAlgorithm.SHA2_512, hashlib.sha512(b"").hexdigest()), 29 | (HashAlgorithm.SHA3_256, hashlib.sha3_256(b"").hexdigest()), 30 | (HashAlgorithm.SHA3_384, hashlib.sha3_384(b"").hexdigest()), 31 | ], 32 | ) 33 | def test_hashed_repr(self, algorithm, digest): 34 | hashed = Hashed(algorithm=algorithm, digest=bytes.fromhex(digest)) 35 | assert str(hashed) == f"{algorithm.name}:{digest}" 36 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Home 2 | 3 | ## Introduction 4 | 5 | `sigstore` is a Python tool for generating and verifying [Sigstore] signatures. 6 | You can use it to sign and verify Python package distributions, or anything 7 | else! 8 | 9 | ## Features 10 | 11 | * Support for keyless signature generation and verification with [Sigstore](https://www.sigstore.dev/) 12 | * Support for signing with ["ambient" OpenID Connect identities](./signing.md#signing-with-ambient-credentials) 13 | * A comprehensive [CLI](#using-sigstore) and corresponding 14 | [importable Python API](./api/index.md) 15 | 16 | ## Installing `sigstore` 17 | 18 | ```console 19 | python -m pip install sigstore 20 | ``` 21 | 22 | See [installation](./installation.md) for more detailed installation instructions or options. 23 | 24 | ## Using `sigstore` 25 | 26 | You can run `sigstore` as a standalone program, or via `python -m`: 27 | 28 | ```console 29 | sigstore --help 30 | python -m sigstore --help 31 | ``` 32 | 33 | - Use `sigstore` to [sign](./signing.md) 34 | - Use `sigstore` to [verify](./verify.md) 35 | 36 | ## SLSA Provenance 37 | 38 | This project emits a [SLSA] provenance on its release! This enables you to verify the integrity 39 | of the downloaded artifacts and ensured that the binary's code really comes from this source code. 40 | 41 | To do so, please follow the instructions [here](https://github.com/slsa-framework/slsa-github-generator#verification-of-provenance). 42 | 43 | [SLSA]: https://slsa.dev/ 44 | [Sigstore]: https://www.sigstore.dev/ -------------------------------------------------------------------------------- /test/assets/signing_config/signingconfig-only-v1-rekor.v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", 3 | "caUrls": [ 4 | { 5 | "url": "https://fulcio.example.com", 6 | "majorApiVersion": 1, 7 | "validFor": { 8 | "start": "2023-04-14T21:38:40Z" 9 | }, 10 | "operator": "example.com" 11 | }, 12 | { 13 | "url": "https://fulcio-old.example.com", 14 | "majorApiVersion": 1, 15 | "validFor": { 16 | "start": "2022-04-14T21:38:40Z", 17 | "end": "2023-04-14T21:38:40Z" 18 | }, 19 | "operator": "example.com" 20 | } 21 | ], 22 | "oidcUrls": [ 23 | { 24 | "url": "https://oauth2.example.com/auth", 25 | "majorApiVersion": 1, 26 | "validFor": { 27 | "start": "2025-04-16T00:00:00Z" 28 | }, 29 | "operator": "example.com" 30 | } 31 | ], 32 | "rekorTlogUrls": [ 33 | { 34 | "url": "https://rekor.example.com", 35 | "majorApiVersion": 1, 36 | "validFor": { 37 | "start": "2021-01-12T11:53:27Z" 38 | }, 39 | "operator": "example.com" 40 | } 41 | ], 42 | "tsaUrls": [ 43 | { 44 | "url": "https://timestamp.example.com/api/v1/timestamp", 45 | "majorApiVersion": 1, 46 | "validFor": { 47 | "start": "2025-04-09T00:00:00Z" 48 | }, 49 | "operator": "example.com" 50 | } 51 | ], 52 | "rekorTlogConfig": { 53 | "selector": "ANY" 54 | }, 55 | "tsaConfig": { 56 | "selector": "ANY" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sigstore/verify/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | API for verifying artifact signatures. 17 | 18 | Example: 19 | ```python 20 | import base64 21 | from pathlib import Path 22 | 23 | from sigstore.models import Bundle 24 | from sigstore.verify import Verifier 25 | from sigstore.verify.policy import Identity 26 | 27 | # The input to verify 28 | input_ = Path("foo.txt").read_bytes() 29 | 30 | # The bundle to verify with 31 | bundle = Bundle.from_json(Path("foo.txt.sigstore.json").read_bytes()) 32 | 33 | verifier = Verifier.production() 34 | result = verifier.verify( 35 | input_, 36 | bundle, 37 | Identity( 38 | identity="foo@bar.com", 39 | issuer="https://accounts.google.com", 40 | ), 41 | ) 42 | print(result) 43 | ``` 44 | """ 45 | 46 | from sigstore.verify import policy, verifier 47 | from sigstore.verify.verifier import Verifier 48 | 49 | __all__ = [ 50 | "Verifier", 51 | "policy", 52 | "verifier", 53 | ] 54 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## With `pip` 4 | 5 | `sigstore` requires Python 3.9 or newer, and can be installed directly via `pip`: 6 | 7 | ```console 8 | python -m pip install sigstore 9 | ``` 10 | 11 | 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: 12 | 13 | ```console 14 | python -m pip install -r https://raw.githubusercontent.com/sigstore/sigstore-python/main/install/requirements.txt 15 | ``` 16 | 17 | This installs the requirements file located [here](https://github.com/sigstore/sigstore-python/blob/main/install/requirements.txt), which is kept up-to-date. 18 | 19 | ## With `uv` 20 | 21 | !!! warning 22 | 23 | `sigstore` depends on `betterproto` pre-releases versions, which are by default not resolved by `uv`. 24 | 25 | ```console 26 | uv pip install --prerelease=allow sigstore 27 | ``` 28 | 29 | `sigstore` can also be used as tool: 30 | 31 | ```console 32 | uvx --prerelease=allow sigstore --help 33 | ``` 34 | 35 | ## GitHub Actions 36 | 37 | `sigstore-python` has [an official GitHub Action](https://github.com/sigstore/gh-action-sigstore-python)! 38 | 39 | You can install it from the [GitHub Marketplace](https://github.com/marketplace/actions/gh-action-sigstore-python), or 40 | add it to your CI manually: 41 | 42 | ```yaml 43 | jobs: 44 | sigstore-python: 45 | steps: 46 | - uses: sigstore/gh-action-sigstore-python@v3.0.0 47 | with: 48 | inputs: foo.txt 49 | ``` 50 | 51 | See the [action documentation](https://github.com/sigstore/gh-action-sigstore-python/blob/main/README.md) for more details and usage examples. -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 15 | with: 16 | persist-credentials: false 17 | 18 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 19 | with: 20 | python-version: "3.x" 21 | cache: "pip" 22 | cache-dependency-path: pyproject.toml 23 | 24 | - name: setup 25 | run: | 26 | make dev SIGSTORE_EXTRA=doc 27 | 28 | - name: build docs 29 | run: | 30 | make doc 31 | 32 | - name: upload docs artifact 33 | uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 34 | with: 35 | path: ./html/ 36 | 37 | # This is copied from the official `pdoc` example: 38 | # https://github.com/mitmproxy/pdoc/blob/main/.github/workflows/docs.yml 39 | # 40 | # Deploy the artifact to GitHub pages. 41 | # This is a separate job so that only actions/deploy-pages has the necessary permissions. 42 | deploy: 43 | needs: build 44 | if: github.repository == 'sigstore/sigstore-python' 45 | runs-on: ubuntu-latest 46 | permissions: 47 | # NOTE: Needed to push to the repository. 48 | pages: write 49 | id-token: write 50 | environment: 51 | name: github-pages 52 | url: ${{ steps.deployment.outputs.page_url }} 53 | steps: 54 | - id: deployment 55 | uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 56 | -------------------------------------------------------------------------------- /test/assets/a.txt.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEfjCCBAWgAwIBAgIUf/SsSCcPgO7o0yKoONG3Vz/RdzIwCgYIKoZIzj0EAwMw 3 | NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl 4 | cm1lZGlhdGUwHhcNMjIwNzI4MTcwNDIxWhcNMjIwNzI4MTcxNDIxWjAAMHYwEAYH 5 | KoZIzj0CAQYFK4EEACIDYgAEiWxhv1x6hf4JJjbH8RSPxMs3DW4tLlQpbBOuVLxQ 6 | sUcxsA2mIW5N3O91Vikum0xT5NFsve8bH1vqOCuSdJItW0uesGJtJRB0aCQUnweC 7 | AWgTFcgObyYZdqfOOgYBp59Qo4IDBzCCAwMwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud 8 | JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBQdR6DA2mG5awJ5IeGQ/KwqhC8teTAf 9 | BgNVHSMEGDAWgBRxhjCmFHxib/n31vQFGn9f/+tvrDAjBgNVHREBAf8EGTAXgRV3 10 | aWxsaWFtQHlvc3Nhcmlhbi5uZXQwLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRo 11 | dWIuY29tL2xvZ2luL29hdXRoMIICRwYKKwYBBAHWeQIEAgSCAjcEggIzAjECLwAb 12 | fBQqTpkrp98eH8V0JFQTbBV6TLMcQQilIbixq+e/TAAAAYJFxDEtAAAEAQIAZtah 13 | 4QQqmJIzhkoDb79Q1NOUMreaes/FxxkHAqPrVbLA0aSnA3NEwScd7P8PgwbRiFGz 14 | LILsACKHBlTmlzVB8s6qTtDrtjP8JHV7NSbxa84QSL0XcHUhMMVSGNvi5gr5dSom 15 | TJsrfxJTX1+uLRDvxhqTdGoPIl9/J9ekTMQ/C8WNlSwAJbQDsAFG4PZhu3M6haH3 16 | cH7l0VCDbewq42axDNlwbY7/jw9bvHrkU+PjSmRBOnyUuGJocHGDywqYA6vQLoO3 17 | /UcSWFQ+/QFHxGVC4f6SrM2c+GCPBTLPUVUAJEwi0U6OnB7d6ObLsMEy0vY56GPJ 18 | TGSJtUhxMFp/zDhMdBeviRDX1tLpT5rjP34F1Iee3KMZh7tEY45NlcIhdCJ8bAbt 19 | j+X7YVI15jrpX3XVBNilTgvAZy0/c4ZBjRcr44GwbHQvYE8N/9WEIf6d8I+9/d6S 20 | mRZJVNEcHEA+bpWGT/S7nzgxrtusFejR5JxxPIes8AVsFKNkCq0BkxiwRWIrIEEJ 21 | 7TzdVZdZFc8OWthKlkVuylrbg2BJfARx7CMnywuj/gx23lf9B9IkgDRptPwJQCJm 22 | +AtNujk0BUTjh7VazWK+FBUqHDciZQEGDEksy24zMW+BxZwLUvQXpXUnKT9tzMLm 23 | YI7x0MeDKSd+lhi36Z5EJHyPGB0KPZ8KNRV49WIwCgYIKoZIzj0EAwMDZwAwZAIw 24 | a/88UepYCuhkuUtwwL4WGsvEO4fN+07Togp9ksspYhpDvgEZSrn/oEwx7cNRbl7e 25 | AjBk/QR+Nif+U8CfM9bqs9SEww+KEykj+uyAud/C/qt97HsI7j7I2ECuy3SJL6vW 26 | l7I= 27 | -----END CERTIFICATE----- 28 | 29 | -------------------------------------------------------------------------------- /test/assets/b.txt.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEfzCCBAWgAwIBAgIUXTEFeZc82/onXSK7L3gOx5ZryQYwCgYIKoZIzj0EAwMw 3 | NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl 4 | cm1lZGlhdGUwHhcNMjIwNzI4MTcwNDIyWhcNMjIwNzI4MTcxNDIyWjAAMHYwEAYH 5 | KoZIzj0CAQYFK4EEACIDYgAE7fIULI0jGzRxDm/UhtOK2aYvsBkbc/J91AwXWRtu 6 | KEy1rPdf7MhP/IFyqQcvc9mBf2/tjBZEdgDDAZ7StmV9BHE3MgsYhkImsH4938mY 7 | EYHf6hzEsKkdiLjtHOcvAKX6o4IDBzCCAwMwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud 8 | JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBQ7bzu/2AekVQ/lsYw3MCxIw+WXzDAf 9 | BgNVHSMEGDAWgBRxhjCmFHxib/n31vQFGn9f/+tvrDAjBgNVHREBAf8EGTAXgRV3 10 | aWxsaWFtQHlvc3Nhcmlhbi5uZXQwLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRo 11 | dWIuY29tL2xvZ2luL29hdXRoMIICRwYKKwYBBAHWeQIEAgSCAjcEggIzAjECLwAb 12 | fBQqTpkrp98eH8V0JFQTbBV6TLMcQQilIbixq+e/TAAAAYJFxDShAAAEAQIAsI37 13 | okHyutfCBZKc0IAP8SgutX1wtA8cKfieySdpkWXnCqeiMjBpseEMaYIJPdpa3dwd 14 | Al1uLA5R24a9Oi6BDPJGFyZSrvRRtTz0oHKLrjVx0eDrL89KBb0Y554dkQH+XYS9 15 | PgZqOAUq1cubDJ1VTH8EEnhU7BSx4i8/Z1jZx1DFNIde+H+esWRmuOQmlk7ViT5W 16 | bir99BdvzfeBHtXqlSVygVfJgiJpJDwl85hBZ7ayKvuArN5bl6mISvfvt/W1fH+B 17 | syn45LcjMqfRO+AwMP3Payv5/KbLV2WL9rfCkqCaR5Q94PHlh8KOAORVilay67kw 18 | g7+FvWGEHzYnk5iEV5/0gjtYwG1EnxsBMxNRtVVOhCLcQs3nfntYz794yvhd1S94 19 | zH0QVo3przPEU40hFjU+cOdNEnVJ0DkspoKVtM9fOrJbL9XXqVLCnUKQBRfwizNZ 20 | 8GFEMi9VJ1lX1rybnP/SWWwiWsRTufWZ8ogpESUvSk3L/+XwFjuEy2gaWczODLAu 21 | Plj6f4YnHqAQxshf6Km7gUTodRw8yjXtYPPH1yxSZNJ1ivOUQdCS+I218SHloqsF 22 | DtHraHb68cRKSQXIsG8a1GGmlZ6ZOTpjFIq62qcRbrPPzTk1du5b0axEIaPHKPOs 23 | BwwKHrnZuVXE4yfwYXtciFgp8fsD8b8vBLJYH00wCgYIKoZIzj0EAwMDaAAwZQIx 24 | APr8q3OZd7zY78xfVpbusI1Vg7tvwOUYFrAHWMc0n5QIrhwfUm7/ZTPKyzVB2An9 25 | 2AIwS/qcggfuupePvsM7pHXXiDNexzTZBI9LP7evpXP32CM5cqebAn+kQFtxJ4jd 26 | afT2 27 | -----END CERTIFICATE----- 28 | 29 | -------------------------------------------------------------------------------- /test/assets/bad.txt.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEfjCCBAWgAwIBAgIUf/SsSCcPgO7o0yKoONG3Vz/RdzIwCgYIKoZIzj0EAwMw 3 | NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl 4 | cm1lZGlhdGUwHhcNMjIwNzI4MTcwNDIxWhcNMjIwNzI4MTcxNDIxWjAAMHYwEAYH 5 | KoZIzj0CAQYFK4EEACIDYgAEiWxhv1x6hf4JJjbH8RSPxMs3DW4tLlQpbBOuVLxQ 6 | sUcxsA2mIW5N3O91Vikum0xT5NFsve8bH1vqOCuSdJItW0uesGJtJRB0aCQUnweC 7 | AWgTFcgObyYZdqfOOgYBp59Qo4IDBzCCAwMwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud 8 | JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBQdR6DA2mG5awJ5IeGQ/KwqhC8teTAf 9 | BgNVHSMEGDAWgBRxhjCmFHxib/n31vQFGn9f/+tvrDAjBgNVHREBAf8EGTAXgRV3 10 | aWxsaWFtQHlvc3Nhcmlhbi5uZXQwLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRo 11 | dWIuY29tL2xvZ2luL29hdXRoMIICRwYKKwYBBAHWeQIEAgSCAjcEggIzAjECLwAb 12 | fBQqTpkrp98eH8V0JFQTbBV6TLMcQQilIbixq+e/TAAAAYJFxDEtAAAEAQIAZtah 13 | 4QQqmJIzhkoDb79Q1NOUMreaes/FxxkHAqPrVbLA0aSnA3NEwScd7P8PgwbRiFGz 14 | LILsACKHBlTmlzVB8s6qTtDrtjP8JHV7NSbxa84QSL0XcHUhMMVSGNvi5gr5dSom 15 | TJsrfxJTX1+uLRDvxhqTdGoPIl9/J9ekTMQ/C8WNlSwAJbQDsAFG4PZhu3M6haH3 16 | cH7l0VCDbewq42axDNlwbY7/jw9bvHrkU+PjSmRBOnyUuGJocHGDywqYA6vQLoO3 17 | /UcSWFQ+/QFHxGVC4f6SrM2c+GCPBTLPUVUAJEwi0U6OnB7d6ObLsMEy0vY56GPJ 18 | TGSJtUhxMFp/zDhMdBeviRDX1tLpT5rjP34F1Iee3KMZh7tEY45NlcIhdCJ8bAbt 19 | j+X7YVI15jrpX3XVBNilTgvAZy0/c4ZBjRcr44GwbHQvYE8N/9WEIf6d8I+9/d6S 20 | mRZJVNEcHEA+bpWGT/S7nzgxrtusFejR5JxxPIes8AVsFKNkCq0BkxiwRWIrIEEJ 21 | 7TzdVZdZFc8OWthKlkVuylrbg2BJfARx7CMnywuj/gx23lf9B9IkgDRptPwJQCJm 22 | +AtNujk0BUTjh7VazWK+FBUqHDciZQEGDEksy24zMW+BxZwLUvQXpXUnKT9tzMLm 23 | YI7x0MeDKSd+lhi36Z5EJHyPGB0KPZ8KNRV49WIwCgYIKoZIzj0EAwMDZwAwZAIw 24 | a/88UepYCuhkuUtwwL4WGsvEO4fN+07Togp9ksspYhpDvgEZSrn/oEwx7cNRbl7e 25 | AjBk/QR+Nif+U8CfM9bqs9SEww+KEykj+uyAud/C/qt97HsI7j7I2ECuy3SJL6vW 26 | l7I= 27 | -----END CERTIFICATE----- 28 | 29 | -------------------------------------------------------------------------------- /test/assets/offline-rekor.txt.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEfzCCBAWgAwIBAgIULV3qds9Z1Ar1hOpW+/9ULyl1LgwwCgYIKoZIzj0EAwMw 3 | NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl 4 | cm1lZGlhdGUwHhcNMjIxMDEzMTk0ODMyWhcNMjIxMDEzMTk1ODMyWjAAMHYwEAYH 5 | KoZIzj0CAQYFK4EEACIDYgAEff7HebXAkCGKe8/QMmJ3OCjSOhsR+3NGYn1FKm7R 6 | 672BvHek5Zza2D5bFDEwBEtM3E9hM2//OwN2EU8dK6BAaVGtlEHZvAzCcWCUwWFj 7 | 8QTp9eQDt3Hrmygyp9qB6mOro4IDBzCCAwMwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud 8 | JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBTxc4F9+1z0h4kG410C/f0NxerAhzAf 9 | BgNVHSMEGDAWgBRxhjCmFHxib/n31vQFGn9f/+tvrDAjBgNVHREBAf8EGTAXgRV3 10 | aWxsaWFtQHlvc3Nhcmlhbi5uZXQwLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRo 11 | dWIuY29tL2xvZ2luL29hdXRoMIICRwYKKwYBBAHWeQIEAgSCAjcEggIzAjECLwAb 12 | fBQqTpkrp98eH8V0JFQTbBV6TLMcQQilIbixq+e/TAAAAYPS5C1VAAAEAQIAO9w0 13 | 5StsDZsK27vfjH1nmzhB8dAcifwsCduL7XS079Jz9hUfcjqKMZOQbL5dlulkteqm 14 | oQPO272u/AxLca7gKDD47gBx0/O9yk6TapGQuqsNrn2JPpfMdvzwJvXQJ/7rL61l 15 | d6zs/3q0UQQu4PqVIdDPhNF9chUMGiau5UKACsManYKtmTi86+wcCT89Etb9SqSj 16 | +QiTlTzQqIi9cKXbUhOTzpiKALjwNvsvB5pQ6U9WN+8OVoQPr919js+O0AeVf8R6 17 | YKhVumMBquvV756FocC/lxThYITbmUH91bY/nQPYy4tAhuums6Cc+9vzYaeQw6y0 18 | dUfum1XM8agJsihYzuaL/U0S2n8HrfsLjLU6a06IPMEx7WVGSEZxTH78PurXDKB8 19 | sLKG2X2wIQpiyglk6CU0zgw4WXb+qON7VFIL4wOe5tdrSHwRdV6xqGOdeSf+TyH4 20 | 7GRPa0raT2pVWAZf6liJPD4vqH2jJWE3WbhOWkfYM9uqoE1fQSQr7GN4+NJzmsdN 21 | scxsD2tiExlXNIMIvpXqTrbWSxDC/reMPjnbpNUHBCwqSyaL7HyW0oB3e6JJOuWl 22 | yFDJIimX2tpLWyMV4tLCMd/p3EZsE5oCs1cGOiDQhAVUTwJOtxH6jk+vhFDJSH6C 23 | gkyIyu8vQAwVGatCdElYKK6R0kt8/yA9szrsFMwwCgYIKoZIzj0EAwMDaAAwZQIx 24 | AJDWJS41EwSk8LLZyqBjK2rG77+ceBjD2Vx6h1oGHVGVBwsiq4CgPsEyPJtVW+1Q 25 | 8wIwZ/gMuXAzIllTHJ4HBFTkODEPUcVYctRDkF75V2lvtS4eO0JFc+agbn/Ah99V 26 | aprh 27 | -----END CERTIFICATE----- 28 | 29 | -------------------------------------------------------------------------------- /test/assets/signing_config/signingconfig.v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", 3 | "caUrls": [ 4 | { 5 | "url": "https://fulcio.example.com", 6 | "majorApiVersion": 1, 7 | "validFor": { 8 | "start": "2023-04-14T21:38:40Z" 9 | }, 10 | "operator": "example.com" 11 | }, 12 | { 13 | "url": "https://fulcio-old.example.com", 14 | "majorApiVersion": 1, 15 | "validFor": { 16 | "start": "2022-04-14T21:38:40Z", 17 | "end": "2023-04-14T21:38:40Z" 18 | }, 19 | "operator": "example.com" 20 | } 21 | ], 22 | "oidcUrls": [ 23 | { 24 | "url": "https://oauth2.example.com/auth", 25 | "majorApiVersion": 1, 26 | "validFor": { 27 | "start": "2025-04-16T00:00:00Z" 28 | }, 29 | "operator": "example.com" 30 | } 31 | ], 32 | "rekorTlogUrls": [ 33 | { 34 | "url": "https://rekor.example.com", 35 | "majorApiVersion": 1, 36 | "validFor": { 37 | "start": "2021-01-12T11:53:27Z" 38 | }, 39 | "operator": "example.com" 40 | }, 41 | { 42 | "url": "https://rekor-v2.example.com", 43 | "majorApiVersion": 2, 44 | "validFor": { 45 | "start": "2021-01-12T11:53:27Z" 46 | }, 47 | "operator": "example.com" 48 | } 49 | ], 50 | "tsaUrls": [ 51 | { 52 | "url": "https://timestamp.example.com/api/v1/timestamp", 53 | "majorApiVersion": 1, 54 | "validFor": { 55 | "start": "2025-04-09T00:00:00Z" 56 | }, 57 | "operator": "example.com" 58 | } 59 | ], 60 | "rekorTlogConfig": { 61 | "selector": "ANY" 62 | }, 63 | "tsaConfig": { 64 | "selector": "ANY" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/assets/trusted_root/certificate_authority.missingroot.json: -------------------------------------------------------------------------------- 1 | { 2 | "subject": { 3 | "organization": "GitHub, Inc.", 4 | "commonName": "Internal Services Root" 5 | }, 6 | "certChain": { 7 | "certificates": [ 8 | { 9 | "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" 10 | }, 11 | { 12 | "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" 13 | } 14 | ] 15 | }, 16 | "validFor": { 17 | "start": "2023-04-14T00:00:00.000Z", 18 | "end": "2024-04-14T00:00:00.000Z" 19 | } 20 | } -------------------------------------------------------------------------------- /sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/signing_config.v0.2.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", 3 | "caUrls": [ 4 | { 5 | "url": "https://fulcio.sigstage.dev", 6 | "majorApiVersion": 1, 7 | "validFor": { 8 | "start": "2022-04-14T21:38:40Z" 9 | }, 10 | "operator": "sigstore.dev" 11 | } 12 | ], 13 | "oidcUrls": [ 14 | { 15 | "url": "https://oauth2.sigstage.dev/auth", 16 | "majorApiVersion": 1, 17 | "validFor": { 18 | "start": "2025-04-16T00:00:00Z" 19 | }, 20 | "operator": "sigstore.dev" 21 | } 22 | ], 23 | "rekorTlogUrls": [ 24 | { 25 | "url": "https://log2025-alpha2.rekor.sigstage.dev", 26 | "majorApiVersion": 2, 27 | "validFor": { 28 | "start": "2025-08-20T07:24:08Z" 29 | }, 30 | "operator": "sigstore.dev" 31 | }, 32 | { 33 | "url": "https://log2025-alpha1.rekor.sigstage.dev", 34 | "majorApiVersion": 2, 35 | "validFor": { 36 | "start": "2025-05-07T12:00:00Z", 37 | "end": "2025-08-20T07:24:08Z" 38 | }, 39 | "operator": "sigstore.dev" 40 | }, 41 | { 42 | "url": "https://rekor.sigstage.dev", 43 | "majorApiVersion": 1, 44 | "validFor": { 45 | "start": "2021-01-12T11:53:27Z" 46 | }, 47 | "operator": "sigstore.dev" 48 | } 49 | ], 50 | "tsaUrls": [ 51 | { 52 | "url": "https://timestamp.sigstage.dev/api/v1/timestamp", 53 | "majorApiVersion": 1, 54 | "validFor": { 55 | "start": "2025-04-09T00:00:00Z" 56 | }, 57 | "operator": "sigstore.dev" 58 | } 59 | ], 60 | "rekorTlogConfig": { 61 | "selector": "ANY" 62 | }, 63 | "tsaConfig": { 64 | "selector": "ANY" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/assets/x509/root-privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJGnUWsZb/91FD 3 | wpxo4z2JoPZVQnDj8fnXcm7EL/tpD7tY98EtDjAvK2gkf0Tt5JOsA+OHuWKVthW8 4 | vZMUsImSJoa46C86kI3y7EL2J7+v66L5iB7EnCUyPDF+rHrtZNdkm0L8ZVLjkhJ+ 5 | royQjBS1Ba9M9cmceXZ6khePh6XpchBZAv+SgGBSXNs2t93BnU2kCbSM0MrjgONc 6 | MLATLxSVMMyV7V0BvhXzQaUXmzztxq42nmx+CHPTmKpmJNKDjjrW/8QUftMSUC25 7 | ouprfo9ujU4QiR5bJ6PyfrSYVvBja/gWtv0b29AxjWBXu8u1618mmAooiP+Ir+Nu 8 | yuuGOwJHAgMBAAECggEAFZogdWbFNCCycNzA+r6yN7b7zwPF5vkcConHHo7oQDcJ 9 | kRB9W+QixpEF7P8OKJ8S7SQ2duKx6tK2OgyDy0dSt8l9/kh+o5lZ43wqjeY41Tey 10 | zYAoN0bDSMamKxfG//otmFKEmw0dSXHBnSxjJWHOwEqTM+lRFgcGyZAo32jwUweW 11 | D8cjuYmoL87I6xMmmcAnSjhTv51inboAoo/PK1OHVX7rcmZ5N61/zEF2swo+wzsm 12 | XkgVQcGa6tjstT85/c6KFx8yZju/Na8jqzpTJS1o11QUy86t9VHF8qNyOrzFrTQr 13 | EBP2kp5QCrCsyy4yk9n/bdKY6BR8h1lhS6yNCvBHSQKBgQDnvtUVwJLy5fvbbexc 14 | f7+7kIVPAGEJAfb5ZYsg82sECKCEyg4TSMxzWPw30XVrdC9+tqYRGk4AO77hdEz9 15 | YUCs2KSQU22Ve50oqRP+I2vkTQWbdmDW+saB0+HHjaPG5Dwt/pdPJwlloqUgHSXr 16 | J2KOAjzs4qsirGb8LN4LDiM0nwKBgQDeJqFBYRlJd1mSlLArrVHFmm/0dQTeHS2h 17 | xYOll6iSxPrd6++9FuCsCTdAnLpA0V8gRY7z/jKWY8CQbAYtLvBAz2Sn+kaHXNSn 18 | /pzWRl4sMSnfa16GZWz46m8NBhTdUGdA94Wj3LqDTFDiXwwYRwuPtewK820ZObh5 19 | +vD6Z0fpWQKBgQCMMh84lJKRjV5LBfnqj4IPV0O+Yk1RpLWjdLGxUnEYNJvfGVlg 20 | gzbkRR34KqftRJGDB735NL+hVoOIYtI8qvv0VO9hPIdb2jdeJMMqiIU5zPqqbPfy 21 | ti0m12aMUXyV0vcxIAarZMNDkBxzDA8nbmEp5eKzsAC17jQzNHVznK7howKBgBco 22 | j8bxCGHQP1Y4ieUDvHKNFv609Dzzbb5fiMnKdZhXUI+x+NwNdn54t3nU3NXE/dWv 23 | aqek6EElRP3JRRuQuRsIg8W/IXsbAlBBCriLvWV9+o9/8eqwyBtq1QjWiXZI23q6 24 | UwQyDn+BhS0UG36saVgh7ul1Vvo6OjD9KAHyolyBAoGAIgncKn3cytyeub8WYzPh 25 | OU8DlAuiwMylMrOtBASDSgnx3zVefyGUqSXh8wK92MgZAiBYI4PaHymbil3PMC0A 26 | 1PT8f2Cen70L2aYMDt4+8c5arT6v8bGwtdz/BKjZTca3nblU4nnhhd2aY5e3Ut3d 27 | ffkc8EfGnIeo7hZBqKAe0gY= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/assets/x509/nonroot-privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC402QfBePPiHp0 3 | bYH6D9w2K+z1lWHQc1YoSPEd1JplDYJdWOcf3WsSPslOwRktiwgK9aOWVgsMJ86Z 4 | XyV/A5MVw8BSrKxoDtIXVsf/wqpMB9piu+tVBowXE7E59yWYwFzZw3Ya1ysstmud 5 | m7BnjQTtlyHHm4JnueO0pXYkvRxAimOz6uc1XGrLXnz9B/8Bx4Llz/mEjRYj2bD2 6 | 4vufMbiFKb8Ja93t+9ALpRX9C30Giy15UhUH/ehh+y2fCGv/xqma+rTPr2I5/3/B 7 | QY9spamBrAXSX7ZnzEx+8Hbl3j2tdYi3LC14GWQ9WIT57DYjHtQBBnnkQ9nOGEjs 8 | +jOi7+ApAgMBAAECggEAN7zoUMLB9PA/naT4saTe0CdnCpjGKsrdjMCSlmBrP1ZX 9 | njcVXHK1u4bbxrhNE4L+Je/2KXxBUKUglPgwoqE9Vi72bPhN9gOiMA+nuOXH3a3w 10 | mh351mZnEP6LT+PMnshEOBfOIkIJby6EPb+Z72CDv/L36O5o4UcZ+Hx9qI6vWnbe 11 | DhRpVuyp2Zbw6XOBSu5MWIpWjSBaJmaV2D5ad/EDC/XmRG+gMDHD1G3Hj0Mjlq1F 12 | n4G94UJwqWGwaaQGjE6gKino/ecbEmaOl4KGKrcdU0wZodtxdN8q54qthQ4z48iu 13 | PTWQQSPgUm9OjnWh/Qg3KKQxuY7Hyfn9Lf0TywPj0QKBgQDqxJA2pj8542foJQyj 14 | PaF29awaKwYpK7IFpLbUAer7kw9P0UWFiOpSQWLHecW0J0YyQjOynS0/FbyCgcNF 15 | 4JxoVaBJGKyR1bGuMVD/u12RgfXktyLW+e3jEPoBqoIuK5AFMamT+nxT0WBjRJ0c 16 | oVdsFDol5pXUfKpP7btpTIiUgwKBgQDJioyXcGpuiE8lfvbrQI2TO4rv4m1J1Nyk 17 | pwl1MrUBesT3+JrB4pT9AqknBN6koenknY7ZVlhvHbAPbgnSi7HW1xJcsSew7Dxl 18 | qgnFj26kEMptZjHlzELTPr34RCvP23iUfb2yiEj0kbFYMOBsu7oxC6+lT+XbIkIZ 19 | bmc7Y4QQ4wKBgQCj3lpPWxGM5ZeUqa+9jfpTX74mcduWB0L2v3dCWqhbu9WXQBrH 20 | z77HdY5ucCg4zKUp1Z3iUeXQP+raKZtU/igOh54fB5MFJGUmkpPYPT9dnpo1cENo 21 | TQHoWeQ4H31Inu2jQnv8p336v44JHE6SOmgcL6464E27CN2Udvs2z84R4wKBgCfh 22 | KoCs1eKZRk/9F47lbx47Ifrlqwp4/E/4XX67UeXBDUikALtswl5uMFpwND4Pa+C4 23 | 7JNE6qrSDQyAkaD/02jXleKRi3EOzcSwKM7W2uXMDMIo/qaiDHcQaza9Bo5St0Fq 24 | wCaboRQD4Du7MC1T2DvsPA1SCgGafcnadsLhpjhRAoGBAM2ofL3gyu9/hSzmlcCf 25 | nhnrE3ccqH5ln9//LnuzMsaqFWFG9zhNrUYuujkzxeaF9P+0sXfLjHnC9yNlO2qa 26 | oyoqySXDy0Kc0q7iVF9XHrv0KMt4KO0KQ6XH3SfM3+0SssSMwyG0M09H5Lh+3GP7 27 | QTjwn+hqS4k6vFLZ4HHy+qQi 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | #### Summary 14 | 17 | 18 | #### Release Note 19 | 34 | 35 | #### Documentation 36 | -------------------------------------------------------------------------------- /docs/advanced/offline.md: -------------------------------------------------------------------------------- 1 | # Offline Verification 2 | 3 | !!! danger 4 | Because `--offline` disables trust root updates, `sigstore-python` falls back 5 | to the latest cached trust root or, if none exists, the trust root baked 6 | into `sigstore-python` itself. Like with any other offline verification, 7 | this means that users may miss trust root changes (such as new root keys, 8 | or revocations) unless they separately keep the trust root up-to-date. 9 | 10 | Users who need to operationalize offline verification may wish to do this 11 | by distributing their own trust configuration; see 12 | [Custom instance with local configuration](./custom_trust.md#using-a-custom-instance-with-local-configuration). 13 | 14 | During verification, there are two kinds of network access that `sigstore-python` 15 | *can* perform: 16 | 17 | 1. When verifying against "detached" materials (e.g. separate `.crt` and `.sig` 18 | files), `sigstore-python` can perform an online transparency log lookup. 19 | 2. By default, during all verifications, `sigstore-python` will attempt to 20 | refresh the locally cached root of trust via a TUF update. 21 | 22 | When performing bundle verification (i.e. `.sigstore` or `.sigstore.json`), 23 | (1) does not apply. However, (2) can still result in online accesses. 24 | 25 | To perform **fully** offline verification, pass `--offline` to your 26 | `sigstore verify` subcommand: 27 | 28 | ```bash 29 | $ sigstore verify identity foo.txt \ 30 | --offline \ 31 | --cert-identity 'hamilcar@example.com' \ 32 | --cert-oidc-issuer 'https://github.com/login/oauth' 33 | ``` 34 | 35 | Alternatively, users may choose to bypass TUF entirely by passing 36 | an entire trust configuration to `sigstore-python` via `--trust-config`: 37 | 38 | ```bash 39 | $ sigstore --trust-config public.trustconfig.json verify identity ... 40 | ``` 41 | 42 | This will similarly result in fully offline operation, as the trust 43 | configuration contains a full trust root. 44 | -------------------------------------------------------------------------------- /.github/workflows/conformance.yml: -------------------------------------------------------------------------------- 1 | name: Conformance Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | pull_request: 9 | schedule: 10 | - cron: "45 7 * * 1,4" 11 | 12 | permissions: {} 13 | 14 | jobs: 15 | conformance: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 19 | with: 20 | persist-credentials: false 21 | 22 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 23 | with: 24 | python-version: "3.x" 25 | cache: "pip" 26 | cache-dependency-path: pyproject.toml 27 | 28 | - name: install sigstore-python 29 | run: python -m pip install . 30 | 31 | - uses: sigstore/sigstore-conformance@b7856cfca56fe3f957d4cefdc2c359cc36a84e14 # v0.0.24 32 | with: 33 | entrypoint: ${{ github.workspace }}/test/integration/sigstore-python-conformance 34 | xfail: "test_verify*intoto-with-custom-trust-root]" # see issue 1442 35 | 36 | file-issue-on-failure: 37 | needs: [conformance] 38 | if: failure() && github.event_name == 'schedule' && github.repository == 'sigstore/sigstore-python' 39 | permissions: 40 | issues: write # required to file an issue 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: File an issue for conformance test failure 44 | uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 45 | with: 46 | github-token: ${{ secrets.GITHUB_TOKEN }} 47 | script: | 48 | github.rest.issues.create({ 49 | owner: context.repo.owner, 50 | repo: context.repo.repo, 51 | title: `[CI] Scheduled conformance test failed`, 52 | body: ` 53 | A scheduled conformance test failed, see [run details](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}). 54 | ` 55 | }); 56 | -------------------------------------------------------------------------------- /test/integration/cli/test_verify.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import pytest 15 | 16 | 17 | @pytest.mark.staging 18 | def test_regression_verify_legacy_bundle(capsys, caplog, asset_integration, sigstore): 19 | # Check that verification continues to work when legacy bundle is present (*.sigstore) and 20 | # no cert, sig and normal bundle (*.sigstore.json) are present. 21 | artifact_filename = "bundle_v3.txt" 22 | artifact = asset_integration(artifact_filename) 23 | legacy_bundle = asset_integration(f"{artifact_filename}.sigstore") 24 | 25 | sig = asset_integration(f"{artifact_filename}.sig") 26 | cert = asset_integration(f"{artifact_filename}.crt") 27 | bundle = asset_integration(f"{artifact_filename}.sigstore.json") 28 | assert not cert.is_file() 29 | assert not sig.is_file() 30 | assert not bundle.is_file() 31 | 32 | sigstore( 33 | "--staging", 34 | "verify", 35 | "identity", 36 | str(artifact), 37 | "--cert-identity", 38 | "william@yossarian.net", 39 | "--cert-oidc-issuer", 40 | "https://github.com/login/oauth", 41 | ) 42 | 43 | captures = capsys.readouterr() 44 | assert captures.err == f"OK: {artifact.absolute()}\n" 45 | 46 | assert len(caplog.records) == 1 47 | assert ( 48 | caplog.records[0].message 49 | == f"{artifact.absolute()}: {legacy_bundle.absolute()} should be named {bundle.absolute()}. Support for discovering 'bare' .sigstore inputs will be deprecated in a future release." 50 | ) 51 | -------------------------------------------------------------------------------- /test/unit/internal/test_sct.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import datetime 16 | import struct 17 | 18 | import pretend 19 | import pytest 20 | from cryptography.x509.certificate_transparency import LogEntryType 21 | 22 | from sigstore._internal import sct 23 | 24 | 25 | @pytest.mark.parametrize( 26 | "precert_bytes_len", 27 | [ 28 | 3, 29 | 255, 30 | 1024, 31 | 16777215, 32 | ], 33 | ) 34 | def test_pack_digitally_signed_precertificate(precert_bytes_len): 35 | precert_bytes = b"x" * precert_bytes_len 36 | 37 | mock_sct = pretend.stub( 38 | version=pretend.stub(value=0), 39 | timestamp=datetime.datetime.fromtimestamp( 40 | 1234 / 1000.0, tz=datetime.timezone.utc 41 | ), 42 | entry_type=LogEntryType.PRE_CERTIFICATE, 43 | extension_bytes=b"", 44 | ) 45 | cert = pretend.stub(tbs_precertificate_bytes=precert_bytes) 46 | issuer_key_hash = b"iamapublickeyshatwofivesixdigest" 47 | 48 | _, l1, l2, l3 = struct.unpack("!4c", struct.pack("!I", len(precert_bytes))) 49 | 50 | data = sct._pack_digitally_signed(mock_sct, cert, issuer_key_hash) 51 | assert data == ( 52 | b"\x00" # version 53 | b"\x00" # signature type 54 | b"\x00\x00\x00\x00\x00\x00\x04\xd2" # timestamp 55 | b"\x00\x01" # entry type 56 | b"iamapublickeyshatwofivesixdigest" # issuer key hash 57 | + l1 58 | + l2 59 | + l3 # tbs cert length 60 | + precert_bytes # tbs cert 61 | + b"\x00\x00" # extensions length 62 | + b"" # extensions 63 | ) 64 | -------------------------------------------------------------------------------- /test/assets/tsa/ca.json: -------------------------------------------------------------------------------- 1 | { 2 | "subject": { 3 | "organization": "local", 4 | "commonName": "Test TSA Timestamping" 5 | }, 6 | "certChain": { 7 | "certificates": [ 8 | { 9 | "rawBytes": "MIIBzDCCAXKgAwIBAgIULk1PjItZiOSoI+mHqdkSfcRIYS0wCgYIKoZIzj0EAwIwMDEOMAwGA1UEChMFbG9jYWwxHjAcBgNVBAMTFVRlc3QgVFNBIEludGVybWVkaWF0ZTAeFw0yNDEwMzExMDE2NDJaFw0zMzEwMzExMDE5NDJaMDAxDjAMBgNVBAoTBWxvY2FsMR4wHAYDVQQDExVUZXN0IFRTQSBUaW1lc3RhbXBpbmcwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARbAXMyQHIMuZtXY1bXlIWCKDPKts1j1JHB9n0j8teSYKs6ju9Z+v0joTcaiD0F0R+YEh6xWB9+71jkg57qsJqCo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFHr84ZbK4DrUPCzsEQbBjdT5OnRBMB8GA1UdIwQYMBaAFCkN6IAfJdG+HSOn1pSw9FnTuO1RMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMCA0gAMEUCIAha0d6Cgx9r/S1itcwzCCLpj6Oddp7XHyQ6c9iskT4GAiEA7HrjYsLE4XjdsUA6OgsoO7h5IzkbsVEYUAnKEFchoKY=" 10 | }, 11 | { 12 | "rawBytes": "MIIB0zCCAXigAwIBAgIUGnqrcxtrSpsILclUa/+bCnYeZOUwCgYIKoZIzj0EAwIwKDEOMAwGA1UEChMFbG9jYWwxFjAUBgNVBAMTDVRlc3QgVFNBIFJvb3QwHhcNMjQxMDMxMTAxNDQyWhcNMzQxMDMxMTAxOTQyWjAwMQ4wDAYDVQQKEwVsb2NhbDEeMBwGA1UEAxMVVGVzdCBUU0EgSW50ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2apBvcj3qtsHACafJaOd5Zw874AKK2s5XXdd6jrlVF9h3S6JFgUZ/5MVpYWDNKjgrkqbvhU3RroOGXJ4DyPGSaN4MHYwDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCkN6IAfJdG+HSOn1pSw9FnTuO1RMB8GA1UdIwQYMBaAFO0kKGzBCz7EddTsYBcuwp1VgbhxMAoGCCqGSM49BAMCA0kAMEYCIQDQutW0fTsKlGN4CohrIi/5fMIOqXpjxXswhxiBfCUa/AIhAOe4rlnAGQlmYlBW1uDqt0lw3a/2oAGvHRhDKbiIMPqo" 13 | }, 14 | { 15 | "rawBytes": "MIIBkzCCATqgAwIBAgIUfHAOxJRvpMlmRi3vt7yebkXSb9IwCgYIKoZIzj0EAwIwKDEOMAwGA1UEChMFbG9jYWwxFjAUBgNVBAMTDVRlc3QgVFNBIFJvb3QwHhcNMjQxMDMxMTAxNDQyWhcNMzQxMDMxMTAxOTQyWjAoMQ4wDAYDVQQKEwVsb2NhbDEWMBQGA1UEAxMNVGVzdCBUU0EgUm9vdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDQ2pO3oB5x2HKXp1YpgHB7SCVD1pag46/QUGfQHpyYWOdO4q7uqSx19f2StEszzqrZvpRioo1j6Lwnpp6oQ4P+jQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTtJChswQs+xHXU7GAXLsKdVYG4cTAKBggqhkjOPQQDAgNHADBEAiAFbrVtlmebUMEzUL6JijgYrZhkjUR9VvNO+J2rbQ2eeAIgHUf+TJCnYoq3In8hUlH4D92Fc3Xad6lI0mLfYWm5wpk=" 16 | } 17 | ] 18 | }, 19 | "validFor": { 20 | "start": "2024-10-31T10:16:42.000Z", 21 | "end": "2033-10-31T10:19:42.000Z" 22 | } 23 | } -------------------------------------------------------------------------------- /sigstore/hashes.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Hashing APIs. 17 | """ 18 | 19 | import rekor_types 20 | from cryptography.hazmat.primitives import hashes 21 | from cryptography.hazmat.primitives.asymmetric.utils import Prehashed 22 | from pydantic import BaseModel 23 | from sigstore_models.common.v1 import HashAlgorithm 24 | 25 | from sigstore.errors import Error 26 | 27 | 28 | class Hashed(BaseModel, frozen=True): 29 | """ 30 | Represents a hashed value. 31 | """ 32 | 33 | algorithm: HashAlgorithm 34 | """ 35 | The digest algorithm uses to compute the digest. 36 | """ 37 | 38 | digest: bytes 39 | """ 40 | The digest representing the hash value. 41 | """ 42 | 43 | def _as_hashedrekord_algorithm(self) -> rekor_types.hashedrekord.Algorithm: 44 | """ 45 | Returns an appropriate `hashedrekord.Algorithm` for this `Hashed`. 46 | """ 47 | if self.algorithm == HashAlgorithm.SHA2_256: 48 | return rekor_types.hashedrekord.Algorithm.SHA256 49 | raise Error(f"unknown hash algorithm: {self.algorithm}") 50 | 51 | def _as_prehashed(self) -> Prehashed: 52 | """ 53 | Returns an appropriate Cryptography `Prehashed` for this `Hashed`. 54 | """ 55 | if self.algorithm == HashAlgorithm.SHA2_256: 56 | return Prehashed(hashes.SHA256()) 57 | raise Error(f"unknown hash algorithm: {self.algorithm}") 58 | 59 | def __str__(self) -> str: 60 | """ 61 | Returns a str representation of this `Hashed`. 62 | """ 63 | return f"{self.algorithm.value}:{self.digest.hex()}" 64 | -------------------------------------------------------------------------------- /test/unit/internal/test_timestamping.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import pytest 15 | import requests 16 | 17 | from sigstore._internal.timestamp import TimestampAuthorityClient, TimestampError 18 | from sigstore._utils import sha256_digest 19 | 20 | 21 | @pytest.mark.timestamp_authority 22 | class TestTimestampAuthorityClient: 23 | def test_sign_request(self, tsa_url: str): 24 | tsa = TimestampAuthorityClient(tsa_url) 25 | response = tsa.request_timestamp(b"hello") 26 | assert response 27 | assert ( 28 | response.tst_info.message_imprint.message == sha256_digest(b"hello").digest 29 | ) 30 | assert ( 31 | response.tst_info.message_imprint.hash_algorithm.dotted_string 32 | == "2.16.840.1.101.3.4.2.1" 33 | ) # SHA256 OID 34 | 35 | def test_sign_request_invalid_url(self): 36 | tsa = TimestampAuthorityClient("http://fake-url") 37 | with pytest.raises(TimestampError, match="error while sending"): 38 | tsa.request_timestamp(b"hello") 39 | 40 | def test_sign_request_invalid_request(self, tsa_url): 41 | tsa = TimestampAuthorityClient(tsa_url) 42 | with pytest.raises(TimestampError, match="invalid request"): 43 | tsa.request_timestamp(b"") # empty value here 44 | 45 | def test_invalid_response(self, tsa_url, monkeypatch): 46 | monkeypatch.setattr(requests.Response, "content", b"invalid-response") 47 | 48 | tsa = TimestampAuthorityClient(tsa_url) 49 | with pytest.raises(TimestampError, match="invalid response"): 50 | tsa.request_timestamp(b"hello") 51 | -------------------------------------------------------------------------------- /.github/workflows/scorecards-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Scorecards supply-chain security 2 | on: 3 | # Only the default branch is supported. 4 | workflow_dispatch: # Manual 5 | branch_protection_rule: 6 | schedule: 7 | - cron: '30 4 * * 0' 8 | push: 9 | branches: [ main ] 10 | 11 | # Clear default permissions. 12 | permissions: {} 13 | 14 | jobs: 15 | analysis: 16 | name: Scorecards analysis 17 | runs-on: ubuntu-latest 18 | permissions: 19 | # Needed to upload the results to code-scanning dashboard. 20 | security-events: write 21 | actions: read 22 | contents: read 23 | # Needed to access GitHub's OIDC token which ensures the uploaded results integrity. 24 | id-token: write 25 | steps: 26 | - name: "Checkout code" 27 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 28 | with: 29 | persist-credentials: false 30 | 31 | - name: "Run analysis" 32 | uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 33 | with: 34 | results_file: results.sarif 35 | results_format: sarif 36 | # Read-only PAT token. To create it, 37 | # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation. 38 | repo_token: ${{ secrets.SCORECARD_TOKEN }} 39 | # Publish the results to enable scorecard badges. For more details, see 40 | # https://github.com/ossf/scorecard-action#publishing-results. 41 | # For private repositories, `publish_results` will automatically be set to `false`, 42 | # regardless of the value entered here. 43 | publish_results: true 44 | 45 | # Upload the results as artifacts (optional). 46 | - name: "Upload artifact" 47 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 48 | with: 49 | name: SARIF file 50 | path: results.sarif 51 | retention-days: 5 52 | 53 | # Upload the results to GitHub's code scanning dashboard. 54 | - name: "Upload to code-scanning" 55 | uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 56 | with: 57 | sarif_file: results.sarif 58 | -------------------------------------------------------------------------------- /docs/advanced/custom_trust.md: -------------------------------------------------------------------------------- 1 | # Custom Sigstore instances 2 | 3 | By default, `sigstore` is configured to work with the public `sigstore.dev` 4 | instance. The trust materials for this instance are bundled with the client, 5 | allowing for a seamless out-of-the-box experience. 6 | 7 | In addition to the public instance, `sigstore` also supports using custom 8 | Sigstore instances. When using a custom instance, you are responsible 9 | for providing the trust materials (at least once). This document outlines 10 | the methods for doing so. 11 | 12 | ### Using a custom instance 13 | 14 | Using a custom Sigstore instance is a two-step process: 15 | 16 | 1. First, you must establish trust for the new instance. This is done using the 17 | `sigstore trust-instance` command. This step only needs to be performed once. 18 | 2. Once trust is established, you can use the `--instance` flag with `sigstore` 19 | commands like `sign` and `verify` to point to your custom instance. 20 | 21 | To establish trust for a custom instance, you need its TUF root file. You can then run: 22 | 23 | ```console 24 | $ sigstore --instance https://my-sigstore.example.com trust-instance my-root.json 25 | ``` 26 | 27 | After successfully adding the new instance, you can use it for signing and verifying 28 | artifacts. For example, to sign a file: 29 | 30 | ```console 31 | $ sigstore --instance https://my-sigstore.example.com sign foo.txt 32 | ``` 33 | 34 | ### Using a custom instance with local configuration 35 | 36 | The trust configuration can also be provided as a local file -- but the user is now 37 | responsible for keeping the trust configuration updated. 38 | 39 | The `--trust-config` flag, accepts a JSON-formatted file conforming to the `ClientTrustConfig` 40 | message in the [Sigstore protobuf specs](https://github.com/sigstore/protobuf-specs). 41 | This file configures the entire Sigstore instance state, *including* the URIs 42 | used to access the CA and artifact transparency services as well as the 43 | cryptographic root of trust itself. 44 | 45 | To use a custom client config, prepend `--trust-config` to any `sigstore` 46 | command: 47 | 48 | ```console 49 | $ sigstore --trust-config custom.trustconfig.json sign foo.txt 50 | $ sigstore --trust-config custom.trustconfig.json verify identity foo.txt ... 51 | ``` -------------------------------------------------------------------------------- /test/unit/internal/rekor/test_client_v2.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import hashlib 16 | 17 | import pytest 18 | 19 | from sigstore import dsse 20 | from sigstore.models import TransparencyLogEntry 21 | 22 | 23 | @pytest.mark.staging 24 | @pytest.mark.ambient_oidc 25 | def test_rekor_v2_create_entry_dsse(staging): 26 | # This is not a real unit test: it requires not only staging rekor but also TUF 27 | # fulcio and oidc -- maybe useful only until we have real integration tests in place 28 | sign_ctx_cls, _, identity = staging 29 | sign_ctx = sign_ctx_cls() 30 | 31 | stmt = ( 32 | dsse.StatementBuilder() 33 | .subjects( 34 | [ 35 | dsse.Subject( 36 | name="null", digest={"sha256": hashlib.sha256(b"").hexdigest()} 37 | ) 38 | ] 39 | ) 40 | .predicate_type("https://cosign.sigstore.dev/attestation/v1") 41 | .predicate( 42 | { 43 | "Data": "", 44 | "Timestamp": "2023-12-07T00:37:58Z", 45 | } 46 | ) 47 | ).build() 48 | 49 | with sign_ctx.signer(identity) as signer: 50 | bundle = signer.sign_dsse(stmt) 51 | 52 | assert isinstance(bundle.log_entry, TransparencyLogEntry) 53 | 54 | 55 | @pytest.mark.staging 56 | @pytest.mark.ambient_oidc 57 | def test_rekor_v2_create_entry_hashed_rekord(staging): 58 | # This is not a real unit test: it requires not only staging rekor but also TUF 59 | # fulcio and oidc -- maybe useful only until we have real integration tests in place 60 | sign_ctx_cls, _, identity = staging 61 | sign_ctx = sign_ctx_cls() 62 | 63 | with sign_ctx.signer(identity) as signer: 64 | bundle = signer.sign_artifact(b"") 65 | 66 | assert isinstance(bundle.log_entry, TransparencyLogEntry) 67 | -------------------------------------------------------------------------------- /.github/workflows/check-embedded-root.yml: -------------------------------------------------------------------------------- 1 | name: Check embedded root 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '13 13 * * 3' 7 | 8 | jobs: 9 | check-embedded-root: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | issues: write 13 | 14 | steps: 15 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 16 | with: 17 | persist-credentials: false 18 | 19 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 20 | with: 21 | python-version: "3.x" 22 | cache: "pip" 23 | cache-dependency-path: pyproject.toml 24 | 25 | - name: Setup environment 26 | run: make dev 27 | 28 | - name: Check if embedded root is up-to-date 29 | run: | 30 | make update-embedded-root 31 | git diff --exit-code 32 | 33 | 34 | - if: failure() 35 | name: Create an issue if embedded root is not up-to-date 36 | uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 37 | with: 38 | script: | 39 | const repo = context.repo.owner + "/" + context.repo.repo 40 | const body = ` 41 | The Sigstore [TUF repository](https://tuf-repo-cdn.sigstore.dev/) contents have changed: the data embedded 42 | in sigstore-python sources can be updated. This is not urgent but will improve cold-cache performance. 43 | 44 | Run \`make update-embedded-root\` to update the embedded data. 45 | 46 | This issue was filed by _${context.workflow}_ [workflow run](${context.serverUrl}/${repo}/actions/runs/${context.runId}). 47 | ` 48 | 49 | const issues = await github.rest.search.issuesAndPullRequests({ 50 | q: "label:embedded-root-update+state:open+type:issue+repo:" + repo, 51 | }) 52 | if (issues.data.total_count > 0) { 53 | console.log("Issue for embedded root update exists already.") 54 | } else { 55 | github.rest.issues.create({ 56 | owner: context.repo.owner, 57 | repo: context.repo.repo, 58 | title: "Embedded TUF root is not up-to-date", 59 | labels: ["embedded-root-update"], 60 | body: body, 61 | }) 62 | console.log("New issue created.") 63 | } 64 | -------------------------------------------------------------------------------- /test/assets/trusted_root/certificate_authority.json: -------------------------------------------------------------------------------- 1 | { 2 | "subject": { 3 | "organization": "GitHub, Inc.", 4 | "commonName": "Internal Services Root" 5 | }, 6 | "certChain": { 7 | "certificates": [ 8 | { 9 | "rawBytes": "MIIB3DCCAWKgAwIBAgIUchkNsH36Xa04b1LqIc+qr9DVecMwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDQxNDAwMDAwMFoXDTI0MDQxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUD5ZNbSqYMd6r8qpOOEX9ibGnZT9GsuXOhr/f8U9FJugBGExKYp40OULS0erjZW7xV9xV52NnJf5OeDq4e5ZKqNWMFQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUaW1RudOgVt0leqY0WKYbuPr47wAwCgYIKoZIzj0EAwMDaAAwZQIwbUH9HvD4ejCZJOWQnqAlkqURllvu9M8+VqLbiRK+zSfZCZwsiljRn8MQQRSkXEE5AjEAg+VxqtojfVfu8DhzzhCx9GKETbJHb19iV72mMKUbDAFmzZ6bQ8b54Zb8tidy5aWe" 10 | }, 11 | { 12 | "rawBytes": "MIICEDCCAZWgAwIBAgIUX8ZO5QXP7vN4dMQ5e9sU3nub8OgwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTI4MDQxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEvMLY/dTVbvIJYANAuszEwJnQE1llftynyMKIMhh48HmqbVr5ygybzsLRLVKbBWOdZ21aeJz+gZiytZetqcyF9WlER5NEMf6JV7ZNojQpxHq4RHGoGSceQv/qvTiZxEDKo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaW1RudOgVt0leqY0WKYbuPr47wAwHwYDVR0jBBgwFoAU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaQAwZgIxAK1B185ygCrIYFlIs3GjswjnwSMG6LY8woLVdakKDZxVa8f8cqMs1DhcxJ0+09w95QIxAO+tBzZk7vjUJ9iJgD4R6ZWTxQWKqNm74jO99o+o9sv4FI/SZTZTFyMn0IJEHdNmyA==" 13 | }, 14 | { 15 | "rawBytes": "MIIB9DCCAXqgAwIBAgIUa/JAkdUjK4JUwsqtaiRJGWhqLSowCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDQxNDAwMDAwMFoXDTMzMDQxMTAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf9jFAXxz4kx68AHRMOkFBhflDcMTvzaXz4x/FCcXjJ/1qEKon/qPIGnaURskDtyNbNDOpeJTDDFqt48iMPrnzpx6IZwqemfUJN4xBEZfza+pYt/iyod+9tZr20RRWSv/o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQU9NYYlobnAG4c0/qjxyH/lq/wz+QwCgYIKoZIzj0EAwMDaAAwZQIxALZLZ8BgRXzKxLMMN9VIlO+e4hrBnNBgF7tz7Hnrowv2NetZErIACKFymBlvWDvtMAIwZO+ki6ssQ1bsZo98O8mEAf2NZ7iiCgDDU0Vwjeco6zyeh0zBTs9/7gV6AHNQ53xD" 16 | } 17 | ] 18 | }, 19 | "validFor": { 20 | "start": "2023-04-14T00:00:00.000Z", 21 | "end": "2024-04-14T00:00:00.000Z" 22 | } 23 | } -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json 2 | 3 | site_name: sigstore-python 4 | site_url: https://sigstore.github.io/sigstore-python 5 | repo_url: https://github.com/sigstore/sigstore-python 6 | site_description: sigstore-python, a Sigstore client written in Python 7 | repo_name: sigstore-python 8 | edit_uri: edit/main/docs/ 9 | theme: 10 | name: material 11 | icon: 12 | repo: fontawesome/brands/github 13 | logo: assets/images/logo.png 14 | features: 15 | - content.action.edit 16 | - content.code.copy 17 | - header.autohide 18 | - navigation.instant 19 | - navigation.instant.progress 20 | - navigation.footer 21 | - search.highlight 22 | - search.suggest 23 | palette: 24 | primary: custom 25 | font: 26 | text: Inter 27 | extra_css: 28 | - stylesheets/custom.css 29 | nav: 30 | - Home: index.md 31 | - Installation: installation.md 32 | - Signing: signing.md 33 | - Verifying: verify.md 34 | - Policy: policy.md 35 | - Advanced: 36 | - Custom Sigstore Instances: advanced/custom_trust.md 37 | - Offline Verification: advanced/offline.md 38 | # begin-api-section 39 | - API: 40 | - api/index.md 41 | - Models: api/models.md 42 | - Errors: api/errors.md 43 | - Hashes: api/hashes.md 44 | - OIDC: api/oidc.md 45 | - Sign: api/sign.md 46 | - Verify: 47 | - Policy: api/verify/policy.md 48 | - Verifier: api/verify/verifier.md 49 | # end-api-section 50 | markdown_extensions: 51 | - admonition 52 | - pymdownx.details 53 | - pymdownx.superfences 54 | copyright: sigstore © 2024 55 | plugins: 56 | - search 57 | - social 58 | - mkdocstrings: 59 | handlers: 60 | python: 61 | options: 62 | members_order: source 63 | unwrap_annotated: true 64 | modernize_annotations: true 65 | merge_init_into_class: true 66 | docstring_section_style: spacy 67 | signature_crossrefs: true 68 | show_symbol_type_toc: true 69 | filters: 70 | - '!^_' 71 | validation: 72 | omitted_files: warn 73 | unrecognized_links: warn 74 | anchors: warn 75 | not_found: warn 76 | 77 | extra: 78 | generator: false 79 | social: 80 | - icon: fontawesome/brands/slack 81 | link: https://sigstore.slack.com 82 | - icon: fontawesome/brands/x-twitter 83 | link: https://twitter.com/projectsigstore 84 | -------------------------------------------------------------------------------- /docs/scripts/gen_ref_pages.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import argparse 15 | import shutil 16 | import sys 17 | from pathlib import Path 18 | 19 | root = Path(__file__).parent.parent.parent 20 | src = root / "sigstore" 21 | api_root = root / "docs" / "api" 22 | 23 | 24 | def main(args: argparse.Namespace) -> None: 25 | """Main script.""" 26 | if args.overwrite: 27 | shutil.rmtree(api_root, ignore_errors=True) 28 | elif not args.check and api_root.exists(): 29 | print(f"API root {api_root} already exists, skipping.") 30 | sys.exit(0) 31 | 32 | seen = set() 33 | for path in src.rglob("*.py"): 34 | module_path = path.relative_to(src).with_suffix("") 35 | full_doc_path = api_root / path.relative_to(src).with_suffix(".md") 36 | 37 | # Exclude private entries 38 | if any(part.startswith("_") for part in module_path.parts): 39 | continue 40 | 41 | if args.check and not full_doc_path.is_file(): 42 | print(f"File {full_doc_path} does not exist.", file=sys.stderr) 43 | sys.exit(1) 44 | 45 | full_doc_path.parent.mkdir(parents=True, exist_ok=True) 46 | with full_doc_path.open("w") as f: 47 | f.write(f":::sigstore.{str(module_path).replace('/', '.')}\n ") 48 | 49 | seen.add(full_doc_path) 50 | 51 | # Add the root 52 | with (api_root / "index.md").open("w") as f: 53 | f.write("""!!! note 54 | 55 | The API reference is automatically generated from the docstrings 56 | 57 | :::sigstore 58 | """) 59 | 60 | seen.add(api_root / "index.md") 61 | 62 | if args.check: 63 | if diff := set(api_root.rglob("*.md")).symmetric_difference(seen): 64 | print(f"Found leftover documentation file: {diff}", file=sys.stderr) 65 | sys.exit(1) 66 | else: 67 | print("API doc generated.") 68 | 69 | 70 | if __name__ == "__main__": 71 | parser = argparse.ArgumentParser( 72 | description="Generate the structure for the API documentation." 73 | ) 74 | parser.add_argument("--overwrite", action="store_true", default=False) 75 | parser.add_argument("--check", action="store_true", default=False) 76 | 77 | arguments = parser.parse_args() 78 | 79 | if arguments.check and arguments.overwrite: 80 | print("You can't specify both --check and --overwrite.", file=sys.stderr) 81 | sys.exit(1) 82 | 83 | main(arguments) 84 | -------------------------------------------------------------------------------- /test/unit/test_dsse.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import base64 16 | import json 17 | 18 | import pytest 19 | 20 | from sigstore import dsse 21 | from sigstore.dsse import InvalidEnvelope 22 | 23 | 24 | class TestEnvelope: 25 | def test_roundtrip(self): 26 | raw = json.dumps( 27 | { 28 | "payload": base64.b64encode(b"foo").decode(), 29 | "payloadType": dsse.Envelope._TYPE, 30 | "signatures": [ 31 | {"sig": base64.b64encode(b"lol").decode()}, 32 | ], 33 | } 34 | ) 35 | evp = dsse.Envelope._from_json(raw) 36 | 37 | assert evp._inner.payload == b"foo" 38 | assert evp._inner.payload_type == dsse.Envelope._TYPE 39 | assert evp.signature == b"lol" 40 | 41 | serialized = evp.to_json() 42 | # envelope matches 43 | assert dsse.Envelope._from_json(serialized) == evp 44 | # parsed JSON marches 45 | assert json.loads(raw) == evp._inner.to_dict() 46 | 47 | def test_missing_signature(self): 48 | raw = json.dumps( 49 | { 50 | "payload": base64.b64encode(b"foo").decode(), 51 | "payloadType": dsse.Envelope._TYPE, 52 | "signatures": [], 53 | } 54 | ) 55 | 56 | with pytest.raises(InvalidEnvelope, match="one signature"): 57 | dsse.Envelope._from_json(raw) 58 | 59 | def test_empty_signature(self): 60 | raw = json.dumps( 61 | { 62 | "payload": base64.b64encode(b"foo").decode(), 63 | "payloadType": dsse.Envelope._TYPE, 64 | "signatures": [ 65 | {"sig": ""}, 66 | ], 67 | } 68 | ) 69 | 70 | with pytest.raises(InvalidEnvelope, match="non-empty"): 71 | dsse.Envelope._from_json(raw) 72 | 73 | def test_multiple_signatures(self): 74 | raw = json.dumps( 75 | { 76 | "payload": base64.b64encode(b"foo").decode(), 77 | "payloadType": dsse.Envelope._TYPE, 78 | "signatures": [ 79 | {"sig": base64.b64encode(b"lol").decode()}, 80 | {"sig": base64.b64encode(b"lmao").decode()}, 81 | ], 82 | } 83 | ) 84 | 85 | with pytest.raises(InvalidEnvelope, match="one signature"): 86 | dsse.Envelope._from_json(raw) 87 | -------------------------------------------------------------------------------- /.github/workflows/staging-tests.yml: -------------------------------------------------------------------------------- 1 | name: Staging Instance Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | schedule: 8 | - cron: "0 */8 * * *" 9 | 10 | permissions: {} 11 | 12 | jobs: 13 | staging-tests: 14 | if: github.event_name != 'schedule' || github.repository == 'sigstore/sigstore-python' 15 | runs-on: ubuntu-latest 16 | permissions: 17 | # Needed to access the workflow's OIDC identity. 18 | id-token: write 19 | 20 | # Needed to create an issue, on failure. 21 | issues: write 22 | steps: 23 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 24 | with: 25 | persist-credentials: false 26 | 27 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 28 | with: 29 | python-version: "3.x" 30 | cache: "pip" 31 | cache-dependency-path: pyproject.toml 32 | 33 | - name: staging tests 34 | env: 35 | SIGSTORE_LOGLEVEL: DEBUG 36 | run: | 37 | # This is similar to the "smoketest" that we run during the 38 | # release workflow, except that we run against Sigstore's 39 | # staging instances instead. 40 | # We also don't bother to build distributions. 41 | 42 | python -m venv staging-env 43 | 44 | ./staging-env/bin/python -m pip install . 45 | 46 | # Our signing target is not important here, so we just sign 47 | # the README in the repository. 48 | ./staging-env/bin/python -m sigstore --verbose --staging sign README.md 49 | 50 | # Verification also requires a different Rekor instance, so we 51 | # also test it. 52 | ./staging-env/bin/python -m sigstore --verbose --staging verify identity \ 53 | --cert-oidc-issuer https://token.actions.githubusercontent.com \ 54 | --cert-identity ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/.github/workflows/staging-tests.yml@${GITHUB_REF} \ 55 | README.md 56 | 57 | - name: generate an issue if staging tests fail 58 | if: failure() 59 | run: | 60 | cat <<- EOF > /tmp/staging-instance-issue.md 61 | ## Staging instance failure 62 | 63 | A scheduled test against Sigstore's staging instance has failed. 64 | 65 | This suggests one of three conditions: 66 | 67 | * A backwards-incompatible change in a Sigstore component; 68 | * A regression in \`sigstore-python\`; 69 | * A transient error. 70 | 71 | The full CI failure can be found here: 72 | 73 | ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/$GITHUB_RUN_ID 74 | EOF 75 | 76 | - name: open an issue if the staging tests fail 77 | if: failure() 78 | uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6.0.0 79 | with: 80 | title: "[CI] Integration failure: staging instance" 81 | # created in the previous step 82 | content-filepath: /tmp/staging-instance-issue.md 83 | labels: bug,component:cicd,component:tests 84 | assignees: woodruffw,di,tetsuo-cpp 85 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | permissions: {} 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 17 | with: 18 | persist-credentials: false 19 | 20 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 21 | with: 22 | python-version: "3.x" 23 | cache: "pip" 24 | cache-dependency-path: pyproject.toml 25 | 26 | - name: deps 27 | run: make dev SIGSTORE_EXTRA=lint 28 | 29 | - name: lint 30 | run: make lint 31 | 32 | check-readme: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 36 | with: 37 | persist-credentials: false 38 | 39 | # NOTE: We intentionally check `--help` rendering against our minimum Python, 40 | # since it changes slightly between Python versions. 41 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 42 | with: 43 | python-version: "3.9" 44 | cache: "pip" 45 | cache-dependency-path: pyproject.toml 46 | 47 | - name: deps 48 | run: make dev 49 | 50 | - name: check-readme 51 | run: make check-readme 52 | 53 | licenses: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 57 | with: 58 | persist-credentials: false 59 | 60 | # adapted from Warehouse's bin/licenses 61 | - run: | 62 | for fn in $(find . -type f -name "*.py"); do 63 | if [[ ! "$(head -5 $fn | grep "^ *\(#\|\*\|\/\/\) .* License\(d*\)")" ]]; then 64 | echo "${fn} is missing a license" 65 | exit 1 66 | fi 67 | done 68 | 69 | x509-testcases: 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 73 | with: 74 | persist-credentials: false 75 | 76 | # NOTE: We intentionally check test certificates against our minimum supported Python. 77 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 78 | with: 79 | python-version: "3.9" 80 | cache: "pip" 81 | cache-dependency-path: pyproject.toml 82 | 83 | - name: deps 84 | run: make dev 85 | 86 | - name: ensure testcase generation does not regress 87 | run: make gen-x509-testcases 88 | 89 | all-lints-pass: 90 | if: always() 91 | 92 | needs: 93 | - lint 94 | - check-readme 95 | - licenses 96 | - x509-testcases 97 | 98 | runs-on: ubuntu-latest 99 | 100 | steps: 101 | - name: check lint jobs 102 | uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 103 | with: 104 | jobs: ${{ toJSON(needs) }} 105 | -------------------------------------------------------------------------------- /.github/workflows/cross-os.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Cross-platform sign and verify 16 | on: 17 | push: 18 | branches: 19 | - main 20 | - series/* 21 | pull_request: 22 | workflow_dispatch: 23 | 24 | permissions: {} 25 | 26 | defaults: 27 | run: 28 | shell: bash 29 | 30 | jobs: 31 | sign: 32 | name: Sign on ${{ matrix.os }} 33 | runs-on: ${{ matrix.os }}-latest 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | os: [ubuntu, macos, windows] 38 | steps: 39 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 40 | with: 41 | persist-credentials: false 42 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 43 | with: 44 | python-version: "3.x" 45 | cache: "pip" 46 | cache-dependency-path: pyproject.toml 47 | - run: pip install . 48 | - name: Fetch testing oidc token 49 | uses: sigstore-conformance/extremely-dangerous-public-oidc-beacon@4a8befcc16064dac9e97f210948d226e5c869bdc # v1.0.0 50 | - name: Sign 51 | run: python -m sigstore --staging sign --identity-token $(cat oidc-token.txt) test/assets/a.txt 52 | - name: upload signature bundle 53 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 54 | with: 55 | name: ${{ matrix.os }}-bundle 56 | path: test/assets/a.txt.sigstore.json 57 | if-no-files-found: error 58 | retention-days: 1 59 | verify: 60 | name: Verify ${{ matrix.signed-with-os }} bundle on ${{ matrix.os }} 61 | if: ${{ always() }} # don't stop some verification if one of the signing jobs failed 62 | needs: [sign] 63 | runs-on: ${{ matrix.os }}-latest 64 | strategy: 65 | fail-fast: false # Don't cancel other jobs if one fails 66 | matrix: 67 | os: [ubuntu, macos, windows] 68 | signed-with-os: [ubuntu, macos, windows] 69 | steps: 70 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 71 | with: 72 | persist-credentials: false 73 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 74 | with: 75 | python-version: "3.x" 76 | cache: "pip" 77 | cache-dependency-path: pyproject.toml 78 | - run: pip install . 79 | - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 80 | with: 81 | name: ${{ matrix.signed-with-os }}-bundle 82 | - name: Verify 83 | run: | 84 | python -m sigstore --staging verify github --verbose \ 85 | --cert-identity "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" \ 86 | --bundle a.txt.sigstore.json \ 87 | test/assets/a.txt 88 | -------------------------------------------------------------------------------- /.github/workflows/cross-version-verify.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: Cross-version verify 16 | on: 17 | push: 18 | branches: 19 | - main 20 | - series/* 21 | pull_request: 22 | workflow_dispatch: 23 | 24 | permissions: {} 25 | 26 | jobs: 27 | sign: 28 | name: Sign 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 32 | with: 33 | persist-credentials: false 34 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 35 | with: 36 | python-version: "3.x" 37 | cache: "pip" 38 | cache-dependency-path: pyproject.toml 39 | - run: pip install . 40 | - name: Fetch testing oidc token 41 | uses: sigstore-conformance/extremely-dangerous-public-oidc-beacon@4a8befcc16064dac9e97f210948d226e5c869bdc # v1.0.0 42 | - name: Sign 43 | run: | 44 | touch artifact 45 | python -m sigstore --staging sign --bundle artifact-rekor2.sigstore.json --identity-token $(cat oidc-token.txt) --rekor-version=2 artifact 46 | python -m sigstore --staging sign --bundle artifact-rekor1.sigstore.json --identity-token $(cat oidc-token.txt) --rekor-version=1 artifact 47 | - name: upload signature bundle 48 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 49 | with: 50 | name: bundle 51 | path: artifact*.sigstore.json 52 | if-no-files-found: error 53 | retention-days: 1 54 | verify: 55 | name: Verify with ${{ matrix.version }} 56 | needs: [sign] 57 | runs-on: ubuntu-latest 58 | strategy: 59 | fail-fast: false # Don't cancel other jobs if one fails 60 | matrix: 61 | version: [3.5.6, 3.6.6, 4.0.0, 4.1.0] 62 | steps: 63 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 64 | with: 65 | python-version: "3.x" 66 | - run: pip install sigstore==${{ matrix.version }} 67 | - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 68 | with: 69 | name: bundle 70 | - run: touch artifact 71 | - name: Verify (Rekor v2) 72 | if: startsWith(matrix.version, '3.') != true 73 | run: | 74 | python -m sigstore --staging verify github --verbose \ 75 | --cert-identity "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" \ 76 | --bundle artifact-rekor2.sigstore.json \ 77 | artifact 78 | - name: Verify (Rekor v1) 79 | run: | 80 | python -m sigstore --staging verify github --verbose \ 81 | --cert-identity "https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" \ 82 | --bundle artifact-rekor1.sigstore.json \ 83 | artifact 84 | -------------------------------------------------------------------------------- /sigstore/_internal/key_details.py: -------------------------------------------------------------------------------- 1 | # Copyright 2025 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Utilities for getting PublicKeyDetails. 17 | """ 18 | 19 | from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding, rsa 20 | from cryptography.x509 import Certificate 21 | from sigstore_models.common.v1 import PublicKeyDetails 22 | 23 | 24 | def _get_key_details(certificate: Certificate) -> PublicKeyDetails: 25 | """ 26 | Determine PublicKeyDetails from the Certificate. 27 | We disclude the unrecommended types. 28 | See 29 | - https://github.com/sigstore/architecture-docs/blob/6a8d78108ef4bb403046817fbcead211a9dca71d/algorithm-registry.md. 30 | - https://github.com/sigstore/protobuf-specs/blob/3aaae418f76fb4b34df4def4cd093c464f20fed3/protos/sigstore_common.proto 31 | """ 32 | public_key = certificate.public_key() 33 | params = certificate.signature_algorithm_parameters 34 | if isinstance(public_key, ec.EllipticCurvePublicKey): 35 | if isinstance(public_key.curve, ec.SECP256R1): 36 | key_details = PublicKeyDetails.PKIX_ECDSA_P256_SHA_256 37 | elif isinstance(public_key.curve, ec.SECP384R1): 38 | key_details = PublicKeyDetails.PKIX_ECDSA_P384_SHA_384 39 | elif isinstance(public_key.curve, ec.SECP521R1): 40 | key_details = PublicKeyDetails.PKIX_ECDSA_P521_SHA_512 41 | else: 42 | raise ValueError(f"Unsupported EC curve: {public_key.curve.name}") 43 | elif isinstance(public_key, rsa.RSAPublicKey): 44 | if public_key.key_size == 2048: 45 | if isinstance(params, padding.PKCS1v15): 46 | key_details = PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256 47 | else: 48 | raise ValueError( 49 | f"Unsupported public key type, size, and padding: {type(public_key)}, {public_key.key_size}, {params}" 50 | ) 51 | elif public_key.key_size == 3072: 52 | if isinstance(params, padding.PKCS1v15): 53 | key_details = PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256 54 | else: 55 | raise ValueError( 56 | f"Unsupported public key type, size, and padding: {type(public_key)}, {public_key.key_size}, {params}" 57 | ) 58 | elif public_key.key_size == 4096: 59 | if isinstance(params, padding.PKCS1v15): 60 | key_details = PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256 61 | else: 62 | raise ValueError( 63 | f"Unsupported public key type, size, and padding: {type(public_key)}, {public_key.key_size}, {params}" 64 | ) 65 | else: 66 | raise ValueError(f"Unsupported RSA key size: {public_key.key_size}") 67 | elif isinstance(public_key, ed25519.Ed25519PublicKey): 68 | key_details = PublicKeyDetails.PKIX_ED25519 69 | # There is likely no need to explicitly detect PKIX_ED25519_PH, especially since the cryptography 70 | # library does not yet support Ed25519ph. 71 | else: 72 | raise ValueError(f"Unsupported public key type: {type(public_key)}") 73 | return key_details 74 | -------------------------------------------------------------------------------- /docs/verify.md: -------------------------------------------------------------------------------- 1 | # Verifying 2 | 3 | ## Generic identities 4 | 5 | This is the most common verification done with `sigstore`, and therefore 6 | the one you probably want: you can use it to verify that a signature was 7 | produced by a particular identity (like `hamilcar@example.com`), as attested 8 | to by a particular OIDC provider (like `https://github.com/login/oauth`). 9 | 10 | ```console 11 | $ sigstore verify identity --cert-identity --cert-oidc-issuer FILE_OR_DIGEST 12 | ``` 13 | 14 | The following command will verify that the bundle `tests/assets/bundle.txt.sigstore` was signed by `a@tny.town` using 15 | the staging infrastructure of `sigstore`. 16 | 17 | ```console 18 | $ sigstore --staging verify identity --cert-identity "a@tny.town" --cert-oidc-issuer "https://github.com/login/oauth" test/assets/bundle.txt 19 | ``` 20 | 21 | ## Verifying from GitHub Actions 22 | 23 | If your signatures are coming from GitHub Actions (e.g., a workflow that uses its [ambient credentials](./signing.md#signing-with-ambient-credentials)), 24 | then you can use the `sigstore verify github` subcommand to verify 25 | claims more precisely than `sigstore verify identity` allows. 26 | 27 | `sigstore verify github` can be used to verify claims specific to signatures coming from GitHub 28 | Actions. `sigstore-python` signs releases via GitHub Actions, so the examples below are working 29 | examples of how you can verify a given `sigstore-python` release. 30 | 31 | When using `sigstore verify github`, you must pass `--cert-identity` or `--repository`, or both. 32 | Unlike `sigstore verify identity`, `--cert-oidc-issuer` is **not** required (since it's 33 | inferred to be GitHub Actions). 34 | 35 | Verifying with `--cert-identity`: 36 | 37 | ```console 38 | $ sigstore verify github sigstore-0.10.0-py3-none-any.whl \ 39 | --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ 40 | --cert-identity https://github.com/sigstore/sigstore-python/.github/workflows/release.yml@refs/tags/v0.10.0 41 | ``` 42 | 43 | Verifying with `--repository`: 44 | 45 | ```console 46 | $ sigstore verify github sigstore-0.10.0-py3-none-any.whl \ 47 | --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ 48 | --repository sigstore/sigstore-python 49 | ``` 50 | 51 | Additional GitHub Actions specific claims can be verified like so: 52 | 53 | ```console 54 | $ sigstore verify github sigstore-0.10.0-py3-none-any.whl \ 55 | --bundle sigstore-0.10.0-py3-none-any.whl.bundle \ 56 | --cert-identity https://github.com/sigstore/sigstore-python/.github/workflows/release.yml@refs/tags/v0.10.0 \ 57 | --trigger release \ 58 | --sha 66581529803929c3ccc45334632ccd90f06e0de4 \ 59 | --name Release \ 60 | --repository sigstore/sigstore-python \ 61 | --ref refs/tags/v0.10.0 62 | ``` 63 | 64 | ## Verifying against a bundle 65 | 66 | By default, `sigstore verify identity` will attempt to find a `.sigstore.json` 67 | or `.sigstore` in the same directory as the file being verified: 68 | 69 | ```console 70 | # looks for foo.txt.sigstore.json 71 | $ sigstore verify identity foo.txt \ 72 | --cert-identity 'hamilcar@example.com' \ 73 | --cert-oidc-issuer 'https://github.com/login/oauth' 74 | ``` 75 | 76 | Multiple files can be verified at once: 77 | 78 | ```console 79 | # looks for {foo,bar}.txt.sigstore.json 80 | $ python -m sigstore verify identity foo.txt bar.txt \ 81 | --cert-identity 'hamilcar@example.com' \ 82 | --cert-oidc-issuer 'https://github.com/login/oauth' 83 | ``` 84 | 85 | ## Verifying a digest instead of a file 86 | 87 | `sigstore-python` supports verifying digests directly, without requiring the artifact to be 88 | present. The digest should be prefixed with the `sha256:` string: 89 | 90 | ```console 91 | $ sigstore verify identity sha256:ce8ab2822671752e201ea1e19e8c85e73d497e1c315bfd9c25f380b7625d1691 \ 92 | --cert-identity 'hamilcar@example.com' \ 93 | --cert-oidc-issuer 'https://github.com/login/oauth' 94 | --bundle 'foo.txt.sigstore.json' 95 | ``` -------------------------------------------------------------------------------- /test/integration/cli/test_plumbing.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import pytest 17 | from sigstore_models.common.v1 import HashAlgorithm 18 | 19 | from sigstore.hashes import Hashed 20 | from sigstore.models import Bundle, InvalidBundle 21 | from sigstore.verify import policy 22 | from sigstore.verify.verifier import Verifier 23 | 24 | 25 | def test_fix_bundle_fixes_missing_checkpoint(capsys, sigstore, asset_integration): 26 | invalid_bundle = asset_integration("Python-3.12.5.tgz.sigstore") 27 | 28 | # The bundle is invalid, because it's missing a checkpoint 29 | # for its inclusion proof. 30 | with pytest.raises( 31 | InvalidBundle, match="entry must contain inclusion proof, with checkpoint" 32 | ): 33 | Bundle.from_json(invalid_bundle.read_text()) 34 | 35 | # Running `sigstore plumbing fix-bundle` emits a fixed bundle. 36 | sigstore("plumbing", "fix-bundle", "--bundle", str(invalid_bundle)) 37 | 38 | captures = capsys.readouterr() 39 | 40 | # The bundle now loads correctly. 41 | bundle = Bundle.from_json(captures.out) 42 | 43 | # We didn't pass `--upgrade-version` so the version is still v0.1. 44 | assert bundle._inner.media_type == Bundle.BundleType.BUNDLE_0_1 45 | 46 | # ...and the fixed bundle can now be used to verify the `Python-3.12.5.tgz` 47 | # release. 48 | verifier = Verifier.production() 49 | verifier.verify_artifact( 50 | Hashed( 51 | algorithm=HashAlgorithm.SHA2_256, 52 | digest=bytes.fromhex( 53 | "38dc4e2c261d49c661196066edbfb70fdb16be4a79cc8220c224dfeb5636d405" 54 | ), 55 | ), 56 | bundle, 57 | policy.AllOf( 58 | [ 59 | policy.Identity( 60 | identity="thomas@python.org", issuer="https://accounts.google.com" 61 | ) 62 | ] 63 | ), 64 | ) 65 | 66 | 67 | def test_fix_bundle_upgrades_bundle(capsys, sigstore, asset_integration): 68 | invalid_bundle = asset_integration("Python-3.12.5.tgz.sigstore") 69 | 70 | # Running `sigstore plumbing fix-bundle --upgrade-version` 71 | # emits a fixed bundle. 72 | sigstore( 73 | "plumbing", "fix-bundle", "--upgrade-version", "--bundle", str(invalid_bundle) 74 | ) 75 | 76 | captures = capsys.readouterr() 77 | 78 | # The bundle now loads correctly. 79 | bundle = Bundle.from_json(captures.out) 80 | 81 | # The bundle is now the latest version (v0.3). 82 | assert bundle._inner.media_type == Bundle.BundleType.BUNDLE_0_3 83 | 84 | # ...and the upgraded (and fixed) bundle can still verify 85 | # the release. 86 | # ...and the fixed can now be used to verify the `Python-3.12.5.tgz` release. 87 | verifier = Verifier.production() 88 | verifier.verify_artifact( 89 | Hashed( 90 | algorithm=HashAlgorithm.SHA2_256, 91 | digest=bytes.fromhex( 92 | "38dc4e2c261d49c661196066edbfb70fdb16be4a79cc8220c224dfeb5636d405" 93 | ), 94 | ), 95 | bundle, 96 | policy.AllOf( 97 | [ 98 | policy.Identity( 99 | identity="thomas@python.org", issuer="https://accounts.google.com" 100 | ) 101 | ] 102 | ), 103 | ) 104 | -------------------------------------------------------------------------------- /test/integration/sigstore-python-conformance: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | A wrapper to convert `sigstore-conformance` CLI protocol invocations to match `sigstore-python`. 5 | """ 6 | 7 | import json 8 | import os 9 | import sys 10 | from contextlib import suppress 11 | from tempfile import NamedTemporaryFile 12 | 13 | # The signing config in this trust_config is not used: it's just here 14 | # so the built trustconfig is complete 15 | trust_config = { 16 | "mediaType": "application/vnd.dev.sigstore.clienttrustconfig.v0.1+json", 17 | "signingConfig": { 18 | "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", 19 | "caUrls": [{ 20 | "url": "https://fulcio.example.com", 21 | "majorApiVersion": 1, 22 | "operator": "", 23 | "validFor": {"start": "1970-01-01T01:01:01Z"} 24 | }], 25 | "oidcUrls": [], 26 | "rekorTlogUrls": [{ 27 | "url": "https://rekor.example.com", 28 | "majorApiVersion": 1, 29 | "operator": "", 30 | "validFor": {"start": "1970-01-01T01:01:01Z"} 31 | }], 32 | "tsaUrls": [], 33 | "rekorTlogConfig": {"selector": "ANY"}, 34 | "tsaConfig": {"selector": "ANY"}, 35 | }, 36 | } 37 | 38 | SUBCMD_REPLACEMENTS = { 39 | "sign-bundle": "sign", 40 | "verify-bundle": "verify", 41 | } 42 | 43 | ARG_REPLACEMENTS = { 44 | "--certificate-identity": "--cert-identity", 45 | "--certificate-oidc-issuer": "--cert-oidc-issuer", 46 | } 47 | 48 | # Trim the script name. 49 | fixed_args = sys.argv[1:] 50 | 51 | # Substitute incompatible subcommands. 52 | subcmd = fixed_args[0] 53 | if subcmd in SUBCMD_REPLACEMENTS: 54 | fixed_args[0] = SUBCMD_REPLACEMENTS[subcmd] 55 | 56 | # Build base command with optional staging argument 57 | command = ["sigstore"] 58 | if "--staging" in fixed_args: 59 | command.append("--staging") 60 | fixed_args.remove("--staging") 61 | 62 | # We may get "--trusted-root" and "--signing-config" as argument but sigstore-python 63 | # wants "--trust-config": 64 | trusted_root_path = None 65 | with suppress(ValueError): 66 | i = fixed_args.index("--trusted-root") 67 | trusted_root_path = fixed_args[i + 1] 68 | fixed_args.pop(i) 69 | fixed_args.pop(i) 70 | 71 | signing_config_path = None 72 | with suppress(ValueError): 73 | i = fixed_args.index("--signing-config") 74 | signing_config_path = fixed_args[i + 1] 75 | fixed_args.pop(i) 76 | fixed_args.pop(i) 77 | 78 | 79 | # If we did get a trustedroot, write a matching trustconfig into a temp file 80 | # Use given signingconfig if possible, otherwise use the fake one in template 81 | with NamedTemporaryFile(mode="wt") as temp_file: 82 | if trusted_root_path is not None: 83 | with open(trusted_root_path) as f: 84 | trusted_root = json.load(f) 85 | trust_config["trustedRoot"] = trusted_root 86 | if signing_config_path is not None: 87 | with open(signing_config_path) as f: 88 | signing_config = json.load(f) 89 | trust_config["signingConfig"] = signing_config 90 | 91 | json.dump(trust_config, temp_file) 92 | temp_file.flush() 93 | 94 | command.extend(["--trust-config", temp_file.name]) 95 | 96 | # Fix-up the subcommand: the conformance suite uses `verify`, but 97 | # `sigstore` requires `verify identity` for identity based verifications. 98 | subcommand, *fixed_args = fixed_args 99 | if subcommand == "sign": 100 | command.append("sign") 101 | elif subcommand == "verify": 102 | command.extend(["verify", "identity"]) 103 | else: 104 | raise ValueError(f"unsupported subcommand: {subcommand}") 105 | 106 | # Replace incompatible flags. 107 | command.extend( 108 | ARG_REPLACEMENTS[arg] if arg in ARG_REPLACEMENTS else arg for arg in fixed_args 109 | ) 110 | 111 | os.execvp("sigstore", command) 112 | -------------------------------------------------------------------------------- /sigstore/_internal/rekor/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | APIs for interacting with Rekor. 17 | """ 18 | 19 | from __future__ import annotations 20 | 21 | import base64 22 | import typing 23 | from abc import ABC, abstractmethod 24 | 25 | import rekor_types 26 | import requests 27 | from cryptography.x509 import Certificate 28 | 29 | from sigstore._utils import base64_encode_pem_cert 30 | from sigstore.dsse import Envelope 31 | from sigstore.hashes import Hashed 32 | 33 | if typing.TYPE_CHECKING: 34 | from sigstore.models import TransparencyLogEntry 35 | 36 | __all__ = [ 37 | "_hashedrekord_from_parts", 38 | ] 39 | 40 | EntryRequestBody = typing.NewType("EntryRequestBody", dict[str, typing.Any]) 41 | 42 | 43 | class RekorClientError(Exception): 44 | """ 45 | A generic error in the Rekor client. 46 | """ 47 | 48 | def __init__(self, http_error: requests.HTTPError): 49 | """ 50 | Create a new `RekorClientError` from the given `requests.HTTPError`. 51 | """ 52 | if http_error.response is not None: 53 | try: 54 | error = rekor_types.Error.model_validate_json(http_error.response.text) 55 | super().__init__(f"{error.code}: {error.message}") 56 | except Exception: 57 | super().__init__( 58 | f"Rekor returned an unknown error with HTTP {http_error.response.status_code}" 59 | ) 60 | else: 61 | super().__init__(f"Unexpected Rekor error: {http_error}") 62 | 63 | 64 | class RekorLogSubmitter(ABC): 65 | """ 66 | Abstract class to represent a Rekor log entry submitter. 67 | 68 | Intended to be implemented by RekorClient and RekorV2Client. 69 | """ 70 | 71 | @abstractmethod 72 | def create_entry( 73 | self, 74 | request: EntryRequestBody, 75 | ) -> TransparencyLogEntry: 76 | """ 77 | Submit the request to Rekor. 78 | """ 79 | pass 80 | 81 | @classmethod 82 | @abstractmethod 83 | def _build_hashed_rekord_request( 84 | self, hashed_input: Hashed, signature: bytes, certificate: Certificate 85 | ) -> EntryRequestBody: 86 | """ 87 | Construct a hashed rekord request to submit to Rekor. 88 | """ 89 | pass 90 | 91 | @classmethod 92 | @abstractmethod 93 | def _build_dsse_request( 94 | self, envelope: Envelope, certificate: Certificate 95 | ) -> EntryRequestBody: 96 | """ 97 | Construct a dsse request to submit to Rekor. 98 | """ 99 | pass 100 | 101 | 102 | # TODO: This should probably live somewhere better. 103 | def _hashedrekord_from_parts( 104 | cert: Certificate, sig: bytes, hashed: Hashed 105 | ) -> rekor_types.Hashedrekord: 106 | return rekor_types.Hashedrekord( 107 | spec=rekor_types.hashedrekord.HashedrekordV001Schema( 108 | signature=rekor_types.hashedrekord.Signature( 109 | content=base64.b64encode(sig).decode(), 110 | public_key=rekor_types.hashedrekord.PublicKey( 111 | content=base64_encode_pem_cert(cert), 112 | ), 113 | ), 114 | data=rekor_types.hashedrekord.Data( 115 | hash=rekor_types.hashedrekord.Hash( 116 | algorithm=hashed._as_hashedrekord_algorithm(), 117 | value=hashed.digest.hex(), 118 | ) 119 | ), 120 | ) 121 | ) 122 | -------------------------------------------------------------------------------- /sigstore/_internal/timestamp.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Utilities to deal with sources of signed time. 17 | """ 18 | 19 | import enum 20 | from dataclasses import dataclass 21 | from datetime import datetime 22 | 23 | import requests 24 | from rfc3161_client import ( 25 | TimestampRequestBuilder, 26 | TimeStampResponse, 27 | decode_timestamp_response, 28 | ) 29 | from rfc3161_client.base import HashAlgorithm 30 | 31 | from sigstore._internal import USER_AGENT 32 | 33 | CLIENT_TIMEOUT: int = 5 34 | 35 | 36 | class TimestampSource(enum.Enum): 37 | """Represents the source of a timestamp.""" 38 | 39 | TIMESTAMP_AUTHORITY = enum.auto() 40 | TRANSPARENCY_SERVICE = enum.auto() 41 | 42 | 43 | @dataclass 44 | class TimestampVerificationResult: 45 | """Represents a timestamp used by the Verifier. 46 | 47 | A Timestamp either comes from a Timestamping Service (RFC3161) or the Transparency 48 | Service. 49 | """ 50 | 51 | source: TimestampSource 52 | time: datetime 53 | 54 | 55 | class TimestampError(Exception): 56 | """ 57 | A generic error in the TimestampAuthority client. 58 | """ 59 | 60 | pass 61 | 62 | 63 | class TimestampAuthorityClient: 64 | """Internal client to deal with a Timestamp Authority""" 65 | 66 | def __init__(self, url: str) -> None: 67 | """ 68 | Create a new `TimestampAuthorityClient` from the given URL. 69 | """ 70 | self.url = url 71 | 72 | def request_timestamp(self, signature: bytes) -> TimeStampResponse: 73 | """ 74 | Timestamp the signature using the configured Timestamp Authority. 75 | 76 | This method generates a RFC3161 Timestamp Request and sends it to a TSA. 77 | The received response is parsed but *not* cryptographically verified. 78 | 79 | Raises a TimestampError on failure. 80 | """ 81 | # Build the timestamp request 82 | try: 83 | timestamp_request = ( 84 | TimestampRequestBuilder() 85 | .hash_algorithm(HashAlgorithm.SHA256) 86 | .data(signature) 87 | .nonce(nonce=True) 88 | .build() 89 | ) 90 | except ValueError as error: 91 | msg = f"invalid request: {error}" 92 | raise TimestampError(msg) 93 | 94 | # Use single use session to avoid potential Session thread safety issues 95 | session = requests.Session() 96 | session.headers.update( 97 | { 98 | "Content-Type": "application/timestamp-query", 99 | "User-Agent": USER_AGENT, 100 | } 101 | ) 102 | 103 | # Send it to the TSA for signing 104 | try: 105 | response = session.post( 106 | self.url, 107 | data=timestamp_request.as_bytes(), 108 | timeout=CLIENT_TIMEOUT, 109 | ) 110 | response.raise_for_status() 111 | except requests.RequestException as error: 112 | msg = f"error while sending the request to the TSA: {error}" 113 | raise TimestampError(msg) 114 | 115 | # Check that we can parse the response but do not *verify* it 116 | try: 117 | timestamp_response = decode_timestamp_response(response.content) 118 | except ValueError as e: 119 | msg = f"invalid response: {e}" 120 | raise TimestampError(msg) 121 | 122 | return timestamp_response 123 | -------------------------------------------------------------------------------- /sigstore/errors.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Exceptions. 17 | """ 18 | 19 | import sys 20 | from collections.abc import Mapping 21 | from logging import Logger 22 | from typing import Any, NoReturn 23 | 24 | 25 | class Error(Exception): 26 | """Base sigstore exception type. Defines helpers for diagnostics.""" 27 | 28 | def diagnostics(self) -> str: 29 | """Returns human-friendly error information.""" 30 | 31 | return str(self) 32 | 33 | def log_and_exit(self, logger: Logger, raise_error: bool = False) -> NoReturn: 34 | """Prints all relevant error information to stderr and exits.""" 35 | 36 | remind_verbose = ( 37 | "Raising original exception:" 38 | if raise_error 39 | else "For detailed error information, run sigstore with the `--verbose` flag." 40 | ) 41 | 42 | logger.error(f"{self.diagnostics()}\n{remind_verbose}") 43 | 44 | if raise_error: 45 | # don't want "during handling another exception" 46 | self.__suppress_context__ = True 47 | raise self 48 | 49 | sys.exit(1) 50 | 51 | 52 | class NetworkError(Error): 53 | """Raised when a connectivity-related issue occurs.""" 54 | 55 | def diagnostics(self) -> str: 56 | """Returns diagnostics for the error.""" 57 | 58 | cause_ctx = ( 59 | f""" 60 | Additional context: 61 | 62 | {self.__cause__} 63 | """ 64 | if self.__cause__ 65 | else "" 66 | ) 67 | 68 | return ( 69 | """\ 70 | A network issue occurred. 71 | 72 | Check your internet connection and try again. 73 | """ 74 | + cause_ctx 75 | ) 76 | 77 | 78 | class TUFError(Error): 79 | """Raised when a TUF error occurs.""" 80 | 81 | def __init__(self, message: str): 82 | """Constructs a `TUFError`.""" 83 | self.message = message 84 | 85 | from tuf.api import exceptions 86 | 87 | _details: Mapping[Any, str] = { 88 | exceptions.DownloadError: NetworkError().diagnostics() 89 | } 90 | 91 | def diagnostics(self) -> str: 92 | """Returns diagnostics specialized to the wrapped TUF error.""" 93 | details = TUFError._details.get( 94 | type(self.__context__), 95 | "Please check any Sigstore instance related arguments and consider " 96 | "reporting the issue at .", 97 | ) 98 | 99 | return f"""\ 100 | {self.message}. 101 | 102 | {details} 103 | """ 104 | 105 | 106 | class MetadataError(Error): 107 | """Raised when TUF metadata does not conform to the expected structure.""" 108 | 109 | def diagnostics(self) -> str: 110 | """Returns diagnostics for the error.""" 111 | return f"""{self}.""" 112 | 113 | 114 | class RootError(Error): 115 | """Raised when TUF cannot establish its root of trust.""" 116 | 117 | def diagnostics(self) -> str: 118 | """Returns diagnostics for the error.""" 119 | return """\ 120 | Unable to establish root of trust. 121 | 122 | This error may occur when the resources embedded in this distribution of sigstore-python are out of date.""" 123 | 124 | 125 | class VerificationError(Error): 126 | """ 127 | Raised whenever any phase or subcomponent of Sigstore verification fails. 128 | """ 129 | 130 | 131 | class CertValidationError(VerificationError): 132 | """ 133 | Raised when a TSA certificate chain fails to validate during Sigstore verification. 134 | 135 | This is used by CLI to hint that an incorrect Sigstore instance may have been used 136 | """ 137 | -------------------------------------------------------------------------------- /test/assets/bundle_no_checkpoint.txt.sigstore: -------------------------------------------------------------------------------- 1 | {"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.2", "verificationMaterial": {"x509CertificateChain": {"certificates": [{"rawBytes": "MIICwjCCAkegAwIBAgIUNRulROGJTUrEWvs9h68bMocfMbcwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjMwMTMwMjI0MjA4WhcNMjMwMTMwMjI1MjA4WjAAMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC4pHa0GudExSiDdn1RwUrytQUraA6CkGiiuVWnP661vvPfETx/3xr5/Q/8sy00tg7LjR5yFggFKSmM8E7Q03YAWZvORioljrokKVSLbJ7tEVtiJsraGaQYfcLcfk+Ei+o4IBSTCCAUUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBSgjmExD0FvLB3+YdpMkbc8D/aTpjAfBgNVHSMEGDAWgBRxhjCmFHxib/n31vQFGn9f/+tvrDAjBgNVHREBAf8EGTAXgRV3aWxsaWFtQHlvc3Nhcmlhbi5uZXQwLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYAKzC83GiIyeLh2CYpXnQfSDkxlgLynDPLXkNA/rKshnoAAAGGBNhGsgAABAMARzBFAiEAyCATYmUVra04RNbRWA1B9IvOQb1Oo6dWbVcmD7lpDA4CIHuU5JUEd6+mud17S2sA0I+lZdknTw3fxK3wwMhWo4BrMAoGCCqGSM49BAMDA2kAMGYCMQCvIjyVjvhvgoLWD9D2S/GKsvCXfAZXR4V+JJvBKrqNJBclJKrEWJoVEryC09nyi+cCMQDsg29gfCZGmtQo2I/1JV3eypmnnrqAX/ot3RE5O2iTVwpgVD+G+ZPBX0xb0nQBVqI="}]}, "tlogEntries": [{"logIndex": "2798447", "logId": {"keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1675118528", "inclusionPromise": {"signedEntryTimestamp": "MEUCIQCvADnaopfUq3ZHmMH5axUTAnVsFm+lRwZTzojS/S/j6gIgTBFqilaERr4ynGts13KQnhW+N+f3SZHuKEPa56TsGjk="}, "inclusionProof": {"logIndex": "2783628", "rootHash": "yI+q1pOVBmLshdZ/AMZyobBGoZSnlP7DEJKa1oih/EM=", "treeSize": "2783629", "hashes": ["M2NdF1n5XRkCCOSIfaQjxtlgrZAtEmt0gPiPc4RERIQ=", "xdOVB9j9HhIpNr3XuX1x3h3YeQbiG3C2ORYLa53P9xk=", "nijvvfATxTieswSd7U9UXoT4CGrSShbXN6vwgF0hz3o=", "i045tKzGMiRsPd+6s0019t2W/w/mPWYAMFQazJ9Z9SI=", "Te4YkwkpHbNU40NJrsh0R/dYUd7IzsjfgscYw6qulqs=", "jiYMh5IprbGRK0sVt0QT4jK3+/wJvwhwO9zm+oJ+vyI=", "oDOc4/cWh/p+nUSrwVD3sGbbXaOdfmqx8ed9TBf/6GE=", "Li4l4euEirqV/WiWSGmyrvIQoYF80WAFTcGY2SXG5tY=", "GkJkTsUxj1BshWxCshtF5bL+BVbG7ZPSzJe157aFBd4=", "P7oQEMYLmrkMhQLUuYWXJ2mL524qm2+ib1buwM/lvic=", "VwBj5hN1tw74kRJeHAQaqdSWrXWk7Zb4c1PJfrpiKNw="]}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4MDJkZDYwZmY4ODMzMzgwMmYyNTg1ZTczMDQzYmQyMWMzNDEyODVlMTk5MmZlNWIzMTc1NWUxY2FkZWFlMzBlIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1HWUNNUUN3UCtVM25QcE9RaWpScDJPK2c2UDhSQzRnZlVMK1BDTkRIcGJmekhqbHVlVWdIanNOZE5SMng2dTRkL0ZpL1ZrQ01RRFExM24vS1hmbEhRekltbG9xRGxPdkxBT2JlR3BZUzdkWUIrWEpIdGw1dnNGUW51R0FHZ1Byei92NWxrQjY2ems9IiwicHVibGljS2V5Ijp7ImNvbnRlbnQiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VOM2FrTkRRV3RsWjBGM1NVSkJaMGxWVGxKMWJGSlBSMHBVVlhKRlYzWnpPV2cyT0dKTmIyTm1UV0pqZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwTmQwMVVUWGROYWtrd1RXcEJORmRvWTA1TmFrMTNUVlJOZDAxcVNURk5ha0UwVjJwQlFVMUlXWGRGUVZsSUNrdHZXa2w2YWpCRFFWRlpSa3MwUlVWQlEwbEVXV2RCUlVNMGNFaGhNRWQxWkVWNFUybEVaRzR4VW5kVmNubDBVVlZ5WVVFMlEydEhhV2wxVmxkdVVEWUtOakYyZGxCbVJWUjRMek40Y2pVdlVTODRjM2t3TUhSbk4weHFValY1Um1kblJrdFRiVTA0UlRkUk1ETlpRVmRhZGs5U2FXOXNhbkp2YTB0V1UweGlTZ28zZEVWV2RHbEtjM0poUjJGUldXWmpUR05tYXl0RmFTdHZORWxDVTFSRFEwRlZWWGRFWjFsRVZsSXdVRUZSU0M5Q1FWRkVRV2RsUVUxQ1RVZEJNVlZrQ2twUlVVMU5RVzlIUTBOelIwRlJWVVpDZDAxRVRVSXdSMEV4VldSRVoxRlhRa0pUWjJwdFJYaEVNRVoyVEVJeksxbGtjRTFyWW1NNFJDOWhWSEJxUVdZS1FtZE9Wa2hUVFVWSFJFRlhaMEpTZUdocVEyMUdTSGhwWWk5dU16RjJVVVpIYmpsbUx5dDBkbkpFUVdwQ1owNVdTRkpGUWtGbU9FVkhWRUZZWjFKV013cGhWM2h6WVZkR2RGRkliSFpqTTA1b1kyMXNhR0pwTlhWYVdGRjNURUZaUzB0M1dVSkNRVWRFZG5wQlFrRlJVV1ZoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZDbVJYU1hWWk1qbDBUREo0ZGxveWJIVk1NamxvWkZoU2IwMUpSMHRDWjI5eVFtZEZSVUZrV2pWQloxRkRRa2gzUldWblFqUkJTRmxCUzNwRE9ETkhhVWtLZVdWTWFESkRXWEJZYmxGbVUwUnJlR3huVEhsdVJGQk1XR3RPUVM5eVMzTm9ibTlCUVVGSFIwSk9hRWR6WjBGQlFrRk5RVko2UWtaQmFVVkJlVU5CVkFwWmJWVldjbUV3TkZKT1lsSlhRVEZDT1VsMlQxRmlNVTl2Tm1SWFlsWmpiVVEzYkhCRVFUUkRTVWgxVlRWS1ZVVmtOaXR0ZFdReE4xTXljMEV3U1N0c0NscGthMjVVZHpObWVFc3pkM2ROYUZkdk5FSnlUVUZ2UjBORGNVZFRUVFE1UWtGTlJFRXlhMEZOUjFsRFRWRkRka2xxZVZacWRtaDJaMjlNVjBRNVJESUtVeTlIUzNOMlExaG1RVnBZVWpSV0swcEtka0pMY25GT1NrSmpiRXBMY2tWWFNtOVdSWEo1UXpBNWJubHBLMk5EVFZGRWMyY3lPV2RtUTFwSGJYUlJid295U1M4eFNsWXpaWGx3Ylc1dWNuRkJXQzl2ZEROU1JUVlBNbWxVVm5kd1oxWkVLMGNyV2xCQ1dEQjRZakJ1VVVKV2NVazlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifX19fQ=="}]}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "gC3WD/iDM4AvJYXnMEO9IcNBKF4Zkv5bMXVeHK3q4w4="}, "signature": "MGYCMQCwP+U3nPpOQijRp2O+g6P8RC4gfUL+PCNDHpbfzHjlueUgHjsNdNR2x6u4d/Fi/VkCMQDQ13n/KXflHQzImloqDlOvLAObeGpYS7dYB+XJHtl5vsFQnuGAGgPrz/v5lkB66zk="}} 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "sigstore" 7 | dynamic = ["version"] 8 | description = "A tool for signing Python package distributions" 9 | readme = "README.md" 10 | license = { file = "LICENSE" } 11 | authors = [ 12 | { name = "Sigstore Authors", email = "sigstore-dev@googlegroups.com" }, 13 | ] 14 | classifiers = [ 15 | "License :: OSI Approved :: Apache Software License", 16 | "Programming Language :: Python :: 3 :: Only", 17 | "Programming Language :: Python :: 3", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "Programming Language :: Python :: 3.14", 24 | "Development Status :: 5 - Production/Stable", 25 | "Intended Audience :: Developers", 26 | "Topic :: Security", 27 | "Topic :: Security :: Cryptography", 28 | ] 29 | dependencies = [ 30 | "cryptography >= 42, < 47", 31 | "id >= 1.1.0", 32 | "importlib_resources ~= 5.7; python_version < '3.11'", 33 | "pyasn1 ~= 0.6", 34 | "pydantic >= 2,< 3", 35 | "pyjwt >= 2.1", 36 | "pyOpenSSL >= 23.0.0", 37 | "requests", 38 | "rich >= 13,< 15", 39 | "rfc8785 ~= 0.1.2", 40 | "rfc3161-client >= 1.0.3,< 1.1.0", 41 | # Both sigstore-models and sigstore-rekor types are unstable 42 | # so we pin them conservatively. 43 | "sigstore-models == 0.0.5", 44 | "sigstore-rekor-types == 0.0.18", 45 | "tuf ~= 6.0", 46 | "platformdirs ~= 4.2", 47 | ] 48 | requires-python = ">=3.9" 49 | 50 | [project.scripts] 51 | sigstore = "sigstore._cli:main" 52 | 53 | [project.urls] 54 | Homepage = "https://pypi.org/project/sigstore/" 55 | Issues = "https://github.com/sigstore/sigstore-python/issues" 56 | Source = "https://github.com/sigstore/sigstore-python" 57 | Documentation = "https://sigstore.github.io/sigstore-python/" 58 | 59 | [project.optional-dependencies] 60 | test = ["pytest", "pytest-cov", "pretend", "coverage[toml]"] 61 | lint = [ 62 | "bandit", 63 | # "interrogate >= 1.7.0", 64 | "mypy ~= 1.1", 65 | # NOTE(ww): ruff is under active development, so we pin conservatively here 66 | # and let Dependabot periodically perform this update. 67 | "ruff < 0.14.10", 68 | "types-requests", 69 | "types-pyOpenSSL", 70 | ] 71 | doc = ["mkdocs-material[imaging]", "mkdocstrings-python"] 72 | dev = ["build", "bump >= 1.3.2", "sigstore[doc,test,lint]"] 73 | 74 | [tool.coverage.run] 75 | # branch coverage in addition to statement coverage. 76 | branch = true 77 | # FIXME(jl): currently overridden. see: https://pytest-cov.readthedocs.io/en/latest/config.html 78 | # include machine name, process id, and a random number in `.coverage-*` so each file is distinct. 79 | parallel = true 80 | # store relative path info for aggregation across runs with potentially differing filesystem layouts. 81 | # see: https://coverage.readthedocs.io/en/7.1.0/config.html#config-run-relative-files 82 | relative_files = true 83 | # don't attempt code coverage for the CLI entrypoints 84 | omit = ["sigstore/_cli.py"] 85 | 86 | [tool.coverage.report] 87 | exclude_lines = [ 88 | "@abc.abstractmethod", 89 | "@typing.overload", 90 | "if typing.TYPE_CHECKING", 91 | ] 92 | 93 | [tool.interrogate] 94 | # don't enforce documentation coverage for packaging, testing, the virtual 95 | # environment, or the CLI (which is documented separately). 96 | exclude = ["env", "test", "sigstore/_cli.py"] 97 | ignore-semiprivate = true 98 | ignore-private = true 99 | # Ignore nested classes for docstring coverage because we use them primarily 100 | # for pydantic model configuration. 101 | ignore-nested-classes = true 102 | fail-under = 100 103 | 104 | [tool.mypy] 105 | allow_redefinition = true 106 | check_untyped_defs = true 107 | disallow_incomplete_defs = true 108 | disallow_untyped_defs = true 109 | enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] 110 | ignore_missing_imports = true 111 | no_implicit_optional = true 112 | sqlite_cache = true 113 | strict = true 114 | strict_equality = true 115 | warn_no_return = true 116 | warn_redundant_casts = true 117 | warn_return_any = true 118 | warn_unreachable = true 119 | warn_unused_configs = true 120 | warn_unused_ignores = true 121 | plugins = ["pydantic.mypy"] 122 | 123 | [tool.bandit] 124 | exclude_dirs = ["./test"] 125 | 126 | [tool.ruff.lint] 127 | extend-select = ["I", "UP"] 128 | ignore = [ 129 | "UP007", # https://github.com/pydantic/pydantic/issues/4146 130 | "UP011", 131 | "UP015", 132 | ] 133 | -------------------------------------------------------------------------------- /sigstore/_store/https%3A%2F%2Ftuf-repo-cdn.sigstage.dev/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "aa61e09f6af7662ac686cf0c6364079f63d3e7a86836684eeced93eace3acd81", 5 | "sig": "3046022100fe72afdbab1bef70c6f461f39f5e75cf543e5277648bfab798a108a0f76f0ca002210098e1e1804b7a13bab42c063691864d85fc4bf6f5a875346b388be00f139c6118" 6 | }, 7 | { 8 | "keyid": "61f9609d2655b346fcebccd66b509d5828168d5e447110e261f0bcc8553624bc", 9 | "sig": "304502210094423ead9a7d546d703f649b408441688eb30f3279fb065b28eea05d2b36843102206f21fa2888836485964c7cb7468a16ddb6297784c50cdba03888578d7b46e0c7" 10 | }, 11 | { 12 | "keyid": "9471fbda95411d10109e467ad526082d15f14a38de54ea2ada9687ab39d8e237", 13 | "sig": "" 14 | }, 15 | { 16 | "keyid": "0374a9e18a20a2103736cb4277e2fdd7f8453642c7d9eaf4ad8aee9cf2d47bb5", 17 | "sig": "" 18 | } 19 | ], 20 | "signed": { 21 | "_type": "root", 22 | "consistent_snapshot": true, 23 | "expires": "2025-12-26T13:27:03Z", 24 | "keys": { 25 | "0374a9e18a20a2103736cb4277e2fdd7f8453642c7d9eaf4ad8aee9cf2d47bb5": { 26 | "keytype": "ecdsa", 27 | "keyval": { 28 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoxkvDOmtGEknB3M+ZkPts8joDM0X\nIH5JZwPlgC2CXs/eqOuNF8AcEWwGYRiDhV/IMlQw5bg8PLICQcgsbrDiKg==\n-----END PUBLIC KEY-----\n" 29 | }, 30 | "scheme": "ecdsa-sha2-nistp256", 31 | "x-tuf-on-ci-keyowner": "@mnm678" 32 | }, 33 | "61f9609d2655b346fcebccd66b509d5828168d5e447110e261f0bcc8553624bc": { 34 | "keytype": "ecdsa", 35 | "keyval": { 36 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE++Wv+DcLRk+mfkmlpCwl1GUi9EMh\npBUTz8K0fH7bE4mQuViGSyWA/eyMc0HvzZi6Xr0diHw0/lUPBvok214YQw==\n-----END PUBLIC KEY-----\n" 37 | }, 38 | "scheme": "ecdsa-sha2-nistp256", 39 | "x-tuf-on-ci-keyowner": "@kommendorkapten" 40 | }, 41 | "9471fbda95411d10109e467ad526082d15f14a38de54ea2ada9687ab39d8e237": { 42 | "keytype": "ecdsa", 43 | "keyval": { 44 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFHDb85JH+JYR1LQmxiz4UMokVMnP\nxKoWpaEnFCKXH8W4Fc/DfIxMnkpjCuvWUBdJXkO0aDIxwsij8TOFh2R7dw==\n-----END PUBLIC KEY-----\n" 45 | }, 46 | "scheme": "ecdsa-sha2-nistp256", 47 | "x-tuf-on-ci-keyowner": "@joshuagl" 48 | }, 49 | "aa61e09f6af7662ac686cf0c6364079f63d3e7a86836684eeced93eace3acd81": { 50 | "keytype": "ecdsa", 51 | "keyval": { 52 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEohqIdE+yTl4OxpX8ZxNUPrg3SL9H\nBDnhZuceKkxy2oMhUOxhWweZeG3bfM1T4ZLnJimC6CAYVU5+F5jZCoftRw==\n-----END PUBLIC KEY-----\n" 53 | }, 54 | "scheme": "ecdsa-sha2-nistp256", 55 | "x-tuf-on-ci-keyowner": "@jku" 56 | }, 57 | "c3479007e861445ce5dc109d9661ed77b35bbc0e3f161852c46114266fc2daa4": { 58 | "keytype": "ecdsa", 59 | "keyval": { 60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExxmEtmhF5U+i+v/6he4BcSLzCgMx\n/0qSrvDg6bUWwUrkSKS2vDpcJrhGy5fmmhRrGawjPp1ALpC3y1kqFTpXDg==\n-----END PUBLIC KEY-----\n" 61 | }, 62 | "scheme": "ecdsa-sha2-nistp256", 63 | "x-tuf-on-ci-online-uri": "gcpkms:projects/projectsigstore-staging/locations/global/keyRings/tuf-keyring/cryptoKeys/tuf-key/cryptoKeyVersions/2" 64 | } 65 | }, 66 | "roles": { 67 | "root": { 68 | "keyids": [ 69 | "aa61e09f6af7662ac686cf0c6364079f63d3e7a86836684eeced93eace3acd81", 70 | "61f9609d2655b346fcebccd66b509d5828168d5e447110e261f0bcc8553624bc", 71 | "9471fbda95411d10109e467ad526082d15f14a38de54ea2ada9687ab39d8e237", 72 | "0374a9e18a20a2103736cb4277e2fdd7f8453642c7d9eaf4ad8aee9cf2d47bb5" 73 | ], 74 | "threshold": 2 75 | }, 76 | "snapshot": { 77 | "keyids": [ 78 | "c3479007e861445ce5dc109d9661ed77b35bbc0e3f161852c46114266fc2daa4" 79 | ], 80 | "threshold": 1, 81 | "x-tuf-on-ci-expiry-period": 3650, 82 | "x-tuf-on-ci-signing-period": 365 83 | }, 84 | "targets": { 85 | "keyids": [ 86 | "aa61e09f6af7662ac686cf0c6364079f63d3e7a86836684eeced93eace3acd81", 87 | "61f9609d2655b346fcebccd66b509d5828168d5e447110e261f0bcc8553624bc", 88 | "9471fbda95411d10109e467ad526082d15f14a38de54ea2ada9687ab39d8e237", 89 | "0374a9e18a20a2103736cb4277e2fdd7f8453642c7d9eaf4ad8aee9cf2d47bb5" 90 | ], 91 | "threshold": 1 92 | }, 93 | "timestamp": { 94 | "keyids": [ 95 | "c3479007e861445ce5dc109d9661ed77b35bbc0e3f161852c46114266fc2daa4" 96 | ], 97 | "threshold": 1, 98 | "x-tuf-on-ci-expiry-period": 7, 99 | "x-tuf-on-ci-signing-period": 6 100 | } 101 | }, 102 | "spec_version": "1.0", 103 | "version": 12, 104 | "x-tuf-on-ci-expiry-period": 182, 105 | "x-tuf-on-ci-signing-period": 35 106 | } 107 | } -------------------------------------------------------------------------------- /test/assets/staging-tuf/17.targets.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "aa61e09f6af7662ac686cf0c6364079f63d3e7a86836684eeced93eace3acd81", 5 | "sig": "3045022031cbae59944160c1b9b1df859c43cf74d8c5257c32924f1c78146ccd621aae53022100cc8097664966a0f187e41643a61524613434517ec97c9a21f319752fd842e122" 6 | }, 7 | { 8 | "keyid": "61f9609d2655b346fcebccd66b509d5828168d5e447110e261f0bcc8553624bc", 9 | "sig": "30440220149fb96582721bcaf506b06465cf8df9b4b4c7847f19165eec8f7faeccc61ed8022020090a30e448e7cd71824bf0042ce9982b8882e557be343a919ffc4d825927f6" 10 | }, 11 | { 12 | "keyid": "9471fbda95411d10109e467ad526082d15f14a38de54ea2ada9687ab39d8e237", 13 | "sig": "" 14 | }, 15 | { 16 | "keyid": "0374a9e18a20a2103736cb4277e2fdd7f8453642c7d9eaf4ad8aee9cf2d47bb5", 17 | "sig": "" 18 | } 19 | ], 20 | "signed": { 21 | "_type": "targets", 22 | "delegations": { 23 | "keys": { 24 | "5e3a4021b11a425fd0a444f1670457ce5b15bbe036144f2417426f7f4b9721da": { 25 | "keytype": "ecdsa", 26 | "keyval": { 27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVfei1dXQRVeArCMcTDgxJtYg+Fs7\nV87DjhQbGlRJPyC7SW5TbNNkmvpmi4LeTv6moLVZ7T2nVqiRZbSkD+cf8w==\n-----END PUBLIC KEY-----\n" 28 | }, 29 | "scheme": "ecdsa-sha2-nistp256", 30 | "x-tuf-on-ci-online-uri": "azurekms://npm-tuf-delegate.vault.azure.net/keys/npm-tuf-delegate-2024-08/e2772c1d01ca400da571096889f1660e" 31 | } 32 | }, 33 | "roles": [ 34 | { 35 | "keyids": [ 36 | "5e3a4021b11a425fd0a444f1670457ce5b15bbe036144f2417426f7f4b9721da" 37 | ], 38 | "name": "registry.npmjs.org", 39 | "paths": [ 40 | "registry.npmjs.org/*" 41 | ], 42 | "terminating": true, 43 | "threshold": 1 44 | } 45 | ] 46 | }, 47 | "expires": "2035-06-10T18:17:38Z", 48 | "spec_version": "1.0", 49 | "targets": { 50 | "ctfe.pub": { 51 | "custom": { 52 | "sigstore": { 53 | "status": "Active", 54 | "uri": "https://ctfe.sigstage.dev/test", 55 | "usage": "CTFE" 56 | } 57 | }, 58 | "hashes": { 59 | "sha256": "bd7a6812a1f239dfddbbb19d36c7423d21510da56d466ba5018401959cd66037" 60 | }, 61 | "length": 775 62 | }, 63 | "ctfe_2022.pub": { 64 | "custom": { 65 | "sigstore": { 66 | "status": "Active", 67 | "uri": "https://ctfe.sigstage.dev/2022", 68 | "usage": "CTFE" 69 | } 70 | }, 71 | "hashes": { 72 | "sha256": "910d899c7763563095a0fe684c8477573fedc19a78586de6ecfbfd8f289f5423" 73 | }, 74 | "length": 178 75 | }, 76 | "ctfe_2022_2.pub": { 77 | "custom": { 78 | "sigstore": { 79 | "status": "Active", 80 | "uri": "https://ctfe.sigstage.dev/2022-2", 81 | "usage": "CTFE" 82 | } 83 | }, 84 | "hashes": { 85 | "sha256": "7054b4f15f969daca1c242bb9e77527abaf0b9acf9818a2a35144e4b32b20dc6" 86 | }, 87 | "length": 178 88 | }, 89 | "fulcio.crt.pem": { 90 | "custom": { 91 | "sigstore": { 92 | "status": "Active", 93 | "uri": "https://fulcio.sigstage.dev", 94 | "usage": "Fulcio" 95 | } 96 | }, 97 | "hashes": { 98 | "sha256": "0e6b0442485ad552bea5f62f11c29e2acfda35307d7538430b4cc1dbef49bff1" 99 | }, 100 | "length": 741 101 | }, 102 | "fulcio_intermediate.crt.pem": { 103 | "custom": { 104 | "sigstore": { 105 | "status": "Active", 106 | "uri": "https://fulcio.sigstage.dev", 107 | "usage": "Fulcio" 108 | } 109 | }, 110 | "hashes": { 111 | "sha256": "782868913fe13c385105ddf33e827191386f58da40a931f2075a7e27b1b6ac7b" 112 | }, 113 | "length": 790 114 | }, 115 | "rekor.pub": { 116 | "custom": { 117 | "sigstore": { 118 | "status": "Active", 119 | "uri": "https://rekor.sigstage.dev", 120 | "usage": "Rekor" 121 | } 122 | }, 123 | "hashes": { 124 | "sha256": "1d80b8f72505a43e65e6e125247cd508f61b459dc457c1d1bcb78d96e1760959" 125 | }, 126 | "length": 178 127 | }, 128 | "signing_config.json": { 129 | "hashes": { 130 | "sha256": "bf52f4aa7dc05849a6c8c760f5ae2ea4047b03b59505d9280efe02a1ec63c6e8" 131 | }, 132 | "length": 220 133 | }, 134 | "signing_config.v0.2.json": { 135 | "hashes": { 136 | "sha256": "0f395087486ba318321eda478d847962b1dd89846c7dc6e95752a6b110669393" 137 | }, 138 | "length": 1022 139 | }, 140 | "trusted_root.json": { 141 | "hashes": { 142 | "sha256": "ed6a9cf4e7c2e3297a4b5974fce0d17132f03c63512029d7aa3a402b43acab49" 143 | }, 144 | "length": 6824 145 | } 146 | }, 147 | "version": 17, 148 | "x-tuf-on-ci-expiry-period": 3650, 149 | "x-tuf-on-ci-signing-period": 365 150 | } 151 | } -------------------------------------------------------------------------------- /test/assets/bundle_no_cert_v1.txt.sigstore: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", 3 | "verificationMaterial": { 4 | "x509CertificateChain": { 5 | "certificates": [] 6 | }, 7 | "tlogEntries": [ 8 | { 9 | "logIndex": "12299864", 10 | "logId": { 11 | "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" 12 | }, 13 | "kindVersion": { 14 | "kind": "hashedrekord", 15 | "version": "0.0.1" 16 | }, 17 | "integratedTime": "1675126613", 18 | "inclusionPromise": { 19 | "signedEntryTimestamp": "MEQCIHznNDQGR9OoggiqwdIy1XL+s0DIN8CKhy7HeeoL1TBLAiAbPK3/+x/j693cYidV0kKNf6qNQQcVQiYoDmc/GPSlNA==" 20 | }, 21 | "inclusionProof": { 22 | "logIndex": "8136433", 23 | "rootHash": "Q0UPuyoLnMKq1ovXXecjQ3T9S+Si3psOoufy+q8rXXo=", 24 | "treeSize": "8136434", 25 | "hashes": [ 26 | "cdd3+Ki2g1oCwg8BSGNbjGjj1vnWCoW/bLvg6BTVewc=", 27 | "WUmDxZ3E04pjC/Boy8pxfDs0Buj3VTncmMNKpjJqsZ8=", 28 | "cVwcUBx0BZZQR36WQaQu0YM7QD7wv2rAAGdv9mbsl6A=", 29 | "upKFhQ0+3Te5YxqUVtD8w1JsYwvexrTLLRVvkiEzk4Y=", 30 | "M4k48iae5vhJ5K85ZwV5YJHrJXYwEQQxJgxeiIBR6O4=", 31 | "BaYLbIqmLbsAG8A+hzSvk3Blffx41WgBvn1c+HtvaPk=", 32 | "8SbpbSXXlm8lFn5KsRE6H+U+ZUj7cZd/JsBckNDHrY4=", 33 | "Xhw0UBkdQpGoX9d4nPr3dfz19Qxe1qKvPdbsEnuGpzQ=", 34 | "XrQ+ynp2Pi6q+yvC/JY+eAIoPPGpB2Y4JCF3sWaZQsA=", 35 | "VSPNAZ/qk9AYNPxCn3CLcArrcsg1pzFhzbkAP49OgHI=", 36 | "S232ZNlVK8JNSKTH1WWagnAGXh/tvDkQOwEKsjWoeFA=", 37 | "YWQp22x9IMWw7/Gm5RLqV6BzS5SuC8fJFGeUY+Aaf7o=", 38 | "WkrRU0sedw8Cv94N4VAIppcc8f+/qWP8nCpkSXOo0tE=" 39 | ] 40 | }, 41 | "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIxYmMzN2Q0ZmVkM2ZkOTYwYmRmOWQwOTVmODcyODNiYThhZDFhYjQ0ODFmMzhhMzRlODAwNGYwYzU2ZmZlNzhkIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1HVUNNR25uRUxCOXkzcStlUzdHSHJnYTJXZDBodWpXUTNMSjRXeVhoL1VMVWl3dXZmR0hOSzh6WFFuUUFOWmxSdEg2a1FJeEFKR0EvaXQ3T1ZiNDRBb3A2VDhETzVzK1RhamNuN0VnRng0MkRaaVdYU3JGZDBWTUl6bjRVRm80U0UxQy9VdkhhZz09IiwicHVibGljS2V5Ijp7ImNvbnRlbnQiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VONFJFTkRRV3QxWjBGM1NVSkJaMGxWU1dwV1JVRlBkazFGY1ZVMU4zaDNPR3A1ZUhRMFkwZGhVa2hKZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwTmQwMVVUWGhOUkVFeFRtcFJNMWRvWTA1TmFrMTNUVlJOZUUxRVJYZE9hbEV6VjJwQlFVMUlXWGRGUVZsSUNrdHZXa2w2YWpCRFFWRlpSa3MwUlVWQlEwbEVXV2RCUlV4dVFrazNiRlEwTUhSU1owbE9ObGt3TlVNd01YZFJSMUZuY0dsTGFWZFhNVk13WkZGQ1RXWUtVME5IU2pOWFdEQm9hVlZQU0RSMVJHMVhZbXhCZGpscmNrUlVRa2RwTURoaGJHZDBSRzB5Y25rcmNqWjJTa3d6ZVZOc1RWUnNTMkZIVHpCNWNFWkZRd295VUhGdVpFZHhjMlkxYmtKdGNrVkVUWE5ZWldoWFZpOXZORWxDVkZSRFEwRlZhM2RFWjFsRVZsSXdVRUZSU0M5Q1FWRkVRV2RsUVUxQ1RVZEJNVlZrQ2twUlVVMU5RVzlIUTBOelIwRlJWVVpDZDAxRVRVSXdSMEV4VldSRVoxRlhRa0pUUkhSQllqWlJVMGRJUjJkd2JtbHZjREJRZDNKVlYyVlBkVE5FUVdZS1FtZE9Wa2hUVFVWSFJFRlhaMEpVWmpBcmJsQldhVkZTYkhadGJ6SlBhMjlXWVV4SFRHaG9hMUI2UVhGQ1owNVdTRkpGUWtGbU9FVkpSRUZsWjFKNGFBcGlSMVkwVEcxT2FHSlhWbmxpTWpWQlpFaEthR0ZYZUhaYWJVcHdaRWhOZFZreU9YUk5RMnRIUTJselIwRlJVVUpuTnpoM1FWRkZSVWN5YURCa1NFSjZDazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVOQ2FXZFpTMHQzV1VKQ1FVaFhaVkZKUlVGblVqaENTRzlCWlVGQ01rRk9NRGtLVFVkeVIzaDRSWGxaZUd0bFNFcHNiazUzUzJsVGJEWTBNMnA1ZEM4MFpVdGpiMEYyUzJVMlQwRkJRVUpvWjFaVWFtVm5RVUZCVVVSQlJXTjNVbEZKYUFwQlRVOWhkVlVyVXpGeGJWQkJNblpOUzJ4QldHUlpiM1p0ZDNwUk5EVTVhMlV6TmxsdE1ERnZhRXRhVkVGcFFrRmhTa0pHVVdKSVNtRTVlREpGY0VsdkNraEZRVEUzTjBsWlpYVjRWVEJ5UkdGdU1sUkdWblJsWW1ORVFVdENaMmR4YUd0cVQxQlJVVVJCZDA1dVFVUkNhMEZxUW0xc1ZqWjVOVFZOTWtaMWR6QUtPVUl6SzNkdmRFTjRhVWRCT1V4TGJUZHlWVzVFUVdaWFdYTlVWek5HTjJWaE1XVkplVkJsYlZvMVlXVnVTV3BoU0RGRlEwMUhSMUF2TjJoa1FYRnRhd3BNUkhoRFRFSmthbHBtTUROV2FVTnllSEpTVEVkQldVUjNRa2w2VERaemFWQlRiV3AyZDBSUFJWSkhOV05sVFVGVlRDOTJkbEU5UFFvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PSJ9fX19" 42 | } 43 | ] 44 | }, 45 | "messageSignature": { 46 | "messageDigest": { 47 | "algorithm": "SHA2_256", 48 | "digest": "G8N9T+0/2WC9+dCV+HKDuorRq0SB84o06ABPDFb/540=" 49 | }, 50 | "signature": "MGUCMGnnELB9y3q+eS7GHrga2Wd0hujWQ3LJ4WyXh/ULUiwuvfGHNK8zXQnQANZlRtH6kQIxAJGA/it7OVb44Aop6T8DO5s+Tajcn7EgFx42DZiWXSrFd0VMIzn4UFo4SE1C/UvHag==" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sigstore/_internal/merkle.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ 16 | Utilities for verifying proof-of-inclusion within Rekor's Merkle Tree. 17 | 18 | This code is based off Google's Trillian Merkle Tree implementation which Cosign uses to validate 19 | Rekor entries. 20 | 21 | The data format for the Merkle tree nodes is described in IETF's RFC 6962. 22 | """ 23 | 24 | from __future__ import annotations 25 | 26 | import hashlib 27 | import struct 28 | import typing 29 | 30 | from sigstore.errors import VerificationError 31 | 32 | if typing.TYPE_CHECKING: 33 | from sigstore.models import TransparencyLogEntry 34 | 35 | 36 | _LEAF_HASH_PREFIX = 0 37 | _NODE_HASH_PREFIX = 1 38 | 39 | 40 | def _decomp_inclusion_proof(index: int, size: int) -> tuple[int, int]: 41 | """ 42 | Breaks down inclusion proof for a leaf at the specified |index| in a tree of the specified 43 | |size| into 2 components. The splitting point between them is where paths to leaves |index| and 44 | |size-1| diverge. 45 | 46 | Returns lengths of the bottom and upper proof parts correspondingly. The sum of the two 47 | determines the correct length of the inclusion proof. 48 | """ 49 | 50 | inner = (index ^ (size - 1)).bit_length() 51 | border = bin(index >> inner).count("1") 52 | return inner, border 53 | 54 | 55 | def _chain_inner(seed: bytes, hashes: list[bytes], log_index: int) -> bytes: 56 | """ 57 | Computes a subtree hash for a node on or below the tree's right border. Assumes |proof| hashes 58 | are ordered from lower levels to upper, and |seed| is the initial subtree/leaf hash on the path 59 | located at the specified |index| on its level. 60 | """ 61 | 62 | for i in range(len(hashes)): 63 | h = hashes[i] 64 | if (log_index >> i) & 1 == 0: 65 | seed = _hash_children(seed, h) 66 | else: 67 | seed = _hash_children(h, seed) 68 | return seed 69 | 70 | 71 | def _chain_border_right(seed: bytes, hashes: list[bytes]) -> bytes: 72 | """ 73 | Chains proof hashes along tree borders. This differs from inner chaining because |proof| 74 | contains only left-side subtree hashes. 75 | """ 76 | 77 | for h in hashes: 78 | seed = _hash_children(h, seed) 79 | return seed 80 | 81 | 82 | def _hash_children(lhs: bytes, rhs: bytes) -> bytes: 83 | pattern = f"B{len(lhs)}s{len(rhs)}s" 84 | data = struct.pack(pattern, _NODE_HASH_PREFIX, lhs, rhs) 85 | return hashlib.sha256(data).digest() 86 | 87 | 88 | def _hash_leaf(leaf: bytes) -> bytes: 89 | pattern = f"B{len(leaf)}s" 90 | data = struct.pack(pattern, _LEAF_HASH_PREFIX, leaf) 91 | return hashlib.sha256(data).digest() 92 | 93 | 94 | def verify_merkle_inclusion(entry: TransparencyLogEntry) -> None: 95 | """Verify the Merkle Inclusion Proof for a given Rekor entry.""" 96 | inclusion_proof = entry._inner.inclusion_proof 97 | 98 | # Figure out which subset of hashes corresponds to the inner and border nodes. 99 | inner, border = _decomp_inclusion_proof( 100 | inclusion_proof.log_index, inclusion_proof.tree_size 101 | ) 102 | 103 | # Check against the number of hashes. 104 | if len(inclusion_proof.hashes) != (inner + border): 105 | raise VerificationError( 106 | f"inclusion proof has wrong size: expected {inner + border}, got " 107 | f"{len(inclusion_proof.hashes)}" 108 | ) 109 | 110 | # The new entry's hash isn't included in the inclusion proof so we should calculate this 111 | # ourselves. 112 | leaf_hash: bytes = _hash_leaf(entry._inner.canonicalized_body) 113 | 114 | # Now chain the hashes belonging to the inner and border portions. We should expect the 115 | # calculated hash to match the root hash. 116 | intermediate_result: bytes = _chain_inner( 117 | leaf_hash, inclusion_proof.hashes[:inner], inclusion_proof.log_index 118 | ) 119 | 120 | calc_hash = _chain_border_right(intermediate_result, inclusion_proof.hashes[inner:]) 121 | 122 | if calc_hash != inclusion_proof.root_hash: 123 | raise VerificationError( 124 | f"inclusion proof contains invalid root hash: expected {inclusion_proof}, calculated " 125 | f"{calc_hash.hex()}" 126 | ) 127 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Sigstore Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | from pathlib import Path 17 | 18 | import pytest 19 | from id import ( 20 | AmbientCredentialError, 21 | GitHubOidcPermissionCredentialError, 22 | detect_credential, 23 | ) 24 | 25 | _ASSETS = (Path(__file__).parent / "assets").resolve() 26 | assert _ASSETS.is_dir() 27 | 28 | TEST_CLIENT_ID = "sigstore" 29 | 30 | 31 | @pytest.fixture 32 | def asset(): 33 | def _asset(name: str) -> Path: 34 | return _ASSETS / name 35 | 36 | return _asset 37 | 38 | 39 | def _has_oidc_id(): 40 | # If there are tokens manually defined for us in the environment, use them. 41 | if os.getenv("SIGSTORE_IDENTITY_TOKEN_production") or os.getenv( 42 | "SIGSTORE_IDENTITY_TOKEN_staging" 43 | ): 44 | return True 45 | 46 | try: 47 | token = detect_credential(TEST_CLIENT_ID) 48 | if token is None: 49 | return False 50 | except GitHubOidcPermissionCredentialError: 51 | # On GitHub Actions, forks do not have access to OIDC identities. 52 | # We differentiate this case from other GitHub credential errors, 53 | # since it's a case where we want to skip (i.e. return False). 54 | # 55 | # We also skip when the repo isn't our own, since downstream 56 | # regression testers (e.g. PyCA Cryptography) don't necessarily 57 | # want to give our unit tests access to an OIDC identity. 58 | return ( 59 | os.getenv("GITHUB_REPOSITORY") == "sigstore/sigstore-python" 60 | and os.getenv("GITHUB_EVENT_NAME") != "pull_request" 61 | ) 62 | except AmbientCredentialError: 63 | # If ambient credential detection raises, then we *are* in an ambient 64 | # environment but one that's been configured incorrectly. We 65 | # pass this through, so that the CI fails appropriately rather than 66 | # silently skipping the faulty tests. 67 | return True 68 | 69 | return True 70 | 71 | 72 | def _has_timestamp_authority_configured() -> bool: 73 | """ 74 | Check if there is a Timestamp Authority that has been configured 75 | """ 76 | return os.getenv("TEST_SIGSTORE_TIMESTAMP_AUTHORITY_URL") is not None 77 | 78 | 79 | def pytest_addoption(parser): 80 | parser.addoption( 81 | "--skip-online", 82 | action="store_true", 83 | help="skip tests that require network connectivity", 84 | ) 85 | parser.addoption( 86 | "--skip-staging", 87 | action="store_true", 88 | help="skip tests that require Sigstore staging infrastructure", 89 | ) 90 | 91 | 92 | def pytest_runtest_setup(item): 93 | # Do we need a network connection? 94 | online = False 95 | for mark in ["online", "staging", "production"]: 96 | if mark in item.keywords: 97 | online = True 98 | 99 | if online and item.config.getoption("--skip-online"): 100 | pytest.skip( 101 | "skipping test that requires network connectivity due to `--skip-online` flag" 102 | ) 103 | elif "ambient_oidc" in item.keywords and not _has_oidc_id(): 104 | pytest.skip("skipping test that requires an ambient OIDC credential") 105 | 106 | if "staging" in item.keywords and item.config.getoption("--skip-staging"): 107 | pytest.skip( 108 | "skipping test that requires staging infrastructure due to `--skip-staging` flag" 109 | ) 110 | 111 | if ( 112 | "timestamp_authority" in item.keywords 113 | and not _has_timestamp_authority_configured() 114 | ): 115 | pytest.skip("skipping test that requires a Timestamp Authority") 116 | 117 | 118 | def pytest_configure(config): 119 | config.addinivalue_line( 120 | "markers", "staging: mark test as requiring Sigstore staging infrastructure" 121 | ) 122 | config.addinivalue_line( 123 | "markers", 124 | "production: mark test as requiring Sigstore production infrastructure", 125 | ) 126 | config.addinivalue_line( 127 | "markers", 128 | "online: mark test as requiring network connectivity (but not a specific Sigstore infrastructure)", 129 | ) 130 | config.addinivalue_line( 131 | "markers", "ambient_oidc: mark test as requiring an ambient OIDC identity" 132 | ) 133 | config.addinivalue_line( 134 | "markers", "timestamp_authority: mark test as requiring a timestamp authority" 135 | ) 136 | -------------------------------------------------------------------------------- /test/assets/integration/Python-3.12.5.tgz.sigstore: -------------------------------------------------------------------------------- 1 | {"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", "verificationMaterial": {"x509CertificateChain": {"certificates": [{"rawBytes": "MIIC5zCCAm2gAwIBAgIUJlhDDqj05f6TwIEKO4YUQ+JeMUgwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQwODA2MjAzMjQ3WhcNMjQwODA2MjA0MjQ3WjAAMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEyfxCuMuSwrq27CDuXVog75EfL9WfcuY9Z2NmxikgeF8oMEG4mMN+ULqfNR/uM9+XzT5ideXYPYp+I9Sj/hDFv4G7dk1YYgvySUqrY7uxeUYvVSk+Y3ZiPgk9ADu6wPAzo4IBbzCCAWswDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBRXq80OR1/j1OhcQlF00SLIgjjKgDAfBgNVHSMEGDAWgBTf0+nPViQRlvmo2OkoVaLGLhhkPzAfBgNVHREBAf8EFTATgRF0aG9tYXNAcHl0aG9uLm9yZzApBgorBgEEAYO/MAEBBBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wKwYKKwYBBAGDvzABCAQdDBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wgYoGCisGAQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAZEpZPIRAAAEAwBHMEUCIQDyXwfd7XnVIidGsF1oawebvXpVrlKE5xaGoywy7KU+XQIgWiFoQP4yq0cZmuY3BWBSvjXC2LFHOt75Bgda6wN40mwwCgYIKoZIzj0EAwMDaAAwZQIwbUsZO2Go1XXJx31LtqG2wA6W8yQUMzoieEy6aSF5h9Ka3G80vJnlGIu1Gv1BgGSuAjEA8I8O6Nb7pGpejOSHEb+eKFBjHJzsAYhRc4+QaVSi2poc9UMvg01qfTtXyE/HsNgw"}]}, "tlogEntries": [{"logIndex": "118981923", "logId": {"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.1"}, "integratedTime": "1722976367", "inclusionPromise": {"signedEntryTimestamp": "MEUCIFHZzeCjijPmhyFe2nM04kIAJ7MUxBZUE5/dDN2az/YYAiEApLjBB/nZJJHYoMXhg+VfKOPmRNymdDQevt390XU6xoo="}, "inclusionProof": {"logIndex": "114818492", "rootHash": "IqAkWiiNkCTFxyYb94s81eNqaapA73SgxBxd06iPI04=", "treeSize": "114818493", "hashes": ["PMN+wGyFObrmIvP3UuG8F/K3r+S5gnVUNjTG9KRxSQI=", "IbBdNH70ZqaY+VA0Gox1yc/e7rTLDAr00GFLtAS1mM0=", "d9hP0b+P5gvyMADKIkgpYQfvzecgmGRsUAAfRXSkCvQ=", "0mWfN8v15Z2C5/2mwHGp1Tns3g82mm+8tcRMCmSlTkQ=", "N/jfjW9aFr+UzHBai8+y+VBVG5BztJO/AZcC+BxllRg=", "aVnjeQ4AARM1lia/y4Z6qLrK9b7yLU9GvzYjrhVNIGQ=", "/oczRbnX0wVoMcxf3FonUslk7JCszDsgFwdWN3hQ/PI=", "bJQEErUPH5I1mbnua8mOhyl0xwcbcK3SE1ktgx9zIZc=", "mJjriUsaYb3cYi8BAKBoYkXOb60BV9QLvVl4JkCof8s=", "FuqiuF+HGbxEPfTq5V1LEOD2xEkbOhSTHhh9OgesRec=", "gdYky8OkC3TR65e8i+N+u+FW8WwVOWv3ReiEdspNMoU=", "8QWire253mh3dyplsqOeYFI2Ar7vM6tDRPFjeMYLxck=", "uQRyyLzWiHmeVVM6L4XonE+3Lh8nQrzaUFXwRnObrjE=", "lvYqunhigwQrJ1cNg7lMmilqxS8D8HoDJPLndmoaKoM=", "1uSClB8CJleRshjxptJIRvzgY8fg8XITEtJZiU2Exwo=", "v7N3pwo5/dDC9hrWE31X4X+pIwTlvQXBlFvUC/xjjdc=", "yPZFEKyq0Jj5sObbCwB/LMHlcgQl8ux2d2IkRYWLIt8=", "ndmjFxe89oJp4z+fXcLQM1BmC+7Sp8m8VMkNIafNhYk=", "a6kLnwN4nPldqWq4OoO6Mz25ZQx1TaLMF0IbMSMVduQ=", "98enzMaC+x5oCMvIZQA5z8vu2apDMCFvE/935NfuPw8="]}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzOGRjNGUyYzI2MWQ0OWM2NjExOTYwNjZlZGJmYjcwZmRiMTZiZTRhNzljYzgyMjBjMjI0ZGZlYjU2MzZkNDA1In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1HVUNNQ1YrbnlnYlJ3RUpkRENJNk9vbCs1R0dzL2RidUdOTzdQU3dBMjl4aHBPSjArQUJRdmwxMnBHekszdXp1bEl6aGdJeEFLbWVDSFYvbUs1cGxlTi9zTHFGaWRobGE5VGFVbXNZaFp5SUJJaCs4NmVydy9GTHBQWGI1bloxOEFXTHJGUWZNQT09IiwicHVibGljS2V5Ijp7ImNvbnRlbnQiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VNMWVrTkRRVzB5WjBGM1NVSkJaMGxWU214b1JFUnhhakExWmpaVWQwbEZTMDgwV1ZWUkswcGxUVlZuZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmQwOUVRVEpOYWtGNlRXcFJNMWRvWTA1TmFsRjNUMFJCTWsxcVFUQk5hbEV6VjJwQlFVMUlXWGRGUVZsSUNrdHZXa2w2YWpCRFFWRlpSa3MwUlVWQlEwbEVXV2RCUlhsbWVFTjFUWFZUZDNKeE1qZERSSFZZVm05bk56VkZaa3c1VjJaamRWazVXakpPYlhocGEyY0taVVk0YjAxRlJ6UnRUVTRyVlV4eFprNVNMM1ZOT1N0WWVsUTFhV1JsV0ZsUVdYQXJTVGxUYWk5b1JFWjJORWMzWkdzeFdWbG5kbmxUVlhGeVdUZDFlQXBsVlZsMlZsTnJLMWt6V21sUVoyczVRVVIxTm5kUVFYcHZORWxDWW5wRFEwRlhjM2RFWjFsRVZsSXdVRUZSU0M5Q1FWRkVRV2RsUVUxQ1RVZEJNVlZrQ2twUlVVMU5RVzlIUTBOelIwRlJWVVpDZDAxRVRVSXdSMEV4VldSRVoxRlhRa0pTV0hFNE1FOVNNUzlxTVU5b1kxRnNSakF3VTB4SloycHFTMmRFUVdZS1FtZE9Wa2hUVFVWSFJFRlhaMEpVWmpBcmJsQldhVkZTYkhadGJ6SlBhMjlXWVV4SFRHaG9hMUI2UVdaQ1owNVdTRkpGUWtGbU9FVkdWRUZVWjFKR01BcGhSemwwV1ZoT1FXTkliREJoUnpsMVRHMDVlVnA2UVhCQ1oyOXlRbWRGUlVGWlR5OU5RVVZDUWtKMGIyUklVbmRqZW05MlRESkdhbGt5T1RGaWJsSjZDa3h0WkhaaU1tUnpXbE0xYW1JeU1IZExkMWxMUzNkWlFrSkJSMFIyZWtGQ1EwRlJaRVJDZEc5a1NGSjNZM3B2ZGt3eVJtcFpNamt4WW01U2VreHRaSFlLWWpKa2MxcFROV3BpTWpCM1oxbHZSME5wYzBkQlVWRkNNVzVyUTBKQlNVVm1RVkkyUVVoblFXUm5SR1JRVkVKeGVITmpVazF0VFZwSWFIbGFXbnBqUXdwdmEzQmxkVTQwT0hKbUswaHBia3RCVEhsdWRXcG5RVUZCV2tWd1dsQkpVa0ZCUVVWQmQwSklUVVZWUTBsUlJIbFlkMlprTjFodVZrbHBaRWR6UmpGdkNtRjNaV0oyV0hCV2NteExSVFY0WVVkdmVYZDVOMHRWSzFoUlNXZFhhVVp2VVZBMGVYRXdZMXB0ZFZrelFsZENVM1pxV0VNeVRFWklUM1EzTlVKblpHRUtObmRPTkRCdGQzZERaMWxKUzI5YVNYcHFNRVZCZDAxRVlVRkJkMXBSU1hkaVZYTmFUekpIYnpGWVdFcDRNekZNZEhGSE1uZEJObGM0ZVZGVlRYcHZhUXBsUlhrMllWTkdOV2c1UzJFelJ6Z3dka3B1YkVkSmRURkhkakZDWjBkVGRVRnFSVUU0U1RoUE5rNWlOM0JIY0dWcVQxTklSV0lyWlV0R1FtcElTbnB6Q2tGWmFGSmpOQ3RSWVZaVGFUSndiMk01VlUxMlp6QXhjV1pVZEZoNVJTOUljMDVuZHdvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PSJ9fX19"}]}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "ONxOLCYdScZhGWBm7b+3D9sWvkp5zIIgwiTf61Y21AU="}, "signature": "MGUCMCV+nygbRwEJdDCI6Ool+5GGs/dbuGNO7PSwA29xhpOJ0+ABQvl12pGzK3uzulIzhgIxAKmeCHV/mK5pleN/sLqFidhla9TaUmsYhZyIBIh+86erw/FLpPXb5nZ18AWLrFQfMA=="}} 2 | -------------------------------------------------------------------------------- /test/assets/staging-rekor-v2.txt.sigstore.json: -------------------------------------------------------------------------------- 1 | {"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIICyzCCAlCgAwIBAgIUJc/6ox+xb+Cmb5UVrFhdu5jiMzIwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNjA5MTE1NzM1WhcNMjUwNjA5MTIwNzM1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvoYb1h6sjlOR276rCjnPc/PgZtTahLzmf32f08PZ/2eWr4q979itVw1PG8IhcK3E2ZiihegXEgh4mPkkMn78BKOCAW8wggFrMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUsoZlvpIKgR6WlgezvkD6xzHypcMwHwYDVR0jBBgwFoAUcYYwphR8Ym/599b0BRp/X//rb6wwGQYDVR0RAQH/BA8wDYELamt1QGdvdG8uZmkwLAYKKwYBBAGDvzABAQQeaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMC4GCisGAQQBg78wAQgEIAweaHR0cHM6Ly9naXRodWIuY29tL2xvZ2luL29hdXRoMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYAKzC83GiIyeLh2CYpXnQfSDkxlgLynDPLXkNA/rKshnoAAAGXVI2aFgAABAMARzBFAiBDHRpKGTpiU3Nx28XgewlvzbMt/ug6ipN8Xj9tryWbwQIhAP/3Cngo4St1nAggkflowySL0fPYg/QDcJKE6XceON3WMAoGCCqGSM49BAMDA2kAMGYCMQCfyQmcNbg2g5PD9Jrb9yOS+vEwwThoY2YDoptDzhJvOxNYLek6DRwCAjZ4SqeTwmQCMQDD3lXotLGsn/CJxGlEiVaF2+z3SKb+bLGGKQATHPkZ/XHvLI2cAdVhcTYeEn36shE="}, "tlogEntries": [{"logIndex": "645", "logId": {"keyId": "8w1amZ2S5mJIQkQmPxdMuOrL/oJkvFg9MnQXmeOCXck="}, "kindVersion": {"kind": "hashedrekord", "version": "0.0.2"}, "inclusionProof": {"logIndex": "645", "rootHash": "kNum4JmdViJPfZLMRB3xPi6flATj2JzJSiF+1pQDzNQ=", "treeSize": "646", "hashes": ["eTqr8nE8VGEREKQ2MDQeD+zKHTJERE6iNw0tG1G+WbQ=", "wzbEsO0X3AWHadlgJZx7yhJdRVEZ2dEY21okXQ6UIi4=", "QMesRTEZdIgthOEinYE/9J7wGv+VmArDZTICj9POmhY=", "UNUMG62rMwoqCqFKknh4R5Ubkf5Z6dj+Pk0m/1xu8uo="], "checkpoint": {"envelope": "log2025-alpha1.rekor.sigstage.dev\n646\nkNum4JmdViJPfZLMRB3xPi6flATj2JzJSiF+1pQDzNQ=\n\n\u2014 log2025-alpha1.rekor.sigstage.dev 8w1amQA0XB55lIjvC/rvbpawQn9lp2R5TSkvqoNJuxcH9Ii05Ddi66xN8z5ZE6GsK2MkvgNZuqnZ5RtHbq2kpt/B8AE=\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJoYXNoZWRSZWtvcmRWMDAyIjp7ImRhdGEiOnsiYWxnb3JpdGhtIjoiU0hBMl8yNTYiLCJkaWdlc3QiOiJGZlp5UmhGWklidDhIZURuNmVrblhJQVczQ1ZLREFDWWlKUkxmdE5rU3FvPSJ9LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQVo2VDhBVVpTQ0JaYUtKa3NMbFNpbE5xRUVPdDRaeUdNR2VwVXBLcDdWR0FpRUFzL1gwa01KVG5FT3V6L0RMV3hUTDR3QlZOa2lXSVVERjM2RUVENzAzOTZBPSIsInZlcmlmaWVyIjp7ImtleURldGFpbHMiOiJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsIng1MDlDZXJ0aWZpY2F0ZSI6eyJyYXdCeXRlcyI6Ik1JSUN5ekNDQWxDZ0F3SUJBZ0lVSmMvNm94K3hiK0NtYjVVVnJGaGR1NWppTXpJd0NnWUlLb1pJemowRUF3TXdOekVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SNHdIQVlEVlFRREV4VnphV2R6ZEc5eVpTMXBiblJsY20xbFpHbGhkR1V3SGhjTk1qVXdOakE1TVRFMU56TTFXaGNOTWpVd05qQTVNVEl3TnpNMVdqQUFNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUV2b1liMWg2c2psT1IyNzZyQ2puUGMvUGdadFRhaEx6bWYzMmYwOFBaLzJlV3I0cTk3OWl0VncxUEc4SWhjSzNFMlppaWhlZ1hFZ2g0bVBra01uNzhCS09DQVc4d2dnRnJNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBekFkQmdOVkhRNEVGZ1FVc29abHZwSUtnUjZXbGdlenZrRDZ4ekh5cGNNd0h3WURWUjBqQkJnd0ZvQVVjWVl3cGhSOFltLzU5OWIwQlJwL1gvL3JiNnd3R1FZRFZSMFJBUUgvQkE4d0RZRUxhbXQxUUdkdmRHOHVabWt3TEFZS0t3WUJCQUdEdnpBQkFRUWVhSFIwY0hNNkx5OW5hWFJvZFdJdVkyOXRMMnh2WjJsdUwyOWhkWFJvTUM0R0Npc0dBUVFCZzc4d0FRZ0VJQXdlYUhSMGNITTZMeTluYVhSb2RXSXVZMjl0TDJ4dloybHVMMjloZFhSb01JR0tCZ29yQmdFRUFkWjVBZ1FDQkh3RWVnQjRBSFlBS3pDODNHaUl5ZUxoMkNZcFhuUWZTRGt4bGdMeW5EUExYa05BL3JLc2hub0FBQUdYVkkyYUZnQUFCQU1BUnpCRkFpQkRIUnBLR1RwaVUzTngyOFhnZXdsdnpiTXQvdWc2aXBOOFhqOXRyeVdid1FJaEFQLzNDbmdvNFN0MW5BZ2drZmxvd3lTTDBmUFlnL1FEY0pLRTZYY2VPTjNXTUFvR0NDcUdTTTQ5QkFNREEya0FNR1lDTVFDZnlRbWNOYmcyZzVQRDlKcmI5eU9TK3ZFd3dUaG9ZMllEb3B0RHpoSnZPeE5ZTGVrNkRSd0NBalo0U3FlVHdtUUNNUUREM2xYb3RMR3NuL0NKeEdsRWlWYUYyK3ozU0tiK2JMR0dLUUFUSFBrWi9YSHZMSTJjQWRWaGNUWWVFbjM2c2hFPSJ9fX19fX0="}], "timestampVerificationData": {"rfc3161Timestamps": [{"signedTimestamp": "MIIE6zADAgEAMIIE4gYJKoZIhvcNAQcCoIIE0zCCBM8CAQMxDTALBglghkgBZQMEAgEwgcMGCyqGSIb3DQEJEAEEoIGzBIGwMIGtAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgOjYPmS6Qixa9OQqdXWQMPN66194GUnV3liEVd7cbW8oCFQDuYcF6Hx3Wi2sgxpmG+IG2KlvUKRgPMjAyNTA2MDkxMTU3MzhaMAMCAQECCQCbf5cNt4JRDqAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgggITMIICDzCCAZagAwIBAgIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTAzMjgwOTE0MDZaFw0zNTAzMjYwODE0MDZaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx1v5F3HpD9egHuknpBFlRz7QBRDJu4aeVzt9zJLRY0lvmx1lF7WBM2c9AN8ZGPQsmDqHlJN2R/7+RxLkvlLzkc19IOx38t7mGGEcB7agUDdCF/Ky3RTLSK0Xo/0AgHQdo2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFKj8ZPYo3i7mO3NPVIxSxOGc3VOlMB8GA1UdIwQYMBaAFDsgRlletTJNRzDObmPuc3RH8gR9MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2cAMGQCMESvVS6GGtF33+J19TfwENWJXjRv4i0/HQFwLUSkX6TfV7g0nG8VnqNHJLvEpAtOjQIwUD3uywTXorQP1DgbV09rF9Yen+CEqs/iEpieJWPst280SSOZ5Na+dyPVk9/8SFk6MYIB3DCCAdgCAQEwUTA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzALBglghkgBZQMEAgGggfwwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA2MDkxMTU3MzhaMC8GCSqGSIb3DQEJBDEiBCA6qJ7IlNaN4uuHegN2O+NsWY5kB6sw8E/Q3H3arU8jmDCBjgYLKoZIhvcNAQkQAi8xfzB9MHsweQQgBvT/4Ef+s1mZtzOw16MjUBz8GOTAM2aoRdd1NudLJ0QwVTA9pDswOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwIEaDBmAjEA9vHFXY/Ia5L2g8F7ipZpiJOgDoAau7L+UkE5c1cCM2FYDZN1QQzWjXGj1CwQMOcuAjEAtBIxQiiecOzOkFo1Bj0n9xkIjyErSBT+P3P6OWgwdivDosxQCTMF7iNeI7wgFQxw"}]}}, "messageSignature": {"messageDigest": {"algorithm": "SHA2_256", "digest": "FfZyRhFZIbt8HeDn6eknXIAW3CVKDACYiJRLftNkSqo="}, "signature": "MEUCIAZ6T8AUZSCBZaKJksLlSilNqEEOt4ZyGMGepUpKp7VGAiEAs/X0kMJTnEOuz/DLWxTL4wBVNkiWIUDF36EED70396A="}} 2 | -------------------------------------------------------------------------------- /test/assets/bundle_v3_no_signed_time.txt.sigstore.json: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", 3 | "verificationMaterial": { 4 | "certificate": { 5 | "rawBytes": "MIIC1DCCAlugAwIBAgIUXgKINnY7rbT5gHmj9yeiZXGg3rkwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQxMjEwMjE0MTI1WhcNMjQxMjEwMjE1MTI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4ul4I08UFGizCla6qRUGFiwEPNsFRnvBPDvQ4ViJ+Q83HOlYWWxCAjoJpGd9FWtyxTPKDsG0n4t6Mr+jSwz22KOCAXowggF2MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUZ7cNLqQlnKAXnf6jmb9cv70dppgwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wIwYDVR0RAQH/BBkwF4EVd2lsbGlhbUB5b3NzYXJpYW4ubmV0MCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDAuBgorBgEEAYO/MAEIBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDCBiwYKKwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABk7KFEeIAAAQDAEgwRgIhAOeS6rR2aksHhN9Rxbx+ANuAlXhP4vTPKMLBHd6JAm4lAiEAx+/kzKJ2SxSCAYm582jKeAa1LCVmUaO85FO2WTV7MYEwCgYIKoZIzj0EAwMDZwAwZAIwDXrVAPgutWZWPfE3QWy/4gG/PbMbYUfqNsEpQEeMm8GeraZN3zffzw16FFhWsMbXAjApxDNgKvmztHOKStyvmOXPiJCixzx/gLFbhVn7Q+qY6vjC83B0XgPsyQ2T0i8Ldzg=" 6 | }, 7 | "tlogEntries": [ 8 | { 9 | "logIndex": "154562758", 10 | "logId": { 11 | "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" 12 | }, 13 | "kindVersion": { 14 | "kind": "hashedrekord", 15 | "version": "0.0.1" 16 | }, 17 | "integratedTime": "1733866885", 18 | "inclusionProof": { 19 | "logIndex": "32658496", 20 | "rootHash": "IbC2+n9aYhFlm5nFwkp+j7/Hc9XuYWxyE5OlXIoIijY=", 21 | "treeSize": "32658497", 22 | "hashes": [ 23 | "CVvwGSdkZ5FUDnltf3Me3nXyco4G9mwTsYbIxz0RS+U=", 24 | "DJrEpKAKhEPhZ5aKvlaRImFebTv5tc17rsfOkhSS6fY=", 25 | "tsYfO+hUsl4KKY+qsPx/k4NzOzE5zWRsc4Ufgn4oh/U=", 26 | "ZjSpDQt5kIQfJd6B/BDNWLRhYOGwnlxE6pT4JJaiD5s=", 27 | "OMoiMVnwD3sG6Cc6HCg+ySmqBAH1nn0mA5+tjFxiyeg=", 28 | "gSWKL2k1ZGZm45C8hSdNwWan8qOrszl5X7Ws56h+FVM=", 29 | "R7hO1X+KgSw8Oojd8i2+G3BzBYztkRBE6LpYSXPg33U=", 30 | "oOecFfN3YqDOkbijS/ej1WF5Da/Gt/AZNhbwE9uoOE8=", 31 | "4lUF0YOu9XkIDXKXA0wMSzd6VeDY3TZAgmoOeWmS2+Y=", 32 | "gf+9m552B3PnkWnO0o4KdVvjcT3WVHLrCbf1DoVYKFw=" 33 | ], 34 | "checkpoint": { 35 | "envelope": "rekor.sigstore.dev - 1193050959916656506\n32658497\nIbC2+n9aYhFlm5nFwkp+j7/Hc9XuYWxyE5OlXIoIijY=\n\n\u2014 rekor.sigstore.dev wNI9ajBGAiEAgjFaCZlVvHUnDgxLf+4XjN6ahWNkkKh9QFTOqHBpyw4CIQDmy4JQs+2BKtvheo/HQogyhh5EYGYZeBDdRvyyX1fg+w==\n" 36 | } 37 | }, 38 | "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjYTZkZTk5YTExZDNkMzgwNTZkODM4YzdkYzlhMjNhMTFhMGM4MWJjYWNlMGQxMWVhYTMwMWEyZmZiNDgyYzQyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUMzc2pYZVZoTHRqbE13dG0yRE5CYVdVaFBWOVJ1U1dsWW1EcHQzRzFQVW5RSWhBUElxRHUwTVkza1FtelE2QmswS2VSTW5mQ3Y0VVdEVU5jclRnN0cyYjdzTCIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTXhSRU5EUVd4MVowRjNTVUpCWjBsVldHZExTVTV1V1RkeVlsUTFaMGh0YWpsNVpXbGFXRWRuTTNKcmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJlRTFxUlhkTmFrVXdUVlJKTVZkb1kwNU5hbEY0VFdwRmQwMXFSVEZOVkVreFYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVUwZFd3MFNUQTRWVVpIYVhwRGJHRTJjVkpWUjBacGQwVlFUbk5HVW01MlFsQkVkbEVLTkZacFNpdFJPRE5JVDJ4WlYxZDRRMEZxYjBwd1IyUTVSbGQwZVhoVVVFdEVjMGN3YmpSME5rMXlLMnBUZDNveU1rdFBRMEZZYjNkblowWXlUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZhTjJOT0NreHhVV3h1UzBGWWJtWTJhbTFpT1dOMk56QmtjSEJuZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBsM1dVUldVakJTUVZGSUwwSkNhM2RHTkVWV1pESnNjMkpIYkdoaVZVSTFZak5PZWxsWVNuQlpWelIxWW0xV01FMURkMGREYVhOSFFWRlJRZ3BuTnpoM1FWRkZSVWh0YURCa1NFSjZUMms0ZGxveWJEQmhTRlpwVEcxT2RtSlRPWE5pTW1Sd1ltazVkbGxZVmpCaFJFRjFRbWR2Y2tKblJVVkJXVTh2Q2sxQlJVbENRMEZOU0cxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YzJJeVpIQmlhVGwyV1ZoV01HRkVRMEpwZDFsTFMzZFpRa0pCU0ZjS1pWRkpSVUZuVWpsQ1NITkJaVkZDTTBGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQUVVGQlFncHJOMHRHUldWSlFVRkJVVVJCUldkM1VtZEphRUZQWlZNMmNsSXlZV3R6U0doT09WSjRZbmdyUVU1MVFXeFlhRkEwZGxSUVMwMU1Ra2hrTmtwQmJUUnNDa0ZwUlVGNEt5OXJla3RLTWxONFUwTkJXVzAxT0RKcVMyVkJZVEZNUTFadFZXRlBPRFZHVHpKWFZGWTNUVmxGZDBObldVbExiMXBKZW1vd1JVRjNUVVFLV25kQmQxcEJTWGRFV0hKV1FWQm5kWFJYV2xkUVprVXpVVmQ1THpSblJ5OVFZazFpV1ZWbWNVNXpSWEJSUldWTmJUaEhaWEpoV2s0emVtWm1lbmN4TmdwR1JtaFhjMDFpV0VGcVFYQjRSRTVuUzNadGVuUklUMHRUZEhsMmJVOVlVR2xLUTJsNGVuZ3ZaMHhHWW1oV2JqZFJLM0ZaTm5acVF6Z3pRakJZWjFCekNubFJNbFF3YVRoTVpIcG5QUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=" 39 | } 40 | ], 41 | "timestampVerificationData": {} 42 | }, 43 | "messageSignature": { 44 | "messageDigest": { 45 | "algorithm": "SHA2_256", 46 | "digest": "ym3pmhHT04BW2DjH3JojoRoMgbys4NEeqjAaL/tILEI=" 47 | }, 48 | "signature": "MEYCIQC3sjXeVhLtjlMwtm2DNBaWUhPV9RuSWlYmDpt3G1PUnQIhAPIqDu0MY3kQmzQ6Bk0KeRMnfCv4UWDUNcrTg7G2b7sL" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/assets/bundle_v3.txt.sigstore: -------------------------------------------------------------------------------- 1 | { 2 | "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", 3 | "verificationMaterial": { 4 | "certificate": { 5 | "rawBytes": "MIIC1DCCAlqgAwIBAgIUO3tlVbLtvLPp+6zGOtep1SPkRigwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjQwNDAyMTkxOTA5WhcNMjQwNDAyMTkyOTA5WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENdrfpgNU1Rjmz+j65rpJWKc08ruKYy4FX7nmmOnbauFZimsQXrdyDSXKNRtEXX4X3t/Amt+euwPDBh+eq7BCnqOCAXkwggF1MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUGRlBhD0wvzAfLb2dMWOgPrrJuRkwHwYDVR0jBBgwFoAUcYYwphR8Ym/599b0BRp/X//rb6wwIwYDVR0RAQH/BBkwF4EVd2lsbGlhbUB5b3NzYXJpYW4ubmV0MCwGCisGAQQBg78wAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDAuBgorBgEEAYO/MAEIBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDCBigYKKwYBBAHWeQIEAgR8BHoAeAB2ACswvNxoiMni4dgmKV50H0g5MZYC8pwzy15DQP6yrIZ6AAABjqBAQZ4AAAQDAEcwRQIgeWUmtnD0MFUl5kkX7nbMdLWCsDGIPzdIlN+WaZF0TmkCIQC7+31saqrFe9RmduVZ2dxXhUPrajltuSDHb1vSGOcuHjAKBggqhkjOPQQDAwNoADBlAjEAn2+uuLHsnH9Db7zkIdF65YhiXbgMMF//iHc+B/QETK0HYVcOPTK3p46FUzXFD6xrAjAO2hrkfjBKANKjJJxHV3FVrtS+TR0GCP0HzC3D7Br95TXzfO7+j4Dd8/N/aAr6Ibs=" 6 | }, 7 | "tlogEntries": [ 8 | { 9 | "logIndex": "25915956", 10 | "logId": { 11 | "keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY=" 12 | }, 13 | "kindVersion": { 14 | "kind": "hashedrekord", 15 | "version": "0.0.1" 16 | }, 17 | "integratedTime": "1712085549", 18 | "inclusionPromise": { 19 | "signedEntryTimestamp": "MEYCIQD2KXW1NppUhkPPzGR8NrUIyN+MzZSSqGZQO7CzvhSnYgIhAO9AHzjbsr1AHXRHmEpdPZcoFHEwwMTgfqwjoOXVMmqN" 20 | }, 21 | "inclusionProof": { 22 | "logIndex": "25901137", 23 | "rootHash": "iGAoHccJIyFemFxmEftti2YC8hvPqixBi5y1EyvfF4c=", 24 | "treeSize": "25901138", 25 | "hashes": [ 26 | "UHUr+lvxENI+G902oEsFW5ovQILgqO9mUWWxvvwHZZc=", 27 | "IcMBsbH3GRW8FX2CiL/ljMb45vzmENmhp5Yp/7IW998=", 28 | "SxC6nr0zP+a6kWb6nO2fmEtz8BYAbqEXc+dsqGLdRPM=", 29 | "sppZRSz/vdeLlavgvICrXHLeReMTJw98bs9HJ0I8WnE=", 30 | "c8lCSuBS6MzrRnt6OiyYjqhTyxUI/22gpVB7dblfDis=", 31 | "eJk64J6cMpIljPSX/72kH0kiIeElyypQm5vJ2gMMyHw=", 32 | "hbIK+jmAwQjU7Yi3iKvnfR1u7GNippk7QsRwJXIuRaw=", 33 | "tpHWIEB2vNU5ZmC68dj1Hh9cwQK083ozogA6zJ3cJ8A=", 34 | "arvuzAipUJ14nDj14OBlvkMSicjdsE9Eus3hq9Jpqdk=", 35 | "Edul4W41O3EfxKEEMlX2nW0+GTgCv00nGmcpwhALgVA=", 36 | "rBWB37+HwkTZgDv0rMtGBUoDI0UZqcgDZp48M6CaUlA=" 37 | ], 38 | "checkpoint": { 39 | "envelope": "rekor.sigstage.dev - 8050909264565447525\n25901138\niGAoHccJIyFemFxmEftti2YC8hvPqixBi5y1EyvfF4c=\n\n\u2014 rekor.sigstage.dev 0y8wozBFAiAMJJLbnNOnmizMbVBz9/A/qnMK15BudWoZkuE+obD6CAIhAJf6A3h2iOpuhz/duEhG3fbAQG9PXln4wXPHFBT5wT1a\n" 40 | } 41 | }, 42 | "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI1ZTZhZTlkZTU4YzExNzdiZWE2MTViNGZjYmZiMmZkNjg4ZThjNGI1MWMyZTU2YjZhMzhlODE3ODMzZWMyNGEyIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRFFTSmk5YWVydFFobVQrY2UxaktOZENlNEtTY3NLR3E5ZlBtMzQyMkRCU0FpRUFoajFzeFo5Nm9ySVRzUXh5TUxJRFJKaW1wb3kxSjFNeWZsY1FWd2tremhzPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTXhSRU5EUVd4eFowRjNTVUpCWjBsVlR6TjBiRlppVEhSMlRGQndLelo2UjA5MFpYQXhVMUJyVW1sbmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFJkMDVFUVhsTlZHdDRUMVJCTlZkb1kwNU5hbEYzVGtSQmVVMVVhM2xQVkVFMVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZPWkhKbWNHZE9WVEZTYW0xNksybzJOWEp3U2xkTFl6QTRjblZMV1hrMFJsZzNibTBLYlU5dVltRjFSbHBwYlhOUldISmtlVVJUV0V0T1VuUkZXRmcwV0ROMEwwRnRkQ3RsZFhkUVJFSm9LMlZ4TjBKRGJuRlBRMEZZYTNkblowWXhUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZIVW14Q0NtaEVNSGQyZWtGbVRHSXlaRTFYVDJkUWNuSktkVkpyZDBoM1dVUldVakJxUWtKbmQwWnZRVlZqV1ZsM2NHaFNPRmx0THpVNU9XSXdRbEp3TDFndkwzSUtZalozZDBsM1dVUldVakJTUVZGSUwwSkNhM2RHTkVWV1pESnNjMkpIYkdoaVZVSTFZak5PZWxsWVNuQlpWelIxWW0xV01FMURkMGREYVhOSFFWRlJRZ3BuTnpoM1FWRkZSVWh0YURCa1NFSjZUMms0ZGxveWJEQmhTRlpwVEcxT2RtSlRPWE5pTW1Sd1ltazVkbGxZVmpCaFJFRjFRbWR2Y2tKblJVVkJXVTh2Q2sxQlJVbENRMEZOU0cxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZbE01YzJJeVpIQmlhVGwyV1ZoV01HRkVRMEpwWjFsTFMzZFpRa0pCU0ZjS1pWRkpSVUZuVWpoQ1NHOUJaVUZDTWtGRGMzZDJUbmh2YVUxdWFUUmtaMjFMVmpVd1NEQm5OVTFhV1VNNGNIZDZlVEUxUkZGUU5ubHlTVm8yUVVGQlFncHFjVUpCVVZvMFFVRkJVVVJCUldOM1VsRkpaMlZYVlcxMGJrUXdUVVpWYkRWcmExZzNibUpOWkV4WFEzTkVSMGxRZW1SSmJFNHJWMkZhUmpCVWJXdERDa2xSUXpjck16RnpZWEZ5Um1VNVVtMWtkVlphTW1SNFdHaFZVSEpoYW14MGRWTkVTR0l4ZGxOSFQyTjFTR3BCUzBKblozRm9hMnBQVUZGUlJFRjNUbThLUVVSQ2JFRnFSVUZ1TWl0MWRVeEljMjVJT1VSaU4zcHJTV1JHTmpWWmFHbFlZbWROVFVZdkwybElZeXRDTDFGRlZFc3dTRmxXWTA5UVZFc3pjRFEyUmdwVmVsaEdSRFo0Y2tGcVFVOHlhSEpyWm1wQ1MwRk9TMnBLU25oSVZqTkdWbkowVXl0VVVqQkhRMUF3U0hwRE0wUTNRbkk1TlZSWWVtWlBOeXRxTkVSa0NqZ3ZUaTloUVhJMlNXSnpQUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0=" 43 | } 44 | ] 45 | }, 46 | "messageSignature": { 47 | "messageDigest": { 48 | "algorithm": "SHA2_256", 49 | "digest": "Xmrp3ljBF3vqYVtPy/sv1ojoxLUcLla2o46BeDPsJKI=" 50 | }, 51 | "signature": "MEUCIDQSJi9aertQhmT+ce1jKNdCe4KScsKGq9fPm3422DBSAiEAhj1sxZ96orITsQxyMLIDRJimpoy1J1MyflcQVwkkzhs=" 52 | } 53 | } 54 | --------------------------------------------------------------------------------