pax_global_header00006660000000000000000000000064147750332550014525gustar00rootroot0000000000000052 comment=38bb98697005cdc5c092f031594c0e45d039f4a0 cosign-2.5.0/000077500000000000000000000000001477503325500130135ustar00rootroot00000000000000cosign-2.5.0/.gitattributes000066400000000000000000000003041477503325500157030ustar00rootroot00000000000000# This file is documented at https://git-scm.com/docs/gitattributes. # Linguist-specific attributes are documented at # https://github.com/github/linguist. doc/cosign*.md linguist-generated=true cosign-2.5.0/.github/000077500000000000000000000000001477503325500143535ustar00rootroot00000000000000cosign-2.5.0/.github/dependabot.yml000066400000000000000000000017701477503325500172100ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 10 groups: gomod: update-types: - "patch" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 10 groups: actions: update-types: - "minor" - "patch" cosign-2.5.0/.github/workflows/000077500000000000000000000000001477503325500164105ustar00rootroot00000000000000cosign-2.5.0/.github/workflows/build.yaml000066400000000000000000000046041477503325500203770ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: CI-Container-Build on: push: paths: - '**' - '!**.md' - '!doc/**' - '!**.txt' - '!images/**' - '!LICENSE' - 'test/**' branches: - main - release-* permissions: {} jobs: build: name: build runs-on: ubuntu-latest if: github.repository == 'sigstore/cosign' permissions: id-token: write contents: read packages: write steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1 - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true # will use the latest release available for ko - uses: ko-build/setup-ko@d982fec422852203cfb2053a8ec6ad302280d04d # v0.8 - name: Set up Cloud SDK uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935 # v2.1.8 with: workload_identity_provider: 'projects/498091336538/locations/global/workloadIdentityPools/githubactions/providers/sigstore-cosign' service_account: 'github-actions@projectsigstore.iam.gserviceaccount.com' - name: creds run: gcloud auth configure-docker --quiet - name: Login to GitHub Container Registry uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: containers-cosign run: make sign-ci-containers env: KO_PREFIX: ghcr.io/sigstore/cosign/cosign/ci COSIGN_PASSWORD: "${{secrets.COSIGN_PASSWORD}}" cosign-2.5.0/.github/workflows/codeql-analysis.yml000066400000000000000000000042541477503325500222300ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: CodeQL on: push: paths: - '**' - '!**.md' - '!doc/**' - '!**.txt' - '!images/**' - '!LICENSE' - 'test/**' branches: [ main ] env: CODEQL_EXTRACTOR_GO_BUILD_TRACING: true permissions: {} jobs: analyze: name: Analyze runs-on: ubuntu-latest if: github.repository == 'sigstore/cosign' permissions: security-events: write actions: read contents: read strategy: fail-fast: false matrix: language: [ 'go' ] steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Utilize Go Module Cache uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: | ~/go/pkg/mod ~/.cache/go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Set correct version of Golang to use during CodeQL run uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # v2.16.1 with: languages: ${{ matrix.language }} - name: Build cosign for CodeQL run: make cosign - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # v2.16.1 cosign-2.5.0/.github/workflows/conformance-nightly.yml000066400000000000000000000046111477503325500231030ustar00rootroot00000000000000# Copyright 2024 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Conformance Tests Nightly on: schedule: - cron: '0 0 * * *' # 12:00 AM UTC workflow_dispatch: permissions: contents: read issues: write jobs: conformance: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - run: make cosign conformance - uses: sigstore/sigstore-conformance@main with: entrypoint: ${{ github.workspace }}/conformance - name: Create Issue on Failure if: failure() uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { owner, repo } = context.repo; const runId = context.runId; const issueTitle = 'Conformance Tests Failed'; const issueBody = `The nightly conformance tests have failed. Please check the logs for more details.\n\nWorkflow run: https://github.com/${owner}/${repo}/actions/runs/${runId}\n\ncc @sigstore/security-response-team @sigstore/cosign-codeowners`; const issueLabel = 'bug'; const existingIssues = await github.rest.issues.listForRepo({ owner, repo, state: 'open', labels: issueLabel, }); const issueExists = existingIssues.data.some(issue => issue.title === issueTitle); if (!issueExists) { await github.rest.issues.create({ owner, repo, title: issueTitle, body: issueBody, labels: [issueLabel], }); } cosign-2.5.0/.github/workflows/conformance.yml000066400000000000000000000024021477503325500214230ustar00rootroot00000000000000# Copyright 2024 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Conformance Tests on: push: branches: - main pull_request: branches: - main permissions: contents: read jobs: conformance: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - run: make cosign conformance - uses: sigstore/sigstore-conformance@640e7dfb715518eeeb492910c6d244cedcc6cfea # v0.0.17 with: entrypoint: ${{ github.workspace }}/conformance cosign-2.5.0/.github/workflows/cosign-test.key000066400000000000000000000012111477503325500213540ustar00rootroot00000000000000-----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 OCwicCI6MX0sInNhbHQiOiJYcDVZWW5nRVBWNkR1Kzh0Qmdsbll1OEc2YTZOWGVJ L1M3bXo0VUYvWk1FPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 Iiwibm9uY2UiOiIyT0YvUFJBQ09sKzRTVTJXVHpycTFHalFML2JlQzQvQiJ9LCJj aXBoZXJ0ZXh0IjoiMDV6eTlxaWVZWlVjZ0pCZHNndWhSNmd6ZmEvc1RrbnBCZTN0 Z3VsdDlXK0g0bnE3UE9sODFvdUttT0xhY3g5bzhKekhLNDg5SFR5NnNBYnhiWUVX R3Ewam85RUtSS0x0SU9NTWJ3cVluc2Z3YnVHQzNSNm1CR29CZncwV1pUOEdlMDM1 NEQ2MzlTR1NLSytLMW9IM0xCSVdDWFgxYkllWFFkaFRWQTR4UDExVDFIZzFLM0RY WXVsUGpydFB0Yk5BVk44YmZUY3VSbHoxTlE9PSJ9 -----END ENCRYPTED COSIGN PRIVATE KEY----- cosign-2.5.0/.github/workflows/cosign-test.pub000066400000000000000000000002621477503325500213570ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZxAfzrQG1EbWyCI8LiSB7YgSFXoI FNGTyQGKHFc6/H8TQumT9VLS78pUwtv3w7EfKoyFZoP32KrO7nzUy2q6Cw== -----END PUBLIC KEY----- cosign-2.5.0/.github/workflows/cut-release.yml000066400000000000000000000030501477503325500213420ustar00rootroot00000000000000# # Copyright 2024 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Cut Release on: workflow_dispatch: inputs: release_tag: required: true type: string description: 'Release tag' key_ring: required: true type: string description: 'Key ring for cosign key' key_name: required: true type: string description: 'Key name for cosign key' concurrency: cut-release jobs: cut-release: name: Cut release uses: sigstore/community/.github/workflows/reusable-release.yml@main permissions: id-token: write contents: read with: release_tag: ${{ github.event.inputs.release_tag }} key_ring: ${{ github.event.inputs.key_ring }} key_name: ${{ github.event.inputs.key_name }} workload_identity_provider: 'projects/498091336538/locations/global/workloadIdentityPools/githubactions/providers/sigstore-cosign' service_account: 'github-actions-cosign@projectsigstore.iam.gserviceaccount.com' repo: 'cosign' cosign-2.5.0/.github/workflows/depsreview.yml000066400000000000000000000016351477503325500213150ustar00rootroot00000000000000# # Copyright 2022 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: 'Dependency Review' on: [pull_request] permissions: {} jobs: dependency-review: name: License and Vulnerability Scan if: github.repository == 'sigstore/cosign' permissions: contents: read uses: sigstore/community/.github/workflows/reusable-dependency-review.yml@9b1b5aca605f92ec5b1bf3681b1e61b3dbc420cc cosign-2.5.0/.github/workflows/donotsubmit.yaml000066400000000000000000000022011477503325500216360ustar00rootroot00000000000000# # Copyright 2024 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Do Not Submit on: pull_request: branches: - 'main' - 'release-*' permissions: {} jobs: donotsubmit: name: Do Not Submit runs-on: ubuntu-latest if: github.repository == 'sigstore/cosign' permissions: contents: read steps: - name: Check out code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v2.4.0 with: persist-credentials: false - name: Do Not Submit uses: chainguard-dev/actions/donotsubmit@84c993eaf02da1c325854fb272a4df9184bd80fc # main cosign-2.5.0/.github/workflows/e2e-tests.yml000066400000000000000000000161241477503325500207520ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: e2e-tests # Run on every push, and allow it to be run manually. on: push: paths: - '**' - '!**.md' - '!doc/**' - '!**.txt' - '!images/**' - '!LICENSE' - 'test/**' branches: - "main" pull_request: workflow_dispatch: jobs: e2e-cross: strategy: matrix: os: [macos-latest, ubuntu-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Run cross platform e2e tests run: go test -tags=e2e,cross -v ./test/... e2e-test-pkcs11: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Run pkcs11 end-to-end tests shell: bash run: ./test/e2e_test_pkcs11.sh e2e-kms: runs-on: ubuntu-latest services: vault: image: hashicorp/vault:latest env: VAULT_DEV_ROOT_TOKEN_ID: root options: >- --health-cmd "VAULT_ADDR=http://127.0.0.1:8200 vault status" --health-interval 1s --health-timeout 5s --health-retries 5 --restart always ports: - 8200:8200 env: VAULT_TOKEN: "root" VAULT_ADDR: "http://localhost:8200" COSIGN_YES: "true" SCAFFOLDING_RELEASE_VERSION: "v0.7.21" steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: setup vault uses: cpanato/vault-installer@e7c1d664fa15219e89e43739e39a9df11ba00849 # v1.2.0 - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - uses: imjasonh/setup-crane@31b88efe9de28ae0ffa220711af4b60be9435f6e # v0.4 - name: Install cluster + sigstore uses: sigstore/scaffolding/actions/setup@main with: version: ${{ env.SCAFFOLDING_RELEASE_VERSION }} - name: enable vault transit run: vault secrets enable transit - name: Acceptance Tests run: go test -tags=e2e,kms -v ./test/... e2e-registry: runs-on: ubuntu-latest env: SCAFFOLDING_RELEASE_VERSION: "v0.7.21" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Setup mirror uses: chainguard-dev/actions/setup-mirror@main with: mirror: mirror.gcr.io - name: Install cluster + sigstore uses: sigstore/scaffolding/actions/setup@main with: version: ${{ env.SCAFFOLDING_RELEASE_VERSION }} - name: Setup local insecure registry run: | # Create a self-signed SSL cert mkdir -p insecure-certs openssl req \ -subj "/C=US/ST=WA/L=Flavorton/O=Tests-R-Us/OU=Dept. of Insecurity/CN=example.com/emailAddress=testing@example.com" \ -newkey rsa:4096 -nodes -sha256 -keyout insecure-certs/domain.key \ -x509 -days 365 -out insecure-certs/domain.crt # Run a registry. docker run -d --restart=always \ --name $INSECURE_REGISTRY_NAME \ -v "$(pwd)"/insecure-certs:/insecure-certs \ -e REGISTRY_HTTP_ADDR=0.0.0.0:$INSECURE_REGISTRY_PORT \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/insecure-certs/domain.crt \ -e REGISTRY_HTTP_TLS_KEY=/insecure-certs/domain.key \ -p $INSECURE_REGISTRY_PORT:$INSECURE_REGISTRY_PORT \ registry:2 sudo echo "127.0.0.1 $INSECURE_REGISTRY_NAME" | sudo tee -a /etc/hosts env: # https://github.com/google/go-containerregistry/pull/125 allows insecure registry for # '*.local' hostnames. INSECURE_REGISTRY_NAME: insecure-registry.notlocal INSECURE_REGISTRY_PORT: 5001 - name: Run Insecure Registry Tests run: go test -tags=e2e,registry -v ./test/... env: COSIGN_TEST_REPO: insecure-registry.notlocal:5001 - name: Setup local insecure OCI 1.1 registry run: | # Create a self-signed SSL cert mkdir -p insecure-certs openssl req \ -subj "/C=US/ST=WA/L=Flavorton/O=Tests-R-Us/OU=Dept. of Insecurity/CN=example.com/emailAddress=testing@example.com" \ -newkey rsa:4096 -nodes -sha256 -keyout insecure-certs/domain.key \ -x509 -days 365 -out insecure-certs/domain.crt cat > config.json << EOF { "distSpecVersion": "1.1.0-dev", "storage": { "rootDirectory": "/tmp/zot" }, "http": { "address": "0.0.0.0", "port": "5002", "realm": "zot", "tls": { "cert": "/insecure-certs/domain.crt", "key": "/insecure-certs/domain.key" } }, "log": { "level": "debug" } } EOF # Run a registry. docker run -d --restart=always \ --name $INSECURE_OCI_REGISTRY_NAME \ -v "$(pwd)"/insecure-certs:/insecure-certs \ -v "$(pwd)"/config.json:/etc/zot/config.json \ -p $INSECURE_OCI_REGISTRY_PORT:$INSECURE_OCI_REGISTRY_PORT \ ghcr.io/project-zot/zot-minimal-linux-amd64:$ZOT_VERSION sudo echo "127.0.0.1 $INSECURE_OCI_REGISTRY_NAME" | sudo tee -a /etc/hosts env: ZOT_VERSION: v2.0.0-rc6 # https://github.com/google/go-containerregistry/pull/125 allows insecure registry for # '*.local' hostnames. INSECURE_OCI_REGISTRY_NAME: insecure-oci-registry.notlocal INSECURE_OCI_REGISTRY_PORT: 5002 - name: Run Insecure OCI 1.1 Registry Tests run: go test -tags=e2e,registry -v ./test/... env: OCI11: yes COSIGN_TEST_REPO: insecure-oci-registry.notlocal:5002 - name: Collect diagnostics if: ${{ failure() }} uses: chainguard-dev/actions/kind-diag@9ba949ac63357c725a9438f3e05a1e33d313498e # main cosign-2.5.0/.github/workflows/e2e-with-binary.yml000066400000000000000000000044011477503325500220400ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: e2e-with-binary # Run on every push, and allow it to be run manually. on: push: paths: - '**' - '!**.md' - '!doc/**' - '!**.txt' - '!images/**' - '!LICENSE' - 'test/**' branches: [ 'main' ] workflow_dispatch: permissions: {} jobs: e2e-tests-with-binary: # Skip if running in a fork that might not have secrets configured. if: ${{ github.repository == 'sigstore/cosign' }} name: Run tests runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] permissions: id-token: write contents: read env: COSIGN_YES: "true" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: build cosign and check sign-blob and verify-blob shell: bash run: | set -e make cosign ./cosign sign-blob --output-certificate certificate.pem --output-signature README.md.sig README.md if [ -s certificate.pem ] then echo "all good for key.pem" else echo "file does not exist, or is empty" exit 1 fi if [ -s README.md.sig ] then exit 0 else echo "file does not exist, or is empty" exit 1 fi # Verify with sign-blob ./cosign verify-blob README.md --certificate certificate.pem --signature README.md.sig cosign-2.5.0/.github/workflows/github-oidc.yaml000066400000000000000000000036551477503325500215030ustar00rootroot00000000000000# Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Test GitHub OIDC on: push: paths: - '**' - '!**.md' - '!doc/**' - '!**.txt' - '!images/**' - '!LICENSE' - 'test/**' branches: [ 'main', 'release-*' ] schedule: - cron: '0 1 * * *' # 1AM UTC workflow_dispatch: permissions: {} jobs: build: runs-on: ubuntu-latest if: github.repository == 'sigstore/cosign' permissions: id-token: write packages: write contents: read env: GIT_HASH: ${{ github.sha }} GIT_VERSION: unstable GITHUB_RUN_ID: ${{ github.run_id }} GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} KO_PREFIX: ghcr.io/${{ github.repository }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true cache: true # Install tools. - uses: ko-build/setup-ko@d982fec422852203cfb2053a8ec6ad302280d04d # v0.8 - name: build cosign from the HEAD run: | make cosign ./cosign version - name: Build and sign a container image run: | set -e # Build and publish an image. make sign-ci-keyless-containers cosign-2.5.0/.github/workflows/golangci-lint.yml000066400000000000000000000035051477503325500216650ustar00rootroot00000000000000# Copyright 2024 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: golangci-lint on: push: branches: - 'main' pull_request: permissions: {} jobs: golangci: name: lint runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: golangci-lint uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 with: version: v2.0 golangci-test-e2e: name: lint-test-e2e runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: golangci-lint uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 with: version: v2.0 args: --build-tags e2e ./test cosign-2.5.0/.github/workflows/kind-verify-attestation.yaml000066400000000000000000000171371477503325500240710ustar00rootroot00000000000000# Copyright 2022 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Test attest / verify-attestation on: pull_request: branches: [ 'main', 'release-*' ] workflow_dispatch: defaults: run: shell: bash permissions: {} jobs: cip-test: name: attest / verify-attestation test runs-on: ubuntu-latest strategy: matrix: k8s-version: - v1.29.x - v1.30.x - v1.31.x - v1.32.x tuf-root: - remote - air-gap permissions: contents: read env: KO_DOCKER_REPO: "registry.local:5000/policy-controller" SCAFFOLDING_RELEASE_VERSION: "v0.7.21" GO111MODULE: on GOFLAGS: -ldflags=-s -ldflags=-w KOCACHE: ~/ko COSIGN_YES: "true" steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true # will use the latest release available for ko - uses: ko-build/setup-ko@d982fec422852203cfb2053a8ec6ad302280d04d # v0.8 - name: Install yq uses: mikefarah/yq@8bf425b4d1344db7cd469a8d10a390876e0c77fd # v4.45.1 - name: build cosign run: | make cosign - name: Install cluster + sigstore uses: sigstore/scaffolding/actions/setup@main with: legacy-variables: "false" k8s-version: ${{ matrix.k8s-version }} version: ${{ env.SCAFFOLDING_RELEASE_VERSION }} - name: Create sample image - demoimage run: | pushd $(mktemp -d) go mod init example.com/demo cat < main.go package main import "fmt" func main() { fmt.Println("hello world") } EOF demoimage=`ko publish -B example.com/demo` echo "demoimage=$demoimage" >> $GITHUB_ENV echo Created image $demoimage popd - name: Initialize with our custom TUF root pointing to remote root if: ${{ matrix.tuf-root == 'remote' }} run: | TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}') ./cosign initialize --mirror $TUF_MIRROR --root ./root.json - name: Initialize with custom TUF root pointing to local filesystem if: ${{ matrix.tuf-root == 'air-gap' }} run: | # Grab the compressed repository for airgap testing. kubectl -n tuf-system get secrets tuf-root -ojsonpath='{.data.repository}' | base64 -d > ./repository.tar.gz tar -zxvf ./repository.tar.gz PWD=$(pwd) ROOT=${PWD}/repository/1.root.json REPOSITORY=${PWD}/repository ./cosign initialize --root ${ROOT} --mirror file://${REPOSITORY} - name: Sign demoimage with cosign run: | ./cosign sign --rekor-url ${REKOR_URL} --fulcio-url ${FULCIO_URL} --yes --allow-insecure-registry ${demoimage} --identity-token ${OIDC_TOKEN} - name: Create attestation for it run: | echo -n 'foobar e2e test' > ./predicate-file ./cosign attest --predicate ./predicate-file --fulcio-url ${FULCIO_URL} --rekor-url ${REKOR_URL} --allow-insecure-registry --yes ${demoimage} --identity-token ${OIDC_TOKEN} - name: Sign a blob run: | ./cosign sign-blob README.md --fulcio-url ${FULCIO_URL} --rekor-url ${REKOR_URL} --output-certificate cert.pem --output-signature sig --yes --identity-token ${OIDC_TOKEN} - name: Verify with cosign run: | ./cosign verify --rekor-url ${REKOR_URL} --allow-insecure-registry ${demoimage} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" - name: Verify custom attestation with cosign, works run: | echo '::group:: test custom verify-attestation success' if ! ./cosign verify-attestation --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" --policy ./test/testdata/policies/cue-works.cue --rekor-url ${REKOR_URL} --allow-insecure-registry ${demoimage} ; then echo Failed to verify attestation with a valid policy exit 1 else echo Successfully validated custom attestation with a valid policy fi echo '::endgroup::' - name: Verify custom attestation with cosign, fails run: | echo '::group:: test custom verify-attestation success' if ./cosign verify-attestation --policy ./test/testdata/policies/cue-fails.cue --rekor-url ${REKOR_URL} --allow-insecure-registry ${demoimage} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then echo custom verify-attestation succeeded with cue policy that should not work exit 1 else echo Successfully failed a policy that should not work fi echo '::endgroup::' - name: Verify a blob run: | ./cosign verify-blob README.md --rekor-url ${REKOR_URL} --certificate ./cert.pem --signature sig --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" - name: Collect diagnostics if: ${{ failure() }} uses: chainguard-dev/actions/kind-diag@9ba949ac63357c725a9438f3e05a1e33d313498e # main - name: Create vuln attestation for it run: | ./cosign attest --predicate ./test/testdata/attestations/vuln-predicate.json --type vuln --fulcio-url ${FULCIO_URL} --rekor-url ${REKOR_URL} --allow-insecure-registry --yes ${demoimage} --identity-token ${OIDC_TOKEN} - name: Verify vuln attestation with cosign, works run: | echo '::group:: test vuln verify-attestation success' if ! ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-works.cue --rekor-url ${REKOR_URL} --allow-insecure-registry ${demoimage} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then echo Failed to verify attestation with a valid policy exit 1 else echo Successfully validated vuln attestation with a valid policy fi echo '::endgroup::' - name: Verify vuln attestation with cosign, fails run: | echo '::group:: test vuln verify-attestation success' if ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-fails.cue --rekor-url ${REKOR_URL} --allow-insecure-registry ${demoimage} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then echo verify-attestation succeeded with cue policy that should not work exit 1 else echo Successfully failed a policy that should not work fi echo '::endgroup::' cosign-2.5.0/.github/workflows/scorecard-action.yml000066400000000000000000000051471477503325500223620ustar00rootroot00000000000000# # Copyright 2024 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Scorecards supply-chain security on: # Only the default branch is supported. branch_protection_rule: schedule: # Weekly on Saturdays. - cron: '30 1 * * 6' push: branches: [ main ] permissions: {} jobs: analysis: name: Scorecards analysis runs-on: ubuntu-latest if: github.repository == 'sigstore/cosign' permissions: # Needed to upload the results to code-scanning dashboard. security-events: write actions: read contents: read id-token: write steps: - name: "Checkout code" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: results.sarif results_format: sarif # Read-only PAT token. To create it, # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation. repo_token: ${{ secrets.SCORECARD_TOKEN }} # Publish the results for public repositories to enable scorecard badges. For more details, see # https://github.com/ossf/scorecard-action#publishing-results. # For private repositories, `publish_results` will automatically be set to `false`, regardless # of the value entered here. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # v2.16.1 with: sarif_file: results.sarif cosign-2.5.0/.github/workflows/tests.yaml000066400000000000000000000233451477503325500204450ustar00rootroot00000000000000# Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: CI-Tests on: workflow_dispatch: push: paths: - '**' - '!**.md' - '!doc/**' - '!**.txt' - '!images/**' - '!LICENSE' - 'test/**' branches: ['main', 'release-*'] pull_request: jobs: unit-tests: name: Run unit tests permissions: contents: read runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] env: OS: ${{ matrix.os }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: # In order: # * Module download cache # * Build cache (Linux) # * Build cache (Mac) # * Build cache (Windows) path: | ~/go/pkg/mod ~/.cache/go-build ~/Library/Caches/go-build %LocalAppData%\go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Run Go tests env: # See #2091 for the issue describing this temp workaround. GODEBUG: x509sha1=1 run: go test -tags=sct -covermode atomic -coverprofile coverage.txt $(go list ./... | grep -v third_party/) - name: Upload Coverage Report uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 with: env_vars: OS - name: Run Go tests w/ `-race` env: # See #2091 for the issue describing this temp workaround. GODEBUG: x509sha1=1 if: ${{ runner.os == 'Linux' }} run: go test -tags=sct -race $(go list ./... | grep -v third_party/) e2e-tests: name: Run e2e tests runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false # Related to https://github.com/sigstore/cosign/issues/3149 - name: free up disk space run: | rm -rf /opt/hostedtoolcache/CodeQL rm -rf /usr/share/dotnet/ rm -rf "$AGENT_TOOLSDIRECTORY" rm -rf "/usr/local/share/boost" rm -rf /opt/ghc docker rmi $(docker image ls -aq) || true swapoff /swapfile || true rm -rf /swapfile /usr/share/dotnet /usr/local/lib/android /opt/ghc || true apt purge aria2 ansible hhvm mono-devel azure-cli shellcheck rpm xorriso zsync \ clang-6.0 lldb-6.0 lld-6.0 clang-format-6.0 clang-8 lldb-8 lld-8 clang-format-8 \ clang-9 lldb-9 lld-9 clangd-9 clang-format-9 dotnet-sdk-3.0 dotnet-sdk-3.1=3.1.101-1 \ esl-erlang firefox g++-8 g++-9 gfortran-8 gfortran-9 google-chrome-stable \ google-cloud-sdk ghc-8.0.2 ghc-8.2.2 ghc-8.4.4 ghc-8.6.2 ghc-8.6.3 ghc-8.6.4 \ ghc-8.6.5 ghc-8.8.1 ghc-8.8.2 ghc-8.8.3 ghc-8.10.1 cabal-install-2.0 cabal-install-2.2 \ cabal-install-2.4 cabal-install-3.0 cabal-install-3.2 heroku imagemagick \ libmagickcore-dev libmagickwand-dev libmagic-dev ant ant-optional kubectl \ mercurial apt-transport-https mono-complete mysql-client libmysqlclient-dev \ mysql-server mssql-tools unixodbc-dev yarn bazel chrpath libssl-dev libxft-dev \ libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev php7.1 php7.1-bcmath \ php7.1-bz2 php7.1-cgi php7.1-cli php7.1-common php7.1-curl php7.1-dba php7.1-dev \ php7.1-enchant php7.1-fpm php7.1-gd php7.1-gmp php7.1-imap php7.1-interbase php7.1-intl \ php7.1-json php7.1-ldap php7.1-mbstring php7.1-mcrypt php7.1-mysql php7.1-odbc \ php7.1-opcache php7.1-pgsql php7.1-phpdbg php7.1-pspell php7.1-readline php7.1-recode \ php7.1-snmp php7.1-soap php7.1-sqlite3 php7.1-sybase php7.1-tidy php7.1-xml \ php7.1-xmlrpc php7.1-xsl php7.1-zip php7.2 php7.2-bcmath php7.2-bz2 php7.2-cgi \ php7.2-cli php7.2-common php7.2-curl php7.2-dba php7.2-dev php7.2-enchant php7.2-fpm \ php7.2-gd php7.2-gmp php7.2-imap php7.2-interbase php7.2-intl php7.2-json php7.2-ldap \ php7.2-mbstring php7.2-mysql php7.2-odbc php7.2-opcache php7.2-pgsql php7.2-phpdbg \ php7.2-pspell php7.2-readline php7.2-recode php7.2-snmp php7.2-soap php7.2-sqlite3 \ php7.2-sybase php7.2-tidy php7.2-xml php7.2-xmlrpc php7.2-xsl php7.2-zip php7.3 \ php7.3-bcmath php7.3-bz2 php7.3-cgi php7.3-cli php7.3-common php7.3-curl php7.3-dba \ php7.3-dev php7.3-enchant php7.3-fpm php7.3-gd php7.3-gmp php7.3-imap php7.3-interbase \ php7.3-intl php7.3-json php7.3-ldap php7.3-mbstring php7.3-mysql php7.3-odbc \ php7.3-opcache php7.3-pgsql php7.3-phpdbg php7.3-pspell php7.3-readline php7.3-recode \ php7.3-snmp php7.3-soap php7.3-sqlite3 php7.3-sybase php7.3-tidy php7.3-xml \ php7.3-xmlrpc php7.3-xsl php7.3-zip php7.4 php7.4-bcmath php7.4-bz2 php7.4-cgi \ php7.4-cli php7.4-common php7.4-curl php7.4-dba php7.4-dev php7.4-enchant php7.4-fpm \ php7.4-gd php7.4-gmp php7.4-imap php7.4-interbase php7.4-intl php7.4-json php7.4-ldap \ php7.4-mbstring php7.4-mysql php7.4-odbc php7.4-opcache php7.4-pgsql php7.4-phpdbg \ php7.4-pspell php7.4-readline php7.4-snmp php7.4-soap php7.4-sqlite3 php7.4-sybase \ php7.4-tidy php7.4-xml php7.4-xmlrpc php7.4-xsl php7.4-zip php-amqp php-apcu \ php-igbinary php-memcache php-memcached php-mongodb php-redis php-xdebug \ php-zmq snmp pollinate libpq-dev postgresql-client powershell ruby-full \ sphinxsearch subversion mongodb-org -yq >/dev/null 2>&1 || true apt-get remove -y '^llvm.*' || true apt-get remove -y 'php.*' || true apt-get autoremove -y >/dev/null 2>&1 || true apt-get autoclean -y >/dev/null 2>&1 || true - name: check disk space run: df -h # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: # In order: # * Module download cache # * Build cache (Linux) # * Build cache (Mac) # * Build cache (Windows) path: | ~/go/pkg/mod ~/.cache/go-build ~/Library/Caches/go-build %LocalAppData%\go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - uses: ko-build/setup-ko@d982fec422852203cfb2053a8ec6ad302280d04d # v0.8 - name: setup kind cluster run: | # Used to test: cosign generate-key-pair k8s://... go install sigs.k8s.io/kind@v0.20.0 kind create cluster - name: Run end-to-end tests run: ./test/e2e_test.sh - name: Collect diagnostics if: ${{ failure() }} uses: chainguard-dev/actions/kind-diag@9ba949ac63357c725a9438f3e05a1e33d313498e # main e2e-windows-powershell-tests: name: Run PowerShell E2E tests runs-on: windows-latest permissions: contents: read steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: # In order: # * Module download cache # * Build cache (Windows) path: | ~/go/pkg/mod %LocalAppData%\go-build key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Run e2e_test.ps1 run: ./test/e2e_test.ps1 license-check: name: license boilerplate check runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Install addlicense run: go install github.com/google/addlicense@latest - name: Check license headers run: | set -e addlicense -check -l apache -c 'The Sigstore Authors' -ignore "third_party/**" -v * cosign-2.5.0/.github/workflows/validate-release.yml000066400000000000000000000072251477503325500223500ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: CI-Validate-Release-Job on: push: branches: - main - release-* pull_request: jobs: check-signature: runs-on: ubuntu-latest container: image: ghcr.io/sigstore/cosign/cosign:v2.4.3-dev@sha256:ed76c008e733aa64d257f754a02eb07b251525ea8dc08f40974baec317dea8c9 steps: - name: Check Signature run: | cosign verify ghcr.io/gythialy/golang-cross:v1.24.1-0@sha256:dcc12fa4a35c7cda162cfbaa3f612b4209d5e2e78093e7c3fd7168d258432706 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-identity "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.24.1-0" env: TUF_ROOT: /tmp validate-release-job: runs-on: ubuntu-latest needs: - check-signature container: image: ghcr.io/gythialy/golang-cross:v1.24.1-0@sha256:dcc12fa4a35c7cda162cfbaa3f612b4209d5e2e78093e7c3fd7168d258432706 volumes: - /usr:/host_usr - /opt:/host_opt permissions: {} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false # Error: fatal: detected dubious ownership in repository at '/__w/cosign/cosign' # To add an exception for this directory, call: # git config --system --add safe.directory /__w/cosign/cosign # Reason: Recent versions of git require the .git folder to be owned # by the same user (see https://github.blog/2022-04-12-git-security-vulnerability-announced/). # Related # - https://github.com/actions/runner/issues/2033 # - https://github.com/actions/checkout/issues/1048 # - https://github.com/actions/runner-images/issues/6775 - run: git config --system --add safe.directory /__w/cosign/cosign # Related to https://github.com/sigstore/cosign/issues/3149 # note this runs from within the golang-cross container, so if we want to free up space # on the host, we have to reach through the bind mounts to clean up unused content on the runners - name: free up disk space for the release run: | rm -rf /host_usr/share/dotnet || true rm -rf /host_usr/share/swift || true rm -rf /host_usr/local/lib/android || true rm -rf /host_usr/local/.ghcup || true rm -rf /host_usr/local/graalvm/ || true rm -rf /host_usr/local/share/powershell || true rm -rf /host_usr/local/share/chromium || true rm -rf /host_usr/local/lib/node_modules || true rm -rf /host_usr/lib/google-cloud-sdk || true rm -rf /host_usr/local/share/boost || true rm -rf /host_opt/hostedtoolcache/ || true rm -rf /host_opt/ghc || true - name: check disk space run: df -h - name: goreleaser snapshot run: make snapshot env: PROJECT_ID: honk-fake-project RUNTIME_IMAGE: gcr.io/distroless/static-debian12:nonroot - name: check binaries run: | ./dist/cosign-linux-amd64 version cosign-2.5.0/.github/workflows/verify-docgen.yaml000066400000000000000000000023521477503325500220370ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Docgen on: workflow_dispatch: push: branches: - 'main' - 'release-*' pull_request: permissions: {} jobs: docgen: name: Verify Docgen runs-on: ubuntu-latest permissions: contents: read steps: - name: deps run: sudo apt-get update && sudo apt-get install -yq libpcsclite-dev - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - run: ./cmd/help/verify.sh cosign-2.5.0/.github/workflows/whitespace.yaml000066400000000000000000000023231477503325500214300ustar00rootroot00000000000000# # Copyright 2024 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. name: Whitespace on: pull_request: branches: - 'main' - 'release-*' permissions: {} jobs: whitespace: name: Check Whitespace runs-on: ubuntu-latest permissions: contents: read steps: - name: Check out code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: chainguard-dev/actions/trailing-space@7071df0659dbd4a79804731f0da2d0f1dba0b356 # main if: ${{ always() }} - uses: chainguard-dev/actions/eof-newline@7071df0659dbd4a79804731f0da2d0f1dba0b356 # main if: ${{ always() }} cosign-2.5.0/.gitignore000066400000000000000000000007311477503325500150040ustar00rootroot00000000000000# Binaries for programs and plugins .DS_STORE *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # cosign stuff /cosign* .vscode .idea # fuzzing artifacts *.libfuzzer *fuzz.a bin* dist/ cosignImagerefs bundle signature certificate sigstore-conformance conformance **verify-experimental* cosign-2.5.0/.golangci.yml000066400000000000000000000036631477503325500154070ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. version: "2" run: issues-exit-code: 1 linters: enable: - asciicheck - errorlint - forbidigo - gocritic - gosec - importas - misspell - prealloc - revive - staticcheck - tparallel - unconvert - unparam - unused - whitespace settings: forbidigo: forbid: # Forbid using os.Getenv and os.LookupEnv with COSIGN_ variables in favor of # pkg/cosign/env package # Reference: https://github.com/sigstore/cosign/issues/2236 - pattern: os\.Getenv.* - pattern: os\.LookupEnv.* exclude-godoc-examples: false gosec: excludes: - G115 # integer overflow conversion int64 -> uint64 exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - errcheck # We want to allow using os.Getenv and os.Setenv in tests because it # might be easier (and needed in some cases) - forbidigo - gosec path: _test\.go paths: - third_party$ - builtin$ - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 uniq-by-line: false formatters: enable: - gofmt - goimports exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ cosign-2.5.0/.goreleaser.yml000066400000000000000000000124341477503325500157500ustar00rootroot00000000000000project_name: cosign version: 2 env: - GO111MODULE=on - CGO_ENABLED=1 - DOCKER_CLI_EXPERIMENTAL=enabled - COSIGN_YES=true # Prevents parallel builds from stepping on each others toes downloading modules before: hooks: - go mod tidy - git --no-pager diff --exit-code go.mod go.sum gomod: proxy: true sboms: - artifacts: binary builds: - id: linux binary: cosign-linux-{{ .Arch }} no_unique_dist_dir: true main: ./cmd/cosign flags: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' goos: - linux goarch: - amd64 - arm64 - arm - s390x - ppc64le - riscv64 goarm: - '7' ldflags: - "{{ .Env.LDFLAGS }}" env: - CGO_ENABLED=0 - id: linux-pivkey-pkcs11key-amd64 binary: cosign-linux-pivkey-pkcs11key-amd64 no_unique_dist_dir: true main: ./cmd/cosign flags: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' goos: - linux goarch: - amd64 ldflags: - "{{ .Env.LDFLAGS }}" tags: - pivkey - pkcs11key hooks: pre: - apt-get update - apt-get -y install --no-install-recommends libpcsclite-dev env: - PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig/ - id: linux-pivkey-pkcs11key-arm64 binary: cosign-linux-pivkey-pkcs11key-arm64 no_unique_dist_dir: true main: ./cmd/cosign flags: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' goos: - linux goarch: - arm64 ldflags: - "{{ .Env.LDFLAGS }}" tags: - pivkey - pkcs11key hooks: pre: - dpkg --add-architecture arm64 - apt-get update - apt-get install -y --no-install-recommends libpcsclite-dev:arm64 env: - CC=aarch64-linux-gnu-gcc - PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/ - id: darwin-amd64 binary: cosign-darwin-amd64 no_unique_dist_dir: true env: - CC=o64-clang - CXX=o64-clang++ main: ./cmd/cosign flags: - -trimpath mod_timestamp: '{{ .CommitTimestamp }}' goos: - darwin goarch: - amd64 ldflags: - "{{ .Env.LDFLAGS }}" tags: - pivkey - pkcs11key - id: darwin-arm64 binary: cosign-darwin-arm64 no_unique_dist_dir: true env: - CC=aarch64-apple-darwin23-clang - CXX=aarch64-apple-darwin23-clang++ main: ./cmd/cosign flags: - -trimpath goos: - darwin goarch: - arm64 tags: - pivkey - pkcs11key ldflags: - "{{.Env.LDFLAGS}}" - id: windows-amd64 binary: cosign-windows-amd64 no_unique_dist_dir: true env: - CC=x86_64-w64-mingw32-gcc - CXX=x86_64-w64-mingw32-g++ main: ./cmd/cosign mod_timestamp: '{{ .CommitTimestamp }}' flags: - -trimpath goos: - windows goarch: - amd64 ldflags: - -buildmode=exe - "{{ .Env.LDFLAGS }}" tags: - pivkey - pkcs11key signs: - id: cosign signature: "${artifact}.sig" cmd: ./dist/cosign-linux-amd64 args: ["sign-blob", "--output-signature", "${artifact}.sig", "--key", "gcpkms://projects/{{ .Env.PROJECT_ID }}/locations/{{ .Env.KEY_LOCATION }}/keyRings/{{ .Env.KEY_RING }}/cryptoKeys/{{ .Env.KEY_NAME }}/versions/{{ .Env.KEY_VERSION }}", "${artifact}"] artifacts: binary # Keyless - id: cosign-keyless signature: "${artifact}-keyless.sig" certificate: "${artifact}-keyless.pem" cmd: ./dist/cosign-linux-amd64 args: ["sign-blob", "--output-signature", "${artifact}-keyless.sig", "--output-certificate", "${artifact}-keyless.pem", "${artifact}"] artifacts: binary - id: checksum-keyless signature: "${artifact}-keyless.sig" certificate: "${artifact}-keyless.pem" cmd: ./dist/cosign-linux-amd64 args: ["sign-blob", "--output-signature", "${artifact}-keyless.sig", "--output-certificate", "${artifact}-keyless.pem", "${artifact}"] artifacts: checksum - id: packages-keyless signature: "${artifact}-keyless.sig" certificate: "${artifact}-keyless.pem" cmd: ./dist/cosign-linux-amd64 args: ["sign-blob", "--output-signature", "${artifact}-keyless.sig", "--output-certificate", "${artifact}-keyless.pem", "${artifact}"] artifacts: package nfpms: - id: cosign package_name: cosign file_name_template: "{{ .ConventionalFileName }}" vendor: Sigstore homepage: https://sigstore.dev maintainer: Sigstore Authors 86837369+sigstore-bot@users.noreply.github.com builds: - linux description: Container Signing, Verification and Storage in an OCI registry. license: "Apache License 2.0" formats: - apk - deb - rpm contents: - src: /usr/bin/cosign-linux-{{ .Arch }} dst: /usr/bin/cosign type: "symlink" archives: - format: binary name_template: "{{ .Binary }}" allow_different_binary_count: true checksum: name_template: "{{ .ProjectName }}_checksums.txt" snapshot: name_template: SNAPSHOT-{{ .ShortCommit }} release: prerelease: allow # remove this when we start publishing non-prerelease or set to auto draft: true # allow for manual edits github: owner: sigstore name: cosign footer: | ### Thanks to all contributors! extra_files: - glob: "./release/release-cosign.pub" cosign-2.5.0/.ko.yaml000066400000000000000000000015651477503325500143750ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. defaultBaseImage: gcr.io/distroless/static-debian12:nonroot builds: - id: cosign dir: . main: ./cmd/cosign env: - CGO_ENABLED=0 flags: - -trimpath - -tags - "{{ .Env.GIT_HASH }}" - -tags - "{{ .Env.GIT_VERSION }}" ldflags: - -extldflags "-static" - "{{ .Env.LDFLAGS }}" cosign-2.5.0/ALUMNI.md000066400000000000000000000007211477503325500143220ustar00rootroot00000000000000# Alumni Thanks to the following folks who used to maintain Cosign (please keep this list sorted)! - [**@asraa**](https://github.com/asraa) - [**@dlorenc**](https://github.com/dlorenc) - [**@font**](https://github.com/font) - [**@loosebazooka**](https://github.com/loosebazooka) - [**@luhring**](https://github.com/luhring) - [**@lukehinds**](https://github.com/lukehinds) - [**@n3wscott**](https://github.com/n3wscott) - [**@vaikas**](https://github.com/vaikas) cosign-2.5.0/CHANGELOG.md000066400000000000000000003565401477503325500146410ustar00rootroot00000000000000# v2.4.3 ## Features * Bump sigstore/sigstore to support KMS plugins (#4073) * Enable fetching signatures without remote get. (#4047) * Feat/file flag completion improvements (#4028) * Update builder to use go1.23.6 (#4052) ## Bug Fixes * fix parsing error in --only for cosign copy (#4049) ## Cleanup * Refactor verifyNewBundle into library function (#4013) * fix comment typo and imports order (#4061) * sync comment with parameter name in function signature (#4063) * sort properly Go imports (#4071) ## Contributors * Bob Callaway * Carlos Tadeu Panato Junior * Cody Soyland * Dmitry Savintsev * Hayden B * Tomasz Janiszewski * Ville Skyttä # v2.4.2 ## Features * Updated open-policy-agent to 1.1.0 library (#4036) - Note that only Rego v0 policies are supported at this time * Add UseSignedTimestamps to CheckOpts, refactor TSA options (#4006) * Add support for verifying root checksum in cosign initialize (#3953) * Detect if user supplied a valid protobuf bundle (#3931) * Add a log message if user doesn't provide `--trusted-root` (#3933) * Support mTLS towards container registry (#3922) * Add bundle create helper command (#3901) * Add trusted-root create helper command (#3876) ## Bug Fixes * fix: set tls config while retaining other fields from default http transport (#4007) * policy fuzzer: ignore known panics (#3993) * Fix for multiple WithRemote options (#3982) * Add nightly conformance test workflow (#3979) * Fix copy --only for signatures + update/align docs (#3904) ## Documentation * Remove usage.md from spec, point to client spec (#3918) * move reference from gcr to ghcr (#3897) ## Contributors * AdamKorcz * Aditya Sirish * Bob Callaway * Carlos Tadeu Panato Junior * Cody Soyland * Colleen Murphy * Hayden B * Jussi Kukkonen * Marco Franssen * Nianyu Shen * Slavek Kabrda * Søren Juul * Warren Hodgkinson * Zach Steindler # v2.4.1 v2.4.1 largely contains bug fixes and updates dependencies. ## Features * Added fuzzing coverage to multiple packages ## Bug Fixes * Fix bug in attest-blob when using a timestamp authority with new bundles (#3877) * fix: documentation link for installation guide (#3884) ## Contributors * AdamKorcz * Bob Callaway * Carlos Tadeu Panato Junior * Hayden B * Hemil K * Sota Sugiura * Zach Steindler # v2.4.0 v2.4.0 begins the modernization of the Cosign client, which includes: * Support for the newer Sigstore specification-compliant bundle format * Support for providing trust roots (e.g. Fulcio certificates, Rekor keys) through a trust root file, instead of many different flags * Conformance test suite integration to verify signing and verification behavior In future updates, we'll include: * General support for the trust root file, instead of only when using the bundle format during verification * Simplification of trust root flags and deprecation of the Cosign-specific bundle format * Bundle support with container signing We have also moved nightly Cosign container builds to GHCR instead of GCR. ## Features * Add new bundle support to `verify-blob` and `verify-blob-attestation` (#3796) * Adding protobuf bundle support to sign-blob and attest-blob (#3752) * Bump sigstore/sigstore to support `email_verified` as string or boolean (#3819) * Conformance testing for cosign (#3806) * move incremental builds per commit to GHCR instead of GCR (#3808) * Add support for recording creation timestamp for cosign attest (#3797) * Include SCT verification failure details in error message (#3799) ## Contributors * Bob Callaway * Hayden B * Slavek Kabrda * Zach Steindler * Zsolt Horvath # v2.3.0 ## Features * Add PayloadProvider interface to decouple AttestationToPayloadJSON from oci.Signature interface (#3693) * add registry options to cosign save (#3645) * Add debug providers command. (#3728) * Make config layers in ociremote mountable (#3741) * upgrade to go1.22 (#3739) * adds tsa cert chain check for env var or tuf targets. (#3600) * add --ca-roots and --ca-intermediates flags to 'cosign verify' (#3464) * add handling of keyless verification for all verify commands (#3761) ## Bug Fixes * fix: close attestationFile (#3679) * Set `bundleVerified` to true after Rekor verification (Resolves #3740) (#3745) ## Documentation * Document ImportKeyPair and LoadPrivateKey functions in pkg/cosign (#3776) ## Testing * Refactor KMS E2E tests (#3684) * Remove sign\_blob\_test.sh test (#3707) * Remove KMS E2E test script (#3702) * Refactor insecure registry E2E tests (#3701) ## Contributors * Billy Lynch * bminahan73 * Bob Callaway * Carlos Tadeu Panato Junior * Cody Soyland * Colleen Murphy * Dmitry Savintsev * guangwu * Hayden B * Hector Fernandez * ian hundere * Jason Power * Jon Johnson * Max Lambrecht * Meeki1l # v2.2.4 ## Bug Fixes * Fixes for GHSA-88jx-383q-w4qc and GHSA-95pr-fxf5-86gv (#3661) * ErrNoSignaturesFound should be used when there is no signature attached to an image. (#3526) * fix semgrep issues for dgryski.semgrep-go ruleset (#3541) * Honor creation timestamp for signatures again (#3549) ## Features * Adds Support for Fulcio Client Credentials Flow, and Argument to Set Flow Explicitly (#3578) ## Documentation * add oci bundle spec (#3622) * Correct help text of triangulate cmd (#3551) * Correct help text of verify-attestation policy argument (#3527) * feat: add OVHcloud MPR registry tested with cosign (#3639) ## Testing * Refactor e2e-tests.yml workflow (#3627) * Clean up and clarify e2e scripts (#3628) * Don't ignore transparency log in tests if possible (#3528) * Make E2E tests hermetic (#3499) * add e2e test for pkcs11 token signing (#3495) # v2.2.3 ## Bug Fixes * Fix race condition on verification with multiple signatures attached to image (#3486) * fix(clean): Fix clean cmd for private registries (#3446) * Fixed BYO PKI verification (#3427) ## Features * Allow for option in cosign attest and attest-blob to upload attestation as supported in Rekor (#3466) * Add support for OpenVEX predicate type (#3405) ## Documentation * Resolves #3088: `version` sub-command expected behaviour documentation and testing (#3447) * add examples for cosign attach signature cmd (#3468) ## Misc * Remove CertSubject function (#3467) * Use local rekor and fulcio instances in e2e tests (#3478) ## Contributors * aalsabag * Bob Callaway * Carlos Tadeu Panato Junior * Colleen Murphy * Hayden B * Mukuls77 * Omri Bornstein * Puerco * vivek kumar sahu # v2.2.2 v2.2.2 adds a new container with a shell, `gcr.io/projectsigstore/cosign:vx.y.z-dev`, in addition to the existing container `gcr.io/projectsigstore/cosign:vx.y.z` without a shell. For private deployments, we have also added an alias for `--insecure-skip-log`, `--private-infrastructure`. ## Bug Fixes * chore(deps): bump github.com/sigstore/sigstore from 1.7.5 to 1.7.6 (#3411) which fixes a bug with using Azure KMS * Don't require CT log keys if using a key/sk (#3415) * Fix copy without any flag set (#3409) * Update cosign generate cmd to not include newline (#3393) * Fix idempotency error with signing (#3371) ## Features * Add `--yes` flag `cosign import-key-pair` to skip the overwrite confirmation. (#3383) * Use the timeout flag value in verify* commands. (#3391) * add --private-infrastructure flag (#3369) ## Container Updates * Bump builder image to use go1.21.4 and add new cosign image tags with shell (#3373) ## Documentation * Update SBOM\_SPEC.md (#3358) ## Contributors * Carlos Tadeu Panato Junior * Dylan Richardson * Hayden B * Lily Sturmann * Nikos Fotiou * Yonghe Zhao # v2.2.1 **Note: This release comes with a fix for CVE-2023-46737 described in this [Github Security Advisory](https://github.com/sigstore/cosign/security/advisories/GHSA-vfp6-jrw2-99g9). Please upgrade to this release ASAP** ## Enhancements * feat: Support basic auth and bearer auth login to registry (#3310) * add support for ignoring certificates with pkcs11 (#3334) * Support ReplaceOp in Signatures (#3315) * feat: added ability to get image digest back via triangulate (#3255) * feat: add `--only` flag in `cosign copy` to copy sign, att & sbom (#3247) * feat: add support attaching a Rekor bundle to a container (#3246) * feat: add support outputting rekor response on signing (#3248) * feat: improve dockerfile verify subcommand (#3264) * Add guard flag for experimental OCI 1.1 verify. (#3272) * Deprecate SBOM attachments (#3256) * feat: dedent line in cosign copy doc (#3244) * feat: add platform flag to cosign copy command (#3234) * Add SLSA 1.0 attestation support to cosign. Closes #2860 (#3219) * attest: pass OCI remote opts to att resolver. (#3225) ## Bug Fixes * Merge pull request from GHSA-vfp6-jrw2-99g9 * fix: allow cosign download sbom when image is absent (#3245) * ci: add a OCI registry test for referrers support (#3253) * Fix ReplaceSignatures (#3292) * Stop using deprecated in_toto.ProvenanceStatement (#3243) * Fixes #3236, disable SCT checking for a cosign verification when usin… (#3237) * fix: update error in `SignedEntity` to be more descriptive (#3233) * Fail timestamp verification if no root is provided (#3224) ## Documentation * Add some docs about verifying in an air-gapped environment (#3321) * Update CONTRIBUTING.md (#3268) * docs: improves the Contribution guidelines (#3257) * Remove security policy (#3230) ## Others * Set go to min 1.21 and update dependencies (#3327) * Update contact for code of conduct (#3266) * Update .ko.yaml (#3240) ## Contributors * AdamKorcz * Andres Galante * Appu * Billy Lynch * Bob Callaway * Caleb Woodbine * Carlos Tadeu Panato Junior * Dylan Richardson * Gareth Healy * Hayden B * John Kjell * Jon Johnson * jonvnadelberg * Luiz Carvalho * Priya Wadhwa * Ramkumar Chinchani * Tosone * Ville Aikas * Vishal Choudhary * ziel # v2.2.0 ## Enhancements * switch to uploading DSSE types to rekor instead of intoto (#3113) * add 'cosign sign' command-line parameters for mTLS (#3052) * improve error messages around bundle != payload hash (#3146) * make VerifyImageAttestation function public (#3156) * Switch to cryptoutils function for SANS (#3185) * Handle HTTP_1_1_REQUIRED errors in github provider (#3172) ## Bug Fixes * Fix nondeterminsitic timestamps (#3121) ## Documentation * doc: Add example of sign-blob with key in env var (#3152) * add deprecation notice for cosign-releases GCS bucket (#3148) * update doc links (#3186) ## Others * Upgrade to go1.21 (#3188) * Updates ci tests (#3142) * test using latest release of scaffolding (#3187) * ci: free up disk space for the gh runner (#3169) * update go-github to v53 (#3116) * call e2e test for cosign attach (#3112) * bump build cross to use go1.20.6 and cosign image to 2.1.1 (#3108) ## Contributors * Bob Callaway * Carlos Tadeu Panato Junior * Dmitry Savintsev * Hayden B * Hector Fernandez * Jason Hall * Jon Johnson * Jubril Oyetunji * Paulo Gomes * Priya Wadhwa * 张志强 # v2.1.1 ## Bug Fixes * wait for the workers become available again to continue the execution (#3084) * fix help text when in a container (#3082) ## Documentation * update changelog (#3080) * DNM: Add CHANGELOG for v2.1.0 (#3068) ## Contributors * Carlos Tadeu Panato Junior * priyawadhwa # v2.1.0 **Breaking Change: The predicate is now a required flag in the attest commands, set via the --type flag.** ## Enhancements * Verify sigs and attestations in parallel (#3066) * Deep inspect attestations when filtering download (#3031) * refactor bundle validation code, add support for DSSE rekor type (#3016) * Allow overriding remote options (#3049) * feat: adds no cert found on sig exit code (#3038) * Make predicate a required flag in attest commands (#3033) * Added support for attaching Time stamp authority Response in attach command (#3001) * Add `sign --sign-container-identity` CLI (#2984) * Feature: Allow cosign to sign digests before they are uploaded. (#2959) * accepts `attachment-tag-prefix` for `cosign copy` (#3014) * Feature: adds '--allow-insecure-registry' for cosign load (#3000) * download attestation: support --platform flag (#2980) * Cleanup: Add `Digest` to the `SignedEntity` interface. (#2960) * verify command: support keyless verification using only a provided certificate chain with non-fulcio roots (#2845) * verify: use workers to limit the paralellism when verifying images with --max-workers flag (#3069) ## Bug Fixes * Fix pkg/cosign/errors (#3050) * fix: update doc to refer to github-actions oidc provider (#3040) * fix: prefer GitHub OIDC provider if enabled (#3044) * Fix --sig-only in cosign copy (#3074) ## Documentation * Fix links to sigstore/docs in markdown files (#3064) * Update release readme (#2942) ## Contributors **Thank you to our contributors!** * Bob Callaway * Carlos Tadeu Panato Junior * Chok Yip Lau * Chris Burns * Dmitry Savintsev * Enyinna Ochulor * Hayden B * Hector Fernandez * Jakub Hrozek * Jason Hall * Jon Johnson * Luiz Carvalho * Matt Moore * Mritunjay Kumar Sharma * Mukuls77 * Ramkumar Chinchani * Sascha Grunert * Yolanda Robla Mota * priyawadhwa # v2.0.2 ## Enhancements * Update sigstore/sigstore to v1.6.2 to pick up TUF CDN change (#2891) * feat: Make cosign copy faster (#2901) * remove sget (#2885) * Require a payload to be provided with a signature (#2785) ## Bug Fixes * cmd: Change error message from KeyParseError to PubKeyParseError for verify-blob. (#2876) * Use `SOURCE_DATE_EPOCH` for OCI CreatedAt times (#2878) ## Documentation * Remove experimental warning from Fulcio flags (#2923) * add missing oidc provider (#2922) * Add zot as a supported registry (#2920) * deprecates `kms_support` docs (#2900) * chore(docs) deprecate note for usage docs (#2906) * adds note of deprecation for examples.md docs (#2899) ## Contributors * Carlos Tadeu Panato Junior * Chris Burns * Dmitry Savintsev * eiffel-fl * Hayden B * Hector Fernandez * Jon Johnson * Miloslav Trmač * priyawadhwa * Ramkumar Chinchani # v2.0.1 ## Enhancements * Add environment variable token provider (#2864) * Remove cosign policy command (#2846) * Allow customising 'go' executable with GOEXE var (#2841) * Consistent tlog warnings during verification (#2840) * Add riscv64 arch (#2821) * Default generated PEM labels to SIGSTORE (#2735) * Update privacy statement and confirmation (#2797) * Add exit codes for verify errors (#2766) * Add Buildkite provider (#2779) * verify-blob-attestation: Loosen arg requirements if --check-claims=false (#2746) ## Bug Fixes * PKCS11 sessions are now opened read only (#2853) * Makefile: date format of log should not show signatures (#2835) * Add missing flags to cosign verify dockerfile/manifest (#2830) * Add a warning to remember how to configure a custom Gitlab host (#2816) * Remove tag warning message from save/copy commands (#2799) * Mark keyless pem files with b64 (#2671) ## Contributors * Aleksandr Razumov * Batuhan Apaydın * Billy Lynch * Carlos Tadeu Panato Junior * Chris Burns * Derek Burdick * Dmitry Savintsev * favonia * Hayden B * Hector Fernandez * Ivana Atanasova * joe miller * Luiz Carvalho * Paolo Mainardi * priyawadhwa * Radoslav Dimitrov * Steve Winslow * Vincent Batts * Zack Newman # v2.0.0 This is the official 2.0.0 release of cosign! There are many new features and breaking changes from version 1.x, for a full explanation please read the Cosign 2.0 [blog post](https://blog.sigstore.dev/). ## Breaking Changes * `COSIGN_EXPERIMENTAL=1` is no longer required to have identity-based ("keyless") signing and transparency. * By default, artifact signatures will be uploaded to Rekor, for both key-based and identity-based signing. To not upload to Rekor, include `--tlog-upload=false`. * You must also include `--insecure-ignore-tlog=true` when verifying an artifact that was not uploaded to Rekor. * Examples of when you may want to skip uploading to the transparency log are if you have a private Sigstore deployment that does not use transparency or a private artifact. * We strongly encourage all other use-cases to upload artifact signatures to Rekor. Transparency is a critical component of supply chain security, to allow artifact maintainers and consumers to monitor a public log for their artifacts and signing identities. * Verification now requires identity flags, `--certificate-identity` and `--certificate-oidc-issuer`. Like verifying a signature with a public key, it's critical to specify who you trust to generate a signature for identity-based signing. See sigstore/cosign#2056 for more discussion on this change. * --certificate-email has been removed. Use --certificate-identity, which supports not only email verification but also any identity specified in a certificate, including SPIFFE, GitHub Actions, or service account identities. * Cosign no longer supports providing a certificate that does not conform to the Fulcio certificate profile, which includes setting the SubjectAlternativeName and OIDC Issuer OID. To verify with a non-conformant certificate, extract the public key from the certificate and verify with `cosign verify --key `. We are actively working on more support for custom certificates for those who want to bring their existing PKI. * Signing OCI images by tag prints a warning and is strongly discouraged, e.g. `cosign sign container.registry.io/foo:tag`. This is considered insecure since tags are mutable. If you want to specify a particular image, you are recommended to do so by digest. * SCT verification, a proof of inclusion in a certificate transparency log, is now on by default for verifying Fulcio certificates. For private deployments without certificate transparency, use `--insecure-ignore-sct=true` to skip this check. * DSSE support in verify-blob has been removed. You can now verify attestations using verify-blob-attestation. * Environment variable `SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY` has been removed. For private deployments, if you would like to set the Rekor public key to verify transparency log entries, use either a TUF setup or set `SIGSTORE_REKOR_PUBLIC_KEY` with the PEM of the custom Rekor public key.. * verify-blob no longer searches for a certificate. You must provide one with either `--certificate` or `--bundle`. * `cosign attest --type {custom|vuln}` (and `cosign verify-attestation`) will now use the RFC 3986 compliant URIs, adding https://, so that these predicate types are compliant with the in-toto specification. * The CosignPredicate envelope that wraps the predicates of SPDX and CycloneDX attestations has been removed, which was a violation of the schema specified via the predicateType field (more information). * `--force` has been removed. To skip any prompts, use `--yes`. ## Improvements * Blob attestation and verification is now supported with cosign attest-blob and cosign verify-blob-attestation. * You can now set flags via environment variables, for example instead of `--certificate-identity=email`, you can set an environment variable for `COSIGN_CERTIFICATE_IDENTITY=email`. * `--offline=true` removes the fallback to the Rekor log when verifying an artifact. Previously, if you did not provide a bundle (a persisted response from Rekor), Cosign would fallback to querying Rekor. You can now skip this fallback for offline environments. Note that if the bundle fails to verify, Cosign will not fallback and will fail early. * A Fulcio certificate can now be issued for self-managed keys by providing `--issue-certificate=true` with a key, `--key`, or security key, `--sk`. This is useful when adopting Sigstore incrementally. * Experimental support for trusted timestamping has been added. Timestamping leverages a third party to provide the timestamp that will be used to verify short-lived Fulcio certificates, which distributes trust. We will be writing more about this in an upcoming blog post! * To use a timestamp when signing a container, use` cosign sign --timestamp-server-url= `, such as https://freetsa.org/tsr, and to verify, `cosign verify --timestamp-certificate-chain= `. * To use a timestamp when signing a blob, use `cosign sign-blob --timestamp-server-url= --rfc3161-timestamp= --bundle= `, and to verify, `cosign verify-blob --rfc3161-timestamp= --timestamp-certificate-chain= --bundle= `. For specific PRs representing enhancements, bug fixes, documentation, and breaking changes, please see the sections below for prereleases v2.0.0-rc.0, v2.0.0-rc.1, v2.0.0-rc.2, and v2.0.0-rc.3. ### Thanks to all contributors! * Anish Shah * Arnaud J Le Hors * Arthur Lutz * Batuhan Apaydın * Bob Callaway * Carlos Tadeu Panato Junior * Chris Burns * Christian Loos * Emmanuel T Odeke * Hayden B * Hector Fernandez * Huang Huang * Jan Wozniak * Josh Dolitsky * Josh Wolf * Kenny Leung * Marko Mudrinić * Matt Moore * Matthias Glastra * Miloslav Trmač * Mukuls77 * Priya Wadhwa * Puerco * Stefan Zhelyazkov * Tim Seagren * Tom Meadows * Ville Aikas * Zack Newman * asraa * kpk47 * priyawadhwa # v2.0.0-rc.3 _Note: this is a prerelease for Cosign 2.0! Feel free to try it out, but know there are many breaking changes from 1.0 and the prereleases may continue to change._ ## Enhancements * Support non-Sigstore TSA requests (#2708) * Add COSIGN_OCI_EXPERIMENTAL, push .sig/.sbom using OCI 1.1+ digest tag (#2684) * Output certificate in bundle when entry is not uploaded to Rekor (#2715) * attach signature and attach sbom must use STDIN to upload raw string (#2637) ## Bug Fixes * Fix: Add missing schemes to cosign predicate types. (#2717) * Fix: Drop the `CosignPredicate` wrapper around SBOM attestations. (#2718) ## Documentation * Adds deprecation note for keyless docs (#2716) # v2.0.0-rc.2 _Note: this is a prerelease for Cosign 2.0! Feel free to try it out, but know there are many breaking changes from 1.0 and the prereleases may continue to change._ ## Enhancements * add generate-key-pair GitHub Enterprise server support (#2676) * add in format string for warning (#2699) * Support for fetching Fulcio certs with self-managed key (#2532) * 2476 predicate type download (#2484) * Upgrade to go1.20 (#2689) ## Bug Fixes * Fix prompts with Windows line endings (#2674) ## Documentation * docs(README): verify example failing on latest (#2694) ## Contributors * Anish Shah * Arthur Lutz * Carlos Tadeu Panato Junior * Christian Loos * Tim Seagren * Zack Newman * priyawadhwa # v2.0.0-rc.1 _Note: this is a prerelease for Cosign 2.0! Feel free to try it out, but know there are many breaking changes from 1.0 and the prereleases may continue to change._ Critical breaking changes include: * Certificate issuer and subject are now required on `cosign verify` ## Breaking Changes * insecure-skip-tlog-verify: rename and adapt the cert expiration check (#2620) * Deprecate --certificate-email flag. Make --certificate-identity and -… (#2411) ## Enhancements * Add warning to use digest instead of tags to other cosign commands (#2650) * Fix up UI messages (#2629) * Remove hardcoded Fulcio from output (#2621) * Fix missing privacy statement, print in multiple locations (#2622) * feat: allows custom key names for import-key-pair (#2587) * feat: support keyless verification for verify-blob-attestation (#2525) * attest-blob: add functionality for keyless signing (#2515) * Rego: add support for custom error/warning messages when evaluating rego rules (#2577) * feat: add debug information to cert validation error (#2579) ## Bug Fixes * fix: panic with unsigned local image (#2656) * Make sure a cert passed in via --cert matches the bundle cert (#2652) * fix: fix github oidc post submit test (#2594) * fix: add enhanced error messages for failing verification with TUF targets (#2589) ## Contributors * Carlos Tadeu Panato Junior * Chris Burns * Hayden B * Hector Fernandez * Huang Huang * Kenny Leung * Priya Wadhwa * Stefan Zhelyazkov * Ville Aikas * Zack Newman * asraa * dependabot[bot] * kpk47 * priyawadhwa # v2.0.0-rc.0 _Note: this is a prerelease for Cosign 2.0! Feel free to try it out, but know there are many breaking changes from 1.0 and the prereleases may continue to change._ Critical breaking changes include: * Removing the COSIGN_EXPERIMENTAL environment variable, so the default signing method is now keyless signing with Fulcio * By default Cosign will now always upload to Rekor, this can be toggled with the `--tlog-upload` flag (defaults to true) ## Breaking Changes * Breaking change: Change SCT verification behavior to default to enforcement (#2400) * Breaking change: remove --force flag from sign and attest and rely on --yes flag to skip confirmation (#2399) * Breaking change: replace --no-tlog-upload flag with --tlog-upload flag (#2397) ## Enhancements * Change go module name to github.com/sigstore/cosign/v2 for Cosign 2.0 (#2544) * Allow users to pass in a path for the --identity-token flag (#2538) * Breaking change: Respect tlog-upload=false, default to true (#2505) * Support outputing a certificate without uploading to the tlog (#2506) * Attestation/Blob signing and verification using a RFC3161 time-stamping server (#2464) * respect tlog-upload flag with TSA (#2474) * Better feedback if specifying incompatible argument on `cosign sign --attachment` (#2449) * Support TSA and Rekor verifications (#2463) * add support for tsa signing and verification of images (#2460) * cosign policy sign: remove experimental flag and make keyless signing default (#2459) * Remove experimental mode from cosign attest and verify-attestation (#2458) * Remove experimental mode from sign-blob and verify-blob (#2457) * Add --offline flag to force offline verification (#2427) * Air gap support (#2299) * Remove experimental flag from cosign sign and cosign verify (#2387) * verify: remove SIGSTORE_TRUST_REKOR_API_PUBLIC_KEY test env var for using a key from rekor's API (#2362) ## Bug Fixes * Fix the file existence check. (#2552) * Fix timestamp verification, add verify-blob tests (#2527) * fix(verify): Consolidate certificate expiry logic (#2504) * Updates to Timestamp signing and verification (#2499) * fix: removes attestation payload from attest-blob's output & no base64 encoding (#2498) * Fix path for e2e-tests badge (#2490) * Fix spdx json media type (#2479) * fix sct verificaction (#2426) ## Others * update builder image that uses go 1.19.4 (#2520) ## Contributors * Anish Shah * Arnaud J Le Hors * Batuhan Apaydın * Bob Callaway * Carlos Tadeu Panato Junior * Emmanuel T Odeke * Hayden B * Hector Fernandez * Jan Wozniak * Matthias Glastra * Miloslav Trmač * Puerco * Tom Meadows * Ville Aikas * Zack Newman * asraa * priyawadhwa # v1.13.6 _Note: v1.13.3, .4, and .5 were skipped due to issues in the release pipeline_ This release backports support for the latest TUF specification. We encourage users to upgrade to Cosign v2. ## Updates * V1 go tuf update (#3598) * Update cloud build script to latest for v1.13.x (#3615) # v1.13.2 This release backports a security fix. We encourage users to upgrade to Cosign v2. ## Updates * [release-1.13] update builder image that uses go 1.19.4 (#2521) * Backport GHSA-vfp6-jrw2-99g9 in (#3364) # v1.13.1 ## Enhancements * verify-blob-attestation: allow multiple subjects in in_toto attestation (#2341) * Add verify-blob-attestation command and tests (#2337) * Add --output-attestation flag to attest-blob and remove experimental signing (#2332) * Add attest-blob command (#2286) * Add '--cert-identity' flag to support subject alternate names for ver… (#2278) * Update Dockerfile section of README (#2323) ## Bug Fixes * Update warning when users sign images by tag. (#2313) ## Others * Remove experimental flags from attest-blob and refactor (#2338) ## Contributors * Alex Cameron * Ville Aikas * Zack Newman * asraa * kpk47 * priyawadhwa # v1.13.0 > # Highlights > * For users who have deployed a private instance of Fulcio release v0.6.x and issue certificates with the Username identity, you will need to upgrade to use this version." ## Enhancements * Add support for Fulcio username identity in SAN (https://github.com/sigstore/cosign/pull/2291) * Data race in FetchSignaturesForReference (https://github.com/sigstore/cosign/pull/2283) * Check error on chain verification failure (https://github.com/sigstore/cosign/pull/2284) * feat: improve the verification message (https://github.com/sigstore/cosign/pull/2268) * feat: use stdin as an input for predicate (https://github.com/sigstore/cosign/pull/2269) ## Bug Fixes * fix: make tlog entry lookups for online verification shard-aware (https://github.com/sigstore/cosign/pull/2297) * Fix: Create a static copy of signatures as part of verification. (https://github.com/sigstore/cosign/pull/2287) * Fix: Remove an extra registry request from verification path. (https://github.com/sigstore/cosign/pull/2285) * fix pivtool generate key touch policy (https://github.com/sigstore/cosign/pull/2282) ## Others * use scaffolding 0.4.8 for tests. (https://github.com/sigstore/cosign/pull/2280) ## Contributors * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Carlos Tadeu Panato Junior (@cpanato) * Hayden Blauzvern (@haydentherapper) * Matt Moore (@mattmoor) * Ross Tannenbaum (@RTann) * Ville Aikas (@vaikas) # v1.12.1 > # Highlights > * Pulls Fulcio root and intermediate when `--certificate-chain` is not passed into `verify-blob`. The v1.12.0 release introduced a regression: when `COSIGN_EXPERIMENTAL` was not set, cosign `verify-blob` would check a `--certificate` (without a `--certificate-chain` provided) against the operating system root CA bundle. In this release, Cosign checks the certificate against Fulcio's CA root instead (restoring the earlier behavior). ## Bug Fixes * fix: fixing breaking changes in rekor v1.12.0 upgrade (https://github.com/sigstore/cosign/pull/2260) * Fixed bug where intermediate certificates were not automatically read from the OCI chain annotation (https://github.com/sigstore/cosign/pull/2244) * fix: add COSIGN_EXPERIMENTAL=1 for verify-blob (https://github.com/sigstore/cosign/pull/2254) * fix: fix cert chain validation for verify-blob in non-experimental mode (https://github.com/sigstore/cosign/pull/2256) * fix: fix secret test, non-experimental bundle should pass (https://github.com/sigstore/cosign/pull/2249) * Fix e2e test failure, add test for local bundle without rekor bundle (https://github.com/sigstore/cosign/pull/2248) ## Contributors * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Carlos Tadeu Panato Junior (@cpanato) * Hayden Blauzvern (@haydentherapper) * n3k0m4 (@n3k0m4) # v1.12.0 **Note: This release comes with a fix for `CVE-2022-36056` described in this [Github Security Advisory](https://github.com/sigstore/cosign/security/advisories/GHSA-8gw7-4j42-w388). Please upgrade to this release ASAP** > # Highlights > **BREAKING:** The fix for [GHSA-GHSA-8gw7-4j42-w388](https://github.com/sigstore/cosign/security/advisories/GHSA-8gw7-4j42-w388) (CVE-2022-36056) means that some `verify-blob` commands that used to work may not anymore. In particular: > - When using `verify-blob` with signatures created with keyless mode, we require either `COSIGN_EXPERIMENTAL=1` or a valid Rekor bundle for offline verification passed with `--bundle`. > > If you upgrade and encounter other issues, please read the advisory in full; your prior checks may have been passing inappropriately. ## Enhancements * Add deprecation warning for sget CLI and packages (https://github.com/sigstore/cosign/pull/2019) * feat: set annotations to generate additional bash completion information (https://github.com/sigstore/cosign/pull/2221) * feat: integrate Alibaba Cloud Container Registry cred helper (https://github.com/sigstore/cosign/pull/2008) * Support non-ECDSA key types for verify-blob (https://github.com/sigstore/cosign/pull/2203) * Bump github.com/theupdateframework/go-tuf from 0.3.1 to 0.5.0 (https://github.com/sigstore/cosign/pull/2232) * feat: Add support for verifying ECDSA PEM-encoded keys. Continues deprecated hex-encoded keys for backward compatibility ## Bug Fixes * fix: fix secret test, non-experimental bundle should pass (https://github.com/sigstore/cosign/pull/2249) * Fix e2e test failure, add test for local bundle without rekor bundle (https://github.com/sigstore/cosign/pull/2248) * Clarify error when KMS provider fails to load (https://github.com/sigstore/cosign/pull/2220) ## Others * update kind to use release v0.15.0 and some version comments (https://github.com/sigstore/cosign/pull/2246) * Bump github.com/theupdateframework/go-tuf from 0.3.1 to 0.5.0 (https://github.com/sigstore/cosign/pull/2232) * update go builder to go1.19.1 (https://github.com/sigstore/cosign/pull/2241) * Bump mikefarah/yq from 4.27.3 to 4.27.5 (https://github.com/sigstore/cosign/pull/2239) * Bump github.com/open-policy-agent/opa from 0.43.0 to 0.44.0 (https://github.com/sigstore/cosign/pull/2234) * Bump github.com/google/go-cmp from 0.5.8 to 0.5.9 (https://github.com/sigstore/cosign/pull/2233) * Bump google.golang.org/api from 0.94.0 to 0.95.0 (https://github.com/sigstore/cosign/pull/2229) * upgrade setup-ko to point to new repo (https://github.com/sigstore/cosign/pull/2225) * Bump github.com/spf13/viper from 1.12.0 to 1.13.0 (https://github.com/sigstore/cosign/pull/2224) * Upgrade to go1.19 (https://github.com/sigstore/cosign/pull/2213) * remove doubl quotes, looks like it is passing as a single string to cosign and not as an array (https://github.com/sigstore/cosign/pull/2205) * use scaffolding v0.4.6. (https://github.com/sigstore/cosign/pull/2201) * Bump google.golang.org/api from 0.93.0 to 0.94.0 (https://github.com/sigstore/cosign/pull/2200) ## Contributors * Asra Ali (@asraa) * Carlos Tadeu Panato Junior (@cpanato) * Engin Diri (@dirien) * Hayden Blauzvern (@haydentherapper) * Huang Huang (@mozillazg) * Jason Hall (@imjasonh) * Priya Wadhwa (@priyawadhwa) * Ville Aikas (@vaikas) * Zack Newman (@znewman01) # v1.11.1 ## Enhancements * feat: Rework fig autocomplete command (https://github.com/sigstore/cosign/pull/2187) ## Bug Fixes * fix: fix typo that caused attestation verification failure (https://github.com/sigstore/cosign/pull/2199) ## Documention * add release cadence section in the readme (https://github.com/sigstore/cosign/pull/2179) ## Others * Bump actions/cache from 3.0.7 to 3.0.8 (https://github.com/sigstore/cosign/pull/2192) * Bump actions/dependency-review-action from 2.0.4 to 2.1.0 (https://github.com/sigstore/cosign/pull/2185) * Bump actions/setup-go from 3.2.1 to 3.3.0 (https://github.com/sigstore/cosign/pull/2196) * Bump github.com/go-openapi/swag from 0.22.1 to 0.22.3 (https://github.com/sigstore/cosign/pull/2182) * Bump github.com/sigstore/fulcio from 0.5.2 to 0.5.3 (https://github.com/sigstore/cosign/pull/2190) * Bump github.com/sigstore/rekor from 0.10.0 to 0.11.0 (https://github.com/sigstore/cosign/pull/2181) * Bump github.com/xanzy/go-gitlab from 0.72.0 to 0.73.0 (https://github.com/sigstore/cosign/pull/2191) * Bump github.com/xanzy/go-gitlab from 0.73.0 to 0.73.1 (https://github.com/sigstore/cosign/pull/2195) * Bump github/codeql-action from 2.1.18 to 2.1.19 (https://github.com/sigstore/cosign/pull/2184) * Bump github/codeql-action from 2.1.19 to 2.1.20 (https://github.com/sigstore/cosign/pull/2193) * Bump google.golang.org/api from 0.92.0 to 0.93.0 (https://github.com/sigstore/cosign/pull/2183) * Update Scorecard action to v2:alpha (https://github.com/sigstore/cosign/pull/2177) * add stale workflow using the workflow template (https://github.com/sigstore/cosign/pull/2175) * bump fulcio dep to 0.5.2 (https://github.com/sigstore/cosign/pull/2176) * bump scaffold in tests to use release v0.4.5 (https://github.com/sigstore/cosign/pull/2180) ## Contributors * Asra Ali (@asraa) * Azeem Shaikh (@azeemshaikh38) * Carlos Tadeu Panato Junior (@cpanato) * Engin Diri (@dirien) * Kenny Leung (@k4leung4) # v1.11.0 ## Enhancements * use updated device flow logic with PKCE (https://github.com/sigstore/cosign/pull/2163) ## Bug Fixes * fix panic when os.Stat returns an error besides ErrNotExists (https://github.com/sigstore/cosign/pull/2162) * fix: add env cmd to root (https://github.com/sigstore/cosign/pull/2171) * fix: rekor get tlog entry with uuid (https://github.com/sigstore/cosign/pull/2058) * fix oidc post-merge job (https://github.com/sigstore/cosign/pull/2164) * fix handling of verify-attestation types for URIs (https://github.com/sigstore/cosign/pull/2159) * fix: adds envelope hash to in-toto entries in tlog entry creation (https://github.com/sigstore/cosign/pull/2118) * fix: fix blob verification output (https://github.com/sigstore/cosign/pull/2157) * Verify the certificate chain against the Fulcio root trust by default (https://github.com/sigstore/cosign/pull/2139) ## Documention * docs: clarify wording in spec about usage of certificate chain (https://github.com/sigstore/cosign/pull/2152) * Add notes to clarify registry use. (https://github.com/sigstore/cosign/pull/2145) ## Others * Bump github.com/go-openapi/swag from 0.22.0 to 0.22.1 (https://github.com/sigstore/cosign/pull/2167) * Bump sigstore/cosign-installer from 2.5.0 to 2.5.1 (https://github.com/sigstore/cosign/pull/2168) * update e2e job to run only when push to main (https://github.com/sigstore/cosign/pull/2169) * Remove third_party (https://github.com/sigstore/cosign/pull/2166) * bump to scaffolding v0.4.4 (https://github.com/sigstore/cosign/pull/2165) * Bump sigs.k8s.io/release-utils from 0.6.0 to 0.7.3 (https://github.com/sigstore/cosign/pull/2102) * Run tests using Go 1.18 (https://github.com/sigstore/cosign/pull/2093) * Bump actions/github-script from 6.1.0 to 6.1.1 (https://github.com/sigstore/cosign/pull/2156) * Bump go.uber.org/atomic from 1.9.0 to 1.10.0 (https://github.com/sigstore/cosign/pull/2155) * Bump github.com/xanzy/go-gitlab from 0.71.0 to 0.72.0 (https://github.com/sigstore/cosign/pull/2148) * Bump tests to use scaffolding-0.4.3. (https://github.com/sigstore/cosign/pull/2153) * Bump google.golang.org/api from 0.91.0 to 0.92.0 (https://github.com/sigstore/cosign/pull/2150) * Bump actions/cache from 3.0.6 to 3.0.7 (https://github.com/sigstore/cosign/pull/2151) * Use TUF from scaffolding for validating cosign. (https://github.com/sigstore/cosign/pull/2146) * Bump github.com/hashicorp/go-secure-stdlib/parseutil from 0.1.6 to 0.1.7 (https://github.com/sigstore/cosign/pull/2141) * Bump github.com/go-openapi/swag from 0.21.1 to 0.22.0 (https://github.com/sigstore/cosign/pull/2140) * Bump github.com/xanzy/go-gitlab from 0.70.0 to 0.71.0 (https://github.com/sigstore/cosign/pull/2142) * Bump actions/cache from 3.0.5 to 3.0.6 (https://github.com/sigstore/cosign/pull/2136) * Bump github.com/go-piv/piv-go from 1.9.0 to 1.10.0 (https://github.com/sigstore/cosign/pull/2135) * Bump github/codeql-action from 2.1.17 to 2.1.18 (https://github.com/sigstore/cosign/pull/2129) * Update CHANGELOG for 1.10.1 release (https://github.com/sigstore/cosign/pull/2130) ## Contributors * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Bob Callaway (@bobcallaway) * Carlos Tadeu Panato Junior (@cpanato) * David Bendory (@bendory) * Jason Hall (@imjasonh) * Kazuma Watanabe (@wata727) * Matt Moore (@mattmoor) * Noah Kreiger (@nkreiger) * Priya Wadhwa (@priyawadhwa) * Samsondeen (@dsa0x) * Ville Aikas (@vaikas) * saso (@otms61) # v1.10.1 **Note: This release comes with a fix for CVE-2022-35929 described in this [Github Security Advisory](https://github.com/sigstore/cosign/security/advisories/GHSA-vjxv-45g9-9296). Please upgrade to this release ASAP** ## Enhancements * update cross-builder to go1.18.5 and cosign image to 1.10.0 (https://github.com/sigstore/cosign/pull/2119) * feat: attach: attestation: allow passing multiple payloads (https://github.com/sigstore/cosign/pull/2085) * Resolves #522 set Created date to time of execution (https://github.com/sigstore/cosign/pull/2108) * Fix field names in the vulnerability attestation (https://github.com/sigstore/cosign/pull/2099) * Change Result in Vulnerability Attestation to interface{} (https://github.com/sigstore/cosign/pull/2096) * Improve error message when no sigs/atts are found for an image (https://github.com/sigstore/cosign/pull/2101) * add flag to allow skipping upload to transparency log (https://github.com/sigstore/cosign/pull/2089) ## Documention * chore: fix documentation and warning on using untrusted rekor key (https://github.com/sigstore/cosign/pull/2124) * Enable Scorecard badge (https://github.com/sigstore/cosign/pull/2109) ## Bug Fixes * Merge pull request from GHSA-vjxv-45g9-9296 * Correct the type used for attest (https://github.com/sigstore/cosign/pull/2128) ## Others * Bump mikefarah/yq from 4.26.1 to 4.27.2 (https://github.com/sigstore/cosign/pull/2116) * Bump github.com/open-policy-agent/opa from 0.42.2 to 0.43.0 (https://github.com/sigstore/cosign/pull/2115) * Bump github.com/xanzy/go-gitlab from 0.69.0 to 0.70.0 (https://github.com/sigstore/cosign/pull/2120) * Bump google.golang.org/api from 0.90.0 to 0.91.0 (https://github.com/sigstore/cosign/pull/2125) * Bump google.golang.org/api from 0.89.0 to 0.90.0 (https://github.com/sigstore/cosign/pull/2111) * Bump github/codeql-action from 2.1.16 to 2.1.17 (https://github.com/sigstore/cosign/pull/2112) * Bump google.golang.org/protobuf from 1.28.0 to 1.28.1 (https://github.com/sigstore/cosign/pull/2110) * Bump google.golang.org/api from 0.88.0 to 0.89.0 (https://github.com/sigstore/cosign/pull/2106) * Bump imjasonh/setup-ko from 0.4 to 0.5 (https://github.com/sigstore/cosign/pull/2107) * Introduce a custom error type to classify errors. (https://github.com/sigstore/cosign/pull/2114) * Bump github.com/hashicorp/go-hclog from 1.2.1 to 1.2.2 (https://github.com/sigstore/cosign/pull/2103) * remove style jobs and cleanup makefile gofmt and goimports are running already with golangci-lint (https://github.com/sigstore/cosign/pull/2105) * Bump sigstore/cosign-installer from 2.4.1 to 2.5.0 (https://github.com/sigstore/cosign/pull/2100) * Remove knative/pkg deps (https://github.com/sigstore/cosign/pull/2092) ## Contributors * Asra Ali (@asraa) * Azeem Shaikh (@azeemshaikh38) * Carlos Tadeu Panato Junior (@cpanato) * Furkan Türkal (@Dentrax) * Jason Hall (@imjasonh) * Kenny Leung (@k4leung4) * Matt Moore (@mattmoor) * Teppei Fukuda (@knqyf263) * Tobias Trabelsi (@Lerentis) * saso (@otms61) # v1.10.0 ## Enhancements * Add env subcommand. (https://github.com/sigstore/cosign/pull/2051) * feat: cert-extensions verify (https://github.com/sigstore/cosign/pull/1626) * sign-blob: bundle should work independently (https://github.com/sigstore/cosign/pull/2016) * Add --oidc-provider flag to specify which provider to use for ambient credentials (https://github.com/sigstore/cosign/pull/1998) * Use pkg/fulcioroots and pkg/tuf from sigstore/sigstore (https://github.com/sigstore/cosign/pull/1866) * Add --platform flag to cosign sbom download (https://github.com/sigstore/cosign/pull/1975) * Route deprectated -version to subcommand (https://github.com/sigstore/cosign/pull/1854) * Add cyclonedx predicate type for attestations (https://github.com/sigstore/cosign/pull/1977) * Updated Azure kms commands. (https://github.com/sigstore/cosign/pull/1972) * Add spdxjson predicate type for attestations (https://github.com/sigstore/cosign/pull/1974) * Drop tuf client dependency on GCS client library (https://github.com/sigstore/cosign/pull/1967) * feat(fulcioroots): singleton error pattern (https://github.com/sigstore/cosign/pull/1965) * tuf: improve TUF client concurrency and caching (https://github.com/sigstore/cosign/pull/1953) * Separate RegExp matching of issuer/subject from strict (https://github.com/sigstore/cosign/pull/1956) ## Documention * update design doc link (https://github.com/sigstore/cosign/pull/2077) * specs: fix list formatting on SIGNATURE_SPEC (https://github.com/sigstore/cosign/pull/2030) * public-key: fix command description (https://github.com/sigstore/cosign/pull/2024) * docs(readme): add installation steps for container image for cosign binary (https://github.com/sigstore/cosign/pull/1986) * Add Cloudsmith Container Registry to tested registry list (https://github.com/sigstore/cosign/pull/1966) ## Bug Fixes * Fix OIDC test (https://github.com/sigstore/cosign/pull/2050) * Use cosign.ConfirmPrompt more consistently (https://github.com/sigstore/cosign/pull/2039) * chore: add note about SIGSTORE_REKOR_PUBLIC_KEY (https://github.com/sigstore/cosign/pull/2040) * Fix #1378 create new attestation signature in replace mode if not existent (https://github.com/sigstore/cosign/pull/2014) * encrypt values to create the github action secret (https://github.com/sigstore/cosign/pull/1990) * fix/update post build job (https://github.com/sigstore/cosign/pull/1983) * fix typos (https://github.com/sigstore/cosign/pull/1982) ## Others * Bump github.com/hashicorp/vault/sdk from 0.5.2 to 0.5.3 (https://github.com/sigstore/cosign/pull/2079) * Bump github.com/go-openapi/strfmt from 0.21.2 to 0.21.3 (https://github.com/sigstore/cosign/pull/2078) * Bump google.golang.org/api from 0.87.0 to 0.88.0 (https://github.com/sigstore/cosign/pull/2081) * Remove hack/tools.go (https://github.com/sigstore/cosign/pull/2080) * Remove replace directives in go.mod. (https://github.com/sigstore/cosign/pull/2070) * Bump mikefarah/yq from 4.25.3 to 4.26.1 (https://github.com/sigstore/cosign/pull/2076) * Bump github.com/xanzy/go-gitlab from 0.68.2 to 0.69.0 (https://github.com/sigstore/cosign/pull/2075) * Bump actions/dependency-review-action from 2.0.2 to 2.0.4 (https://github.com/sigstore/cosign/pull/2073) * Bump google.golang.org/api from 0.86.0 to 0.87.0 (https://github.com/sigstore/cosign/pull/2064) * chore(deps): CycloneDX PredicateType changed to use in-toto-golang (https://github.com/sigstore/cosign/pull/2067) * Bump github.com/open-policy-agent/opa from 0.42.0 to 0.42.2 (https://github.com/sigstore/cosign/pull/2063) * Bump google.golang.org/grpc from 1.47.0 to 1.48.0 (https://github.com/sigstore/cosign/pull/2062) * Bump actions/setup-go from 3.2.0 to 3.2.1 (https://github.com/sigstore/cosign/pull/2060) * Bump github/codeql-action from 2.1.15 to 2.1.16 (https://github.com/sigstore/cosign/pull/2065) * Bump actions/cache from 3.0.4 to 3.0.5 (https://github.com/sigstore/cosign/pull/2066) * update to go 1.18 (https://github.com/sigstore/cosign/pull/2059) * Bump github.com/open-policy-agent/opa from 0.35.0 to 0.42.0 (https://github.com/sigstore/cosign/pull/2046) * update ct/otel and etcd (https://github.com/sigstore/cosign/pull/2054) * remove tests with 1.21 k8s cluster because it is deprecated and add v1.23/24 (https://github.com/sigstore/cosign/pull/2055) * Bump sigstore/cosign-installer from 2.4.0 to 2.4.1 (https://github.com/sigstore/cosign/pull/2042) * Bump github.com/hashicorp/go-version from 1.5.0 to 1.6.0 (https://github.com/sigstore/cosign/pull/2032) * Bump github.com/spiffe/go-spiffe/v2 from 2.1.0 to 2.1.1 (https://github.com/sigstore/cosign/pull/2037) * Bump github/codeql-action from 2.1.14 to 2.1.15 (https://github.com/sigstore/cosign/pull/2038) * Bump google.golang.org/api from 0.85.0 to 0.86.0 (https://github.com/sigstore/cosign/pull/2036) * Bump github.com/stretchr/testify from 1.7.5 to 1.8.0 (https://github.com/sigstore/cosign/pull/2035) * Bump ossf/scorecard-action from 1.1.1 to 1.1.2 (https://github.com/sigstore/cosign/pull/2033) * Bump github.com/xanzy/go-gitlab from 0.68.0 to 0.68.2 (https://github.com/sigstore/cosign/pull/2029) * Bump github.com/stretchr/testify from 1.7.4 to 1.7.5 (https://github.com/sigstore/cosign/pull/2026) * Attempt to clean up pkg/cosign (https://github.com/sigstore/cosign/pull/2018) * Bump github/codeql-action from 2.1.13 to 2.1.14 (https://github.com/sigstore/cosign/pull/2023) * Bump github.com/google/go-containerregistry from 0.9.0 to 0.10.0 (https://github.com/sigstore/cosign/pull/2021) * Bump mikefarah/yq from 4.25.2 to 4.25.3 (https://github.com/sigstore/cosign/pull/2022) * Bump google.golang.org/api from 0.84.0 to 0.85.0 (https://github.com/sigstore/cosign/pull/2015) * Bump github.com/stretchr/testify from 1.7.3 to 1.7.4 (https://github.com/sigstore/cosign/pull/2010) * Bump github.com/google/go-github/v45 from 45.1.0 to 45.2.0 (https://github.com/sigstore/cosign/pull/2011) * Bump github.com/spf13/cobra from 1.4.0 to 1.5.0 (https://github.com/sigstore/cosign/pull/2012) * Bump github/codeql-action from 2.1.12 to 2.1.13 (https://github.com/sigstore/cosign/pull/2013) * Bump github.com/stretchr/testify from 1.7.2 to 1.7.3 (https://github.com/sigstore/cosign/pull/2009) * Bump actions/dependency-review-action from 2.0.1 to 2.0.2 (https://github.com/sigstore/cosign/pull/2001) * Bump github.com/hashicorp/vault/sdk from 0.5.1 to 0.5.2 (https://github.com/sigstore/cosign/pull/1996) * Bump actions/dependency-review-action from 1.0.2 to 2.0.1 (https://github.com/sigstore/cosign/pull/2000) * Bump google.golang.org/api from 0.83.0 to 0.84.0 (https://github.com/sigstore/cosign/pull/1999) * Bump sigstore/sigstore to HEAD (https://github.com/sigstore/cosign/pull/1995) * Bump github.com/hashicorp/vault/sdk from 0.5.0 to 0.5.1 (https://github.com/sigstore/cosign/pull/1988) * cleanup ci job and remove policy-controller references (https://github.com/sigstore/cosign/pull/1981) * Bump google.golang.org/api from 0.82.0 to 0.83.0 (https://github.com/sigstore/cosign/pull/1979) * cleanup: unexport kubernetes.Client method (https://github.com/sigstore/cosign/pull/1973) * Remove policy-controller now that it lives in sigstore/policy-controller (https://github.com/sigstore/cosign/pull/1976) * Bump sigstore/cosign-installer from 2.3.0 to 2.4.0 (https://github.com/sigstore/cosign/pull/1980) * Bump actions/cache from 3.0.3 to 3.0.4 (https://github.com/sigstore/cosign/pull/1970) * Bump github.com/hashicorp/go-hclog from 1.2.0 to 1.2.1 (https://github.com/sigstore/cosign/pull/1968) * Bump github.com/stretchr/testify from 1.7.1 to 1.7.2 (https://github.com/sigstore/cosign/pull/1963) * Bump google.golang.org/grpc from 1.46.2 to 1.47.0 (https://github.com/sigstore/cosign/pull/1943) * Bump github.com/hashicorp/go-secure-stdlib/parseutil from 0.1.5 to 0.1.6 (https://github.com/sigstore/cosign/pull/1958) * replace gcr.io/distroless/ to use ghcr.io/distroless/ (https://github.com/sigstore/cosign/pull/1961) * Bump github/codeql-action from 2.1.11 to 2.1.12 (https://github.com/sigstore/cosign/pull/1951) * Bump google.golang.org/api from 0.81.0 to 0.82.0 (https://github.com/sigstore/cosign/pull/1948) ## Contributors * Adolfo García Veytia (@puerco) * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Billy Lynch (@wlynch) * Bob Callaway (@bobcallaway) * Carlos Tadeu Panato Junior (@cpanato) * Ciara Carey (@ciaracarey) * Frederik Boster (@Syquel) * Furkan Türkal (@Dentrax) * Hector Fernandez (@hectorj2f) * Jason Hall (@imjasonh) * Jinhong Brejnholt (@JBrejnholt) * Josh Dolitsky (@jdolitsky) * Masahiro331 (@masahiro331) * Priya Wadhwa (@priyawadhwa) * Ville Aikas (@vaikas) * William Woodruff (@woodruffw) # v1.9.0 ## Enhancements * Do not push to public rekor. (https://github.com/sigstore/cosign/pull/1931) * Add privacy statement for PII storage (https://github.com/sigstore/cosign/pull/1909) * Add support for "**" in image glob matching (https://github.com/sigstore/cosign/pull/1914) * [cosigned] Rename cosigned references to policy-controller (https://github.com/sigstore/cosign/pull/1893) * [cosigned] Remove undefined apiGroups from policy clusterrole (https://github.com/sigstore/cosign/pull/1896) * tree: support --attachment-tag-prefix (https://github.com/sigstore/cosign/pull/1900) * v1beta1 API for cosigned (https://github.com/sigstore/cosign/pull/1890) * tree: only report artifacts that are present (https://github.com/sigstore/cosign/pull/1872) * Check certificate policy flags with only a certificate (https://github.com/sigstore/cosign/pull/1869) * Normalize certificate flag names (https://github.com/sigstore/cosign/pull/1868) * Add rekor.0.pub TUF target to unit tests (https://github.com/sigstore/cosign/pull/1860) * If SBOM ref has .json suffix, assume JSON mediatype (https://github.com/sigstore/cosign/pull/1859) * sget: Enable KMS providers for sget (https://github.com/sigstore/cosign/pull/1852) * Use filepath match instead of glob (https://github.com/sigstore/cosign/pull/1842) * cosigned: Fix podAntiAffinity labels (https://github.com/sigstore/cosign/pull/1841) * Add function to explictly request a certain provider (https://github.com/sigstore/cosign/pull/1837) * Validate tlog entry when verifying signature via public key. (https://github.com/sigstore/cosign/pull/1833) * New flag --oidc-providers-disable to disable OIDC providers (https://github.com/sigstore/cosign/pull/1832) * Add auth flow option to KeyOpts. (https://github.com/sigstore/cosign/pull/1827) * cosigned: Test unsupported KMS providers (https://github.com/sigstore/cosign/pull/1820) * Refactor fulcio signer to take in KeyOpts (take 2) (https://github.com/sigstore/cosign/pull/1818) * feat: add rego policy support (https://github.com/sigstore/cosign/pull/1817) * [Cosigned] Add signature pull secrets (https://github.com/sigstore/cosign/pull/1805) * Check failure message of policy that fails with issuer mismatch (https://github.com/sigstore/cosign/pull/1815) * Support PKCS1 encoded and non-ECDSA CT log public keys (https://github.com/sigstore/cosign/pull/1806) ## Documention * update README with ebpf modules (https://github.com/sigstore/cosign/pull/1888) * Point git commmit FUN.md to gitsign! (https://github.com/sigstore/cosign/pull/1874) * Add IBM Cloud Container Registry to tested registry list (https://github.com/sigstore/cosign/pull/1856) * Document Staging instance usage with Keyless (https://github.com/sigstore/cosign/pull/1824) ## Bug Fixes * fix: fix #1930 for AWS KMS formats (https://github.com/sigstore/cosign/pull/1946) * fix: fix fetching updated targets from TUF root (https://github.com/sigstore/cosign/pull/1921) * Fix piv-tool generate-key command in TOKENS doc (https://github.com/sigstore/cosign/pull/1850) ## Others * remove deprecation (https://github.com/sigstore/cosign/pull/1952) * Bump github.com/aws/aws-sdk-go-v2 from 1.14.0 to 1.16.4 (https://github.com/sigstore/cosign/pull/1949) * update cross-builder image to use go1.17.11 (https://github.com/sigstore/cosign/pull/1950) * Bump ossf/scorecard-action from 1.1.0 to 1.1.1 (https://github.com/sigstore/cosign/pull/1945) * Bump github.com/secure-systems-lab/go-securesystemslib (https://github.com/sigstore/cosign/pull/1944) * Bump actions/cache from 3.0.2 to 3.0.3 (https://github.com/sigstore/cosign/pull/1937) * Bump mikefarah/yq from 4.25.1 to 4.25.2 (https://github.com/sigstore/cosign/pull/1933) * Bump github.com/spf13/viper from 1.11.0 to 1.12.0 (https://github.com/sigstore/cosign/pull/1924) * Bump github.com/hashicorp/vault/sdk from 0.4.1 to 0.5.0 (https://github.com/sigstore/cosign/pull/1926) * Bump actions/setup-go from 3.1.0 to 3.2.0 (https://github.com/sigstore/cosign/pull/1927) * Bump actions/dependency-review-action from 1.0.1 to 1.0.2 (https://github.com/sigstore/cosign/pull/1915) * Bump google-github-actions/auth from 0.7.3 to 0.8.0 (https://github.com/sigstore/cosign/pull/1916) * Bump ossf/scorecard-action from 1.0.4 to 1.1.0 (https://github.com/sigstore/cosign/pull/1922) * Bump google.golang.org/api from 0.80.0 to 0.81.0 (https://github.com/sigstore/cosign/pull/1918) * Bump github.com/armon/go-metrics from 0.3.11 to 0.4.0 (https://github.com/sigstore/cosign/pull/1919) * Bump github.com/xanzy/go-gitlab from 0.66.0 to 0.68.0 (https://github.com/sigstore/cosign/pull/1920) * Bump github.com/xanzy/go-gitlab from 0.65.0 to 0.66.0 (https://github.com/sigstore/cosign/pull/1913) * Move deprecated dependency: google/trillian/merkle to transparency-dev (https://github.com/sigstore/cosign/pull/1910) * Bump github.com/hashicorp/go-version from 1.4.0 to 1.5.0 (https://github.com/sigstore/cosign/pull/1902) * Bump github.com/hashicorp/go-secure-stdlib/parseutil from 0.1.4 to 0.1.5 (https://github.com/sigstore/cosign/pull/1883) * Bump cloud.google.com/go/storage from 1.22.0 to 1.22.1 (https://github.com/sigstore/cosign/pull/1906) * Bump actions/upload-artifact from 3.0.0 to 3.1.0 (https://github.com/sigstore/cosign/pull/1907) * The timeout arg in golangci-lint has been moved to the generic args param. (https://github.com/sigstore/cosign/pull/1901) * Update go-tuf (https://github.com/sigstore/cosign/pull/1894) * Bump google.golang.org/api from 0.79.0 to 0.80.0 (https://github.com/sigstore/cosign/pull/1897) * Bump google-github-actions/auth from 0.7.2 to 0.7.3 (https://github.com/sigstore/cosign/pull/1898) * Bump github/codeql-action from 2.1.10 to 2.1.11 (https://github.com/sigstore/cosign/pull/1891) * Update github.com/google/go-containerregistry/pkg/authn/k8schain module to f1b065c6cb3d (https://github.com/sigstore/cosign/pull/1889) * Remove dependency on deprecated github.com/pkg/errors (https://github.com/sigstore/cosign/pull/1887) * Bump google.golang.org/grpc from 1.46.0 to 1.46.2 (https://github.com/sigstore/cosign/pull/1884) * Bump google-github-actions/auth from 0.7.1 to 0.7.2 (https://github.com/sigstore/cosign/pull/1886) * go.mod: format go.mod (https://github.com/sigstore/cosign/pull/1879) * chore: remove regex from image pattern (https://github.com/sigstore/cosign/pull/1873) * Bump actions/dependency-review-action (https://github.com/sigstore/cosign/pull/1875) * Bump actions/github-script from 6.0.0 to 6.1.0 (https://github.com/sigstore/cosign/pull/1876) * Bump actions/setup-go from 3.0.0 to 3.1.0 (https://github.com/sigstore/cosign/pull/1870) * Update go to 1.17.10 / cosign image to 1.18.0 and actions setup go (https://github.com/sigstore/cosign/pull/1861) * Bump github/codeql-action from 2.1.9 to 2.1.10 (https://github.com/sigstore/cosign/pull/1863) * Bump golangci/golangci-lint-action from 3.1.0 to 3.2.0 (https://github.com/sigstore/cosign/pull/1864) * Bump google.golang.org/api from 0.78.0 to 0.79.0 (https://github.com/sigstore/cosign/pull/1858) * Bump github.com/xanzy/go-gitlab from 0.64.0 to 0.65.0 (https://github.com/sigstore/cosign/pull/1857) * Bump github.com/go-openapi/runtime from 0.24.0 to 0.24.1 (https://github.com/sigstore/cosign/pull/1851) * remove exclude from go.mod (https://github.com/sigstore/cosign/pull/1846) * Bump github.com/hashicorp/go-plugin from 1.4.3 to 1.4.4 (https://github.com/sigstore/cosign/pull/1843) * Bump google.golang.org/api from 0.77.0 to 0.78.0 (https://github.com/sigstore/cosign/pull/1838) * Bump mikefarah/yq from 4.24.5 to 4.25.1 (https://github.com/sigstore/cosign/pull/1831) * Bump google.golang.org/api from 0.76.0 to 0.77.0 (https://github.com/sigstore/cosign/pull/1829) * Bump github.com/go-openapi/runtime from 0.23.3 to 0.24.0 (https://github.com/sigstore/cosign/pull/1830) * Bump github.com/spiffe/go-spiffe/v2 from 2.0.0 to 2.1.0 (https://github.com/sigstore/cosign/pull/1828) * chore(deps): Included dependency review (https://github.com/sigstore/cosign/pull/1792) * Bump sigstore/cosign-installer from 2.2.1 to 2.3.0 (https://github.com/sigstore/cosign/pull/1813) * Bump github/codeql-action from 2.1.8 to 2.1.9 (https://github.com/sigstore/cosign/pull/1814) * Bump google.golang.org/api from 0.75.0 to 0.76.0 (https://github.com/sigstore/cosign/pull/1810) * Bump github.com/google/go-cmp from 0.5.7 to 0.5.8 (https://github.com/sigstore/cosign/pull/1809) * Bump github.com/armon/go-metrics from 0.3.10 to 0.3.11 (https://github.com/sigstore/cosign/pull/1808) ## Contributors * Asra Ali (@asraa) * Adolfo García Veytia (@puerco) * Andrés Torres (@elfotografo007) * Billy Lynch (@wlynch) * Carlos Tadeu Panato Junior (@cpanato) * Dan Lorenc (@dlorenc) * Denny (@DennyHoang) * Eitan Yarmush (@EItanya) * Hayden Blauzvern (@haydentherapper) * Hector Fernandez (@hectorj2f) * Jack Baines (@bainsy88) * Jason Hall (@imjasonh) * Josh Dolitsky (@jdolitsky) * Kenny Leung (@k4leung4) * Koichi Shiraishi (@zchee) * Naveen Srinivasan (@naveensrinivasan) * Neal McBurnett (@nealmcb) * Priya Wadhwa (@priyawadhwa) * Rob Best (@ribbybibby) * Tomasz Janiszewski (@janisz) * Ville Aikas (@vaikas) * Vladimir Nachev (@vpnachev) # v1.8.0 _NOTE_: If you use Fulcio to issue certificates you will need to use this release. ## Enhancements * Support PKCS1 encoded and non-ECDSA CT log public keys (https://github.com/sigstore/cosign/pull/1806) * Load in intermediate cert pool from TUF (https://github.com/sigstore/cosign/pull/1804) * Don't fail open in VerifyBundle (https://github.com/sigstore/cosign/pull/1648) * Handle context cancelled properly + tests. (https://github.com/sigstore/cosign/pull/1796) * Allow passing keys via environment variables (`env://` refs) (https://github.com/sigstore/cosign/pull/1794) * Add parallelization for processing policies / authorities. (https://github.com/sigstore/cosign/pull/1795) * Attestations + policy in cip. (https://github.com/sigstore/cosign/pull/1772) * Refactor fulcio signer to take in KeyOpts. (https://github.com/sigstore/cosign/pull/1788) * Remove the dependency on v1alpha1.Identity which brings in (https://github.com/sigstore/cosign/pull/1790) * Add Fulcio intermediate CA certificate to intermediate pool (https://github.com/sigstore/cosign/pull/1774) * Cosigned validate against remote sig src (https://github.com/sigstore/cosign/pull/1754) * tuf: add debug info if tuf update fails (https://github.com/sigstore/cosign/pull/1766) * Break the CIP action tests into a sh script. (https://github.com/sigstore/cosign/pull/1767) * [policy-webhook] The webhooks name is now configurable via --(validating|mutating)-webhook-name flags (https://github.com/sigstore/cosign/pull/1757) * Verify embedded SCTs (https://github.com/sigstore/cosign/pull/1731) * Validate issuer/subject regexp in validate webhook. (https://github.com/sigstore/cosign/pull/1761) * Add intermediate CA certificate pool for Fulcio (https://github.com/sigstore/cosign/pull/1749) * [cosigned] The webhook name is now configurable via --webhook-name flag (https://github.com/sigstore/cosign/pull/1726) * Use bundle log ID to find verification key (https://github.com/sigstore/cosign/pull/1748) * Refactor policy related code, add support for vuln verify (https://github.com/sigstore/cosign/pull/1747) * Create convert functions for internal CIP (https://github.com/sigstore/cosign/pull/1736) * Move the KMS integration imports into the binary entrypoints (https://github.com/sigstore/cosign/pull/1744) ## Bug Fixes * Fix a bug where an error would send duplicate results. (https://github.com/sigstore/cosign/pull/1797) * fix: more informative error (https://github.com/sigstore/cosign/pull/1778) * fix: add support for rsa keys (https://github.com/sigstore/cosign/pull/1768) * Implement identities, fix bug in webhook validation. (https://github.com/sigstore/cosign/pull/1759) ## Others * update changelog for 1.8.0 (https://github.com/sigstore/cosign/pull/1807) * add changelog for release v1.8.0 (https://github.com/sigstore/cosign/pull/1803) * Bump github.com/hashicorp/go-retryablehttp from 0.7.0 to 0.7.1 (https://github.com/sigstore/cosign/pull/1758) * Bump google-github-actions/auth from 0.7.0 to 0.7.1 (https://github.com/sigstore/cosign/pull/1801) * Bump google.golang.org/grpc from 1.45.0 to 1.46.0 (https://github.com/sigstore/cosign/pull/1800) * Bump github.com/xanzy/go-gitlab from 0.63.0 to 0.64.0 (https://github.com/sigstore/cosign/pull/1799) * Revert "Refactor fulcio signer to take in KeyOpts. (https://github.com/sigstore/cosign/pull/1788)" (https://github.com/sigstore/cosign/pull/1798) * chore: add rego function to consume modules (https://github.com/sigstore/cosign/pull/1787) * test: add cue unit tests (https://github.com/sigstore/cosign/pull/1791) * Run update-codegen. (https://github.com/sigstore/cosign/pull/1789) * Bump actions/checkout from 3.0.1 to 3.0.2 (https://github.com/sigstore/cosign/pull/1783) * Bump github.com/mitchellh/mapstructure from 1.4.3 to 1.5.0 (https://github.com/sigstore/cosign/pull/1782) * Bump k8s.io/code-generator from 0.23.5 to 0.23.6 (https://github.com/sigstore/cosign/pull/1781) * Bump google.golang.org/api from 0.74.0 to 0.75.0 (https://github.com/sigstore/cosign/pull/1780) * Bump cuelang.org/go from 0.4.2 to 0.4.3 (https://github.com/sigstore/cosign/pull/1779) * Bump codecov/codecov-action from 3.0.0 to 3.1.0 (https://github.com/sigstore/cosign/pull/1784) * Bump actions/checkout from 3.0.0 to 3.0.1 (https://github.com/sigstore/cosign/pull/1764) * Bump mikefarah/yq from 4.24.4 to 4.24.5 (https://github.com/sigstore/cosign/pull/1765) * chore: add warning when downloading a sBOM (https://github.com/sigstore/cosign/pull/1763) * chore: add warn when attaching sBOM (https://github.com/sigstore/cosign/pull/1756) * Bump sigstore/cosign-installer from 2.2.0 to 2.2.1 (https://github.com/sigstore/cosign/pull/1752) * update go builder and cosign images (https://github.com/sigstore/cosign/pull/1755) * test: create fake TUF test root and create test SETs for verification (https://github.com/sigstore/cosign/pull/1750) * Bump github.com/spf13/viper from 1.10.1 to 1.11.0 (https://github.com/sigstore/cosign/pull/1751) * Bump mikefarah/yq from 4.24.2 to 4.24.4 (https://github.com/sigstore/cosign/pull/1746) * Bump github.com/xanzy/go-gitlab from 0.62.0 to 0.63.0 (https://github.com/sigstore/cosign/pull/1745) ## Contributors * Asra Ali (@asraa) * Billy Lynch (@wlynch) * Carlos Tadeu Panato Junior (@cpanato) * Denny (@DennyHoang) * Hayden Blauzvern (@haydentherapper) * Hector Fernandez (@hectorj2f) * Matt Moore (@mattmoor) * Ville Aikas (@vaikas) * Vladimir Nachev (@vpnachev) * Youssef Bel Mekki (@ybelMekk) * Zack Newman (@znewman01) # v1.7.2 ## Bug Fixes * Make public all types required to use ValidatePolicy (https://github.com/sigstore/cosign/pull/1727) * fix: add permissions to patch events (https://github.com/sigstore/cosign/pull/1722) * Fix publicKey unmarshal (https://github.com/sigstore/cosign/pull/1719) * Update release job (https://github.com/sigstore/cosign/pull/1720) * Makefile: fix directory not found error (https://github.com/sigstore/cosign/pull/1718) ## Others * Remove newline from download sbom output (https://github.com/sigstore/cosign/pull/1732) * Bump github.com/hashicorp/go-uuid from 1.0.2 to 1.0.3 (https://github.com/sigstore/cosign/pull/1724) * Add unit tests for IntotoAttestation verifier. (https://github.com/sigstore/cosign/pull/1728) * Bump github/codeql-action from 2.1.7 to 2.1.8 (https://github.com/sigstore/cosign/pull/1725) * Bump cloud.google.com/go/storage from 1.21.0 to 1.22.0 (https://github.com/sigstore/cosign/pull/1721) * Bump sigstore/cosign-installer from 2.1.0 to 2.2.0 (https://github.com/sigstore/cosign/pull/1723) * Bump github.com/xanzy/go-gitlab from 0.61.0 to 0.62.0 (https://github.com/sigstore/cosign/pull/1711) * Bump google-github-actions/auth from 0.6.0 to 0.7.0 (https://github.com/sigstore/cosign/pull/1712) * Bump github/codeql-action from 2.1.6 to 2.1.7 (https://github.com/sigstore/cosign/pull/1713) * Bump codecov/codecov-action from 2.1.0 to 3 (https://github.com/sigstore/cosign/pull/1714) ## Contributors * Carlos Tadeu Panato Junior (@cpanato) * Denny (@DennyHoang) * Hector Fernandez (@hectorj2f) * Josh Dolitsky (@jdolitsky) * Rob Best (@ribbybibby) * Ville Aikas (@vaikas) # v1.7.1 ## Bug Fixes * commenting out the copy from gcr to ghcr due issues on github side (https://github.com/sigstore/cosign/pull/1715) # v1.7.0 ## Enhancements * sign: set the oidc redirect uri (https://github.com/sigstore/cosign/pull/1675) * Use ValidatePubKey from sigstore/sigstore (https://github.com/sigstore/cosign/pull/1676) * Remove the hardcoded sigstore audience (https://github.com/sigstore/cosign/pull/1698) * verify: remove extra calls to rekor for verify and verify-blob (https://github.com/sigstore/cosign/pull/1694) * add leaf hash verification (https://github.com/sigstore/cosign/pull/1688) * cosign clean: Don't log failure if the registry responds with 404 (https://github.com/sigstore/cosign/pull/1687) * Update error message for verify/verify attestation (https://github.com/sigstore/cosign/pull/1686) * change file_name_template to PackageName (https://github.com/sigstore/cosign/pull/1683) * Make `cosign copy` copy metadata attached to child images. (https://github.com/sigstore/cosign/pull/1682) * Add support for cert and cert chain flags with PKCS11 tokens (https://github.com/sigstore/cosign/pull/1671) * Find all valid entries in verify-blob (https://github.com/sigstore/cosign/pull/1673) * Refactor based on discussions in #1650 (https://github.com/sigstore/cosign/pull/1674) * feat: add ability to override registry keychain (https://github.com/sigstore/cosign/pull/1666) * Add specific suffixes mediaTypes to sboms (https://github.com/sigstore/cosign/pull/1663) * Add certificate chain flag for signing (https://github.com/sigstore/cosign/pull/1656) * First batch of followups to #1650 (https://github.com/sigstore/cosign/pull/1664) * Add support for certificate chain to verify certificate (https://github.com/sigstore/cosign/pull/1659) * Use syscall.Stdin for input handle. Fixes #1153 (https://github.com/sigstore/cosign/pull/1657) * Shorten example OAuth URL (https://github.com/sigstore/cosign/pull/1661) * Prompt user before running `cosign clean` (https://github.com/sigstore/cosign/pull/1649) * Add support for intermediate certificates when verifiying (https://github.com/sigstore/cosign/pull/1631) * feat: tree command utility (https://github.com/sigstore/cosign/pull/1603) * Validate authority keys (https://github.com/sigstore/cosign/pull/1623) * improve cosigned validation error messages (https://github.com/sigstore/cosign/pull/1618) * Init entity from ociremote when signing a digest ref (https://github.com/sigstore/cosign/pull/1616) * Add two env variables. One for using Rekor public key from OOB and (https://github.com/sigstore/cosign/pull/1610) * Ensure entry is removed from CM on secret error. (https://github.com/sigstore/cosign/pull/1605) * Validate a public key in a secret is valid. (https://github.com/sigstore/cosign/pull/1602) * Add public key validation (https://github.com/sigstore/cosign/pull/1598) * Add ability to inline secrets from SecretRef to configmap. (https://github.com/sigstore/cosign/pull/1595) * 1417 policy validations (https://github.com/sigstore/cosign/pull/1548) * Support deletion of ClusterImagePolicy (https://github.com/sigstore/cosign/pull/1580) * Start of the necessary pieces to get #1418 and #1419 implemented (https://github.com/sigstore/cosign/pull/1562) ## Bug Fixes * Fix handling of policy in verify-attestation (https://github.com/sigstore/cosign/pull/1672) * Fix relative paths in Gitub OIDC blob test (https://github.com/sigstore/cosign/pull/1677) * fix build date format for version command (https://github.com/sigstore/cosign/pull/1644) * Fix 1608, 1613 (https://github.com/sigstore/cosign/pull/1617) * Fix copy/paste mistake in repo name. (https://github.com/sigstore/cosign/pull/1600) * Fix #1592 move authorities as siblings of images. (https://github.com/sigstore/cosign/pull/1593) * Fix piping 'cosign verify' using fulcio/rekor (https://github.com/sigstore/cosign/pull/1590) * Fix #1583 #1582. Disallow regex now until implemented. (https://github.com/sigstore/cosign/pull/1584) * Don't lowercase input image refs, just fail (https://github.com/sigstore/cosign/pull/1586) ## Documention * Document Elastic container registry support (https://github.com/sigstore/cosign/pull/1641) * FUN.md broke when RecordObj changed to HashedRecordObj (https://github.com/sigstore/cosign/pull/1633) * Add example using AWS Key Management Service (KMS) (https://github.com/sigstore/cosign/pull/1564) ## Others * Use the github actions from sigstore/scaffolding. (https://github.com/sigstore/cosign/pull/1699) * Bump google.golang.org/api from 0.73.0 to 0.74.0 (https://github.com/sigstore/cosign/pull/1695) * Bump github/codeql-action from 1.1.5 to 2.1.6 (https://github.com/sigstore/cosign/pull/1690) * Bump actions/cache from 3.0.0 to 3.0.1 (https://github.com/sigstore/cosign/pull/1689) * Add e2e test for attest / verify-attestation (https://github.com/sigstore/cosign/pull/1685) * Use cosign @ HEAD for Github OIDC sign blob test (https://github.com/sigstore/cosign/pull/1678) * Bump mikefarah/yq from 4.23.1 to 4.24.2 (https://github.com/sigstore/cosign/pull/1670) * remove replace directive (https://github.com/sigstore/cosign/pull/1669) * update font when output the cosign version (https://github.com/sigstore/cosign/pull/1668) * Use ClusterImagePolicy with Keyless + e2e tests for CIP with kind (https://github.com/sigstore/cosign/pull/1650) * Bump google.golang.org/protobuf from 1.27.1 to 1.28.0 (https://github.com/sigstore/cosign/pull/1646) * Bump mikefarah/yq from 4.22.1 to 4.23.1 (https://github.com/sigstore/cosign/pull/1639) * Bump actions/cache from 2.1.7 to 3 (https://github.com/sigstore/cosign/pull/1640) * Bump github.com/go-openapi/runtime from 0.23.2 to 0.23.3 (https://github.com/sigstore/cosign/pull/1638) * Add extra label and change the latest tag to unstable for non tagged releases (https://github.com/sigstore/cosign/pull/1637) * push latest tag when building a release (https://github.com/sigstore/cosign/pull/1636) * update crane to v0.8.0 release (https://github.com/sigstore/cosign/pull/1635) * Bump github.com/xanzy/go-gitlab from 0.59.0 to 0.60.0 (https://github.com/sigstore/cosign/pull/1634) * Included OpenSSF Best Practices Badge (https://github.com/sigstore/cosign/pull/1628) * Use latest knative/pkg's configmap informer (https://github.com/sigstore/cosign/pull/1615) * Bump github.com/stretchr/testify from 1.7.0 to 1.7.1 (https://github.com/sigstore/cosign/pull/1621) * Bump google.golang.org/api from 0.72.0 to 0.73.0 (https://github.com/sigstore/cosign/pull/1619) * Bump github/codeql-action from 1.1.4 to 1.1.5 (https://github.com/sigstore/cosign/pull/1622) * Bump ecr-login to pick up WithLogger rename (https://github.com/sigstore/cosign/pull/1624) * Bump to knative pkg 1.3 (https://github.com/sigstore/cosign/pull/1614) * Bump google.golang.org/api from 0.71.0 to 0.72.0 (https://github.com/sigstore/cosign/pull/1612) * Use reusuable release workflow in sigstore/sigstore (https://github.com/sigstore/cosign/pull/1599) * Bump github.com/spiffe/go-spiffe/v2 from 2.0.0-beta.12 to 2.0.0 (https://github.com/sigstore/cosign/pull/1597) * Bump mikefarah/yq from 4.21.1 to 4.22.1 (https://github.com/sigstore/cosign/pull/1589) * Bump google.golang.org/grpc from 1.44.0 to 1.45.0 (https://github.com/sigstore/cosign/pull/1587) * Bump github.com/spf13/cobra from 1.3.0 to 1.4.0 (https://github.com/sigstore/cosign/pull/1588) * Bump github.com/xanzy/go-gitlab from 0.58.0 to 0.59.0 (https://github.com/sigstore/cosign/pull/1579) * Bump google-github-actions/setup-gcloud from 0.5.1 to 0.6.0 (https://github.com/sigstore/cosign/pull/1578) * Bump github.com/hashicorp/go-hclog from 1.1.0 to 1.2.0 (https://github.com/sigstore/cosign/pull/1576) * Bump google.golang.org/api from 0.70.0 to 0.71.0 (https://github.com/sigstore/cosign/pull/1577) * Bump github/codeql-action from 1.1.3 to 1.1.4 (https://github.com/sigstore/cosign/pull/1565) * add definition for artifact hub to verify the ownership (https://github.com/sigstore/cosign/pull/1563) * Bump sigstore/cosign-installer from 2.0.1 to 2.1.0 (https://github.com/sigstore/cosign/pull/1561) * Bump github.com/go-openapi/runtime from 0.23.1 to 0.23.2 (https://github.com/sigstore/cosign/pull/1559) * Bump github.com/xanzy/go-gitlab from 0.57.0 to 0.58.0 (https://github.com/sigstore/cosign/pull/1560) * Update hashicorp/parseutil to v0.1.3. (https://github.com/sigstore/cosign/pull/1557) * Mirror signed release images from GCR to GHCR as part of release with Cloud Build. (https://github.com/sigstore/cosign/pull/1547) * Bump github.com/xanzy/go-gitlab from 0.56.0 to 0.57.0 (https://github.com/sigstore/cosign/pull/1552) * Bump actions/upload-artifact from 2.3.1 to 3 (https://github.com/sigstore/cosign/pull/1553) * pkcs11: fix build instructions (https://github.com/sigstore/cosign/pull/1550) * Update images for release job (https://github.com/sigstore/cosign/pull/1551) ## Contributors * Adam A.G. Shamblin (@coyote240) * Adolfo García Veytia (@puerco) * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Carlos Tadeu Panato Junior (@cpanato) * Dan Lorenc (@dlorenc) * Davi Garcia (@davivcgarcia) * Hayden Blauzvern (@haydentherapper) * Hector Fernandez (@hectorj2f) * James Strong (@strongjz) * Jason Hall (@imjasonh) * Kavitha (@kkavitha) * Kenny Leung (@k4leung4) * Luiz Carvalho (@lcarva) * Marco Franssen (@marcofranssen) * Mark Percival (@mdp) * Matt Moore (@mattmoor) * Maxime Gréau (@mgreau) * Mitch Thomas (@MitchellJThomas) * Naveen Srinivasan (@naveensrinivasan) * Nghia Tran (@tcnghia) * Priya Wadhwa (@priyawadhwa) * Radoslav Gerganov (@rgerganov) * Thomas Strömberg (@tstromberg) * Ville Aikas (@vaikas) * noamichael (@noamichael) # v1.6.0 ## Security Fixes * CVE-2022-23649 - Make sure signature in Rekor bundle matches signature being verified ## Enhancements * Change Fulcio URL default to be fulcio.sigstore.dev (https://github.com/sigstore/cosign/pull/1529) * Add CertExtensions func to extract all extensions (https://github.com/sigstore/cosign/pull/1515) * Add a dummy.go file to allow vendoring config (https://github.com/sigstore/cosign/pull/1520) * Add skeleton reconciler for cosigned API CRD. (https://github.com/sigstore/cosign/pull/1513) * use v6 api calls (https://github.com/sigstore/cosign/pull/1511) * This sets up the scaffolding for the `cosigned` CRD types. (https://github.com/sigstore/cosign/pull/1504) * add correct layer media type to attach attestation (https://github.com/sigstore/cosign/pull/1503) * Pick up some of the shared workflows (https://github.com/sigstore/cosign/pull/1490) * feat: support other types in copy cmd (https://github.com/sigstore/cosign/pull/1493) * Pick up a change to quiet ECR-login logging. (https://github.com/sigstore/cosign/pull/1491) * Merge pull request from GHSA-ccxc-vr6p-4858 * fix(sign): refactor unsupported provider log (https://github.com/sigstore/cosign/pull/1464) * Print message when verifying with old TUF targets (https://github.com/sigstore/cosign/pull/1468) * convert release cosigned to also generate yaml artifact. (https://github.com/sigstore/cosign/pull/1453) * Streamline `SignBlobCmd` API with `SignCmd` (https://github.com/sigstore/cosign/pull/1454) * feat: add -buildid= to ldflags (https://github.com/sigstore/cosign/pull/1451) * Fetch verification targets by TUF custom metadata (https://github.com/sigstore/cosign/pull/1423) * feat: fig autocomplete feature (https://github.com/sigstore/cosign/pull/1360) * Improve log lines to match with implementation (https://github.com/sigstore/cosign/pull/1432) * use the upstream kubernetes version lib and ldflags (https://github.com/sigstore/cosign/pull/1413) * feat: enhance clean cmd capability (https://github.com/sigstore/cosign/pull/1430) * Remove TUF timestamp from OCI signature bundle (https://github.com/sigstore/cosign/pull/1428) * Allow `PassFunc` to be `nil` (https://github.com/sigstore/cosign/pull/1426) * Add ability to override the Spiffe socket via environmental variable: (https://github.com/sigstore/cosign/pull/1421) * Improve error message when image is not found in registry (https://github.com/sigstore/cosign/pull/1410) * add root status output (https://github.com/sigstore/cosign/pull/1404) * feat: login command (https://github.com/sigstore/cosign/pull/1398) * Minor refactor to verify SCT and Rekor entry with multiple keys (https://github.com/sigstore/cosign/pull/1396) * Add Cosign logo to README (https://github.com/sigstore/cosign/pull/1395) * Add `--timeout` support to `sign` command (https://github.com/sigstore/cosign/pull/1379) ## Bug Fixes * bug fix: import ed25519 keys and fix error handling (https://github.com/sigstore/cosign/pull/1518) * Fix wording on attach attestation help (https://github.com/sigstore/cosign/pull/1480) * fix(sign): kms unspported message (https://github.com/sigstore/cosign/pull/1475) * Fix incorrect error check when verifying SCT (https://github.com/sigstore/cosign/pull/1422) * make imageRef lowercase before parsing (https://github.com/sigstore/cosign/pull/1409) * Add a new line after password input (https://github.com/sigstore/cosign/pull/1407) * Fix comparison in replace option for attestation (https://github.com/sigstore/cosign/pull/1366) ## Documention * Quay OCI Support in README (https://github.com/sigstore/cosign/pull/1539) * feat: nominate Dentrax as codeowner (https://github.com/sigstore/cosign/pull/1492) * add initial changelog for 1.5.2 (https://github.com/sigstore/cosign/pull/1483) * fix tkn link in readme (https://github.com/sigstore/cosign/pull/1459) * Add FEATURES.md and DEPRECATIONS.md (https://github.com/sigstore/cosign/pull/1429) * Update the cosign keyless documentation to point to the GA release. (https://github.com/sigstore/cosign/pull/1427) * Fix link for SECURITY.md (https://github.com/sigstore/cosign/pull/1399) ## Others * Consistent parenthesis use in Makefile (https://github.com/sigstore/cosign/pull/1541) * Bump github.com/xanzy/go-gitlab from 0.55.1 to 0.56.0 (https://github.com/sigstore/cosign/pull/1538) * add rpm,deb and apks for cosign packages (https://github.com/sigstore/cosign/pull/1537) * update github.com/hashicorp/vault/sdk, codegen and go module to 1.17 (https://github.com/sigstore/cosign/pull/1536) * images: remove --bare flags that conflict with --base-import-paths (https://github.com/sigstore/cosign/pull/1533) * Bump actions/checkout from 2 to 3 (https://github.com/sigstore/cosign/pull/1531) * Add codecov as github action, set permissions to read content only (https://github.com/sigstore/cosign/pull/1530) * Bump github.com/spiffe/go-spiffe/v2 from 2.0.0-beta.11 to 2.0.0-beta.12 (https://github.com/sigstore/cosign/pull/1528) * Bump actions/setup-go from 2 to 3 (https://github.com/sigstore/cosign/pull/1527) * Bump golangci/golangci-lint-action from 3.0.0 to 3.1.0 (https://github.com/sigstore/cosign/pull/1526) * Bump mikefarah/yq from 4.20.2 to 4.21.1 (https://github.com/sigstore/cosign/pull/1525) * Bump github.com/secure-systems-lab/go-securesystemslib (https://github.com/sigstore/cosign/pull/1524) * chore(ci): add artifact hub support (https://github.com/sigstore/cosign/pull/1522) * optimize codeql speed by using caching and tracing (https://github.com/sigstore/cosign/pull/1519) * Bump golangci/golangci-lint-action from 2.5.2 to 3 (https://github.com/sigstore/cosign/pull/1516) * Bump github/codeql-action from 1.1.2 to 1.1.3 (https://github.com/sigstore/cosign/pull/1512) * Bump mikefarah/yq from 4.16.2 to 4.20.2 (https://github.com/sigstore/cosign/pull/1510) * Bump github.com/go-openapi/runtime from 0.23.0 to 0.23.1 (https://github.com/sigstore/cosign/pull/1507) * Bump go.uber.org/zap from 1.20.0 to 1.21.0 (https://github.com/sigstore/cosign/pull/1509) * Bump actions/setup-go from 2.1.5 to 2.2.0 (https://github.com/sigstore/cosign/pull/1495) * Bump google-github-actions/auth from 0.4.4 to 0.6.0 (https://github.com/sigstore/cosign/pull/1501) * Bump ossf/scorecard-action (https://github.com/sigstore/cosign/pull/1502) * Bump google.golang.org/api from 0.69.0 to 0.70.0 (https://github.com/sigstore/cosign/pull/1500) * Bump sigstore/cosign-installer from 1.4.1 to 2.0.1 (https://github.com/sigstore/cosign/pull/1496) * Bump actions/github-script from 4.1.1 to 6 (https://github.com/sigstore/cosign/pull/1497) * Update github/codeql-action requirement to d39d5d5c9707b926d517b1b292905ef4c03aa777 (https://github.com/sigstore/cosign/pull/1498) * Bump google-github-actions/setup-gcloud from 0.3.0 to 0.5.1 (https://github.com/sigstore/cosign/pull/1499) * chore(makefile): use kocache, convert publish to build (https://github.com/sigstore/cosign/pull/1488) * Bump cloud.google.com/go/storage from 1.20.0 to 1.21.0 (https://github.com/sigstore/cosign/pull/1481) * update changelog (https://github.com/sigstore/cosign/pull/1485) * fix lint (https://github.com/sigstore/cosign/pull/1484) * update go-tuf and simplify TUF client code (https://github.com/sigstore/cosign/pull/1455) * Bump sigstore/sigstore to pick up the kms change and the monkey-patch work. (https://github.com/sigstore/cosign/pull/1479) * refactor release cloudbuild job (https://github.com/sigstore/cosign/pull/1476) * increase timeout for goreleaser snapshot (https://github.com/sigstore/cosign/pull/1473) * Double goreleaser timeout (https://github.com/sigstore/cosign/pull/1472) * Bump google.golang.org/api from 0.68.0 to 0.69.0 (https://github.com/sigstore/cosign/pull/1469) * tests: `/bin/bash` -> `/usr/bin/env bash` (https://github.com/sigstore/cosign/pull/1470) * Bump the gitlab library and add a nil opt for the API change. (https://github.com/sigstore/cosign/pull/1466) * Bump webhook timeout. (https://github.com/sigstore/cosign/pull/1465) * update cross-build to use go 1.17.7 (https://github.com/sigstore/cosign/pull/1446) * Bump go-containerregistry, pick up new features (https://github.com/sigstore/cosign/pull/1442) * update cross-build image which adds goimports (https://github.com/sigstore/cosign/pull/1435) * Bump google.golang.org/api from 0.67.0 to 0.68.0 (https://github.com/sigstore/cosign/pull/1434) * Skip the ReadWrite test that flakes on Windows. (https://github.com/sigstore/cosign/pull/1415) * Bump github.com/go-openapi/strfmt from 0.21.1 to 0.21.2 (https://github.com/sigstore/cosign/pull/1411) * Bump github.com/go-openapi/runtime from 0.22.0 to 0.23.0 (https://github.com/sigstore/cosign/pull/1412) * Bump cloud.google.com/go/storage from 1.19.0 to 1.20.0 (https://github.com/sigstore/cosign/pull/1403) * Bump google.golang.org/api from 0.66.0 to 0.67.0 (https://github.com/sigstore/cosign/pull/1402) * Bump cuelang.org/go from 0.4.1 to 0.4.2 (https://github.com/sigstore/cosign/pull/1401) * update cosign and cross-build image for the release job (https://github.com/sigstore/cosign/pull/1400) * Bump github.com/xanzy/go-gitlab from 0.54.3 to 0.54.4 (https://github.com/sigstore/cosign/pull/1391) * Bump github.com/go-openapi/swag from 0.20.0 to 0.21.1 (https://github.com/sigstore/cosign/pull/1386) * Fix double `time` import in e2e tests (https://github.com/sigstore/cosign/pull/1388) * Bump github.com/go-openapi/swag from 0.19.15 to 0.20.0 (https://github.com/sigstore/cosign/pull/1383) * Bump github.com/go-openapi/runtime from 0.21.1 to 0.22.0 (https://github.com/sigstore/cosign/pull/1382) * add changelog for 1.5.1 release (https://github.com/sigstore/cosign/pull/1376) ## Contributors * Andrew Block (@sabre1041) * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Blake Burkhart (@bburky) * Bob Callaway (@bobcallaway) * Carlos Tadeu Panato Junior (@cpanato) * Christian Kotzbauer (@ckotzbauer) * Christopher Angelo Phillips (@spiffcs) * Dan Lorenc (@dlorenc) * Dan Luhring (@luhring) * Furkan Türkal (@Dentrax) * Hayden Blauzvern (@haydentherapper) * Jason Hall (@imjasonh) * Josh Dolitsky (@jdolitsky) * Kenny Leung (@k4leung4) * Matt Moore (@mattmoor) * Marco Franssen (@marcofranssen) * Nathan Smith (@nsmith5) * Priya Wadhwa (@priyawadhwa) * Sascha Grunert (@saschagrunert) * Scott Nichols (@n3wscott) * Teppei Fukuda (@knqyf263) * Ville Aikas (@vaikas) * Yongxuan Zhang (@Yongxuanzhang) * Zack Newman (@znewman01) # v1.5.2 ## Security Fixes * CVE-2022-23649 - Make sure signature in Rekor bundle matches signature being verified ## Others * refactor release cloudbuild job (https://github.com/sigstore/cosign/pull/1476) * increase timeout for goreleaser snapshot (https://github.com/sigstore/cosign/pull/1473) * Double goreleaser timeout (https://github.com/sigstore/cosign/pull/1472) * Bump webhook timeout. (https://github.com/sigstore/cosign/pull/1465) * convert release cosigned to also generate yaml artifact. (https://github.com/sigstore/cosign/pull/1453) * feat: add -buildid= to ldflags (https://github.com/sigstore/cosign/pull/1451) * update cross-build to use go 1.17.7 (https://github.com/sigstore/cosign/pull/1446) ## Contributors * Batuhan Apaydın (@developer-guy) * Carlos Tadeu Panato Junior (@cpanato) * Dan Lorenc (@dlorenc) * Kenny Leung (@k4leung4) * Matt Moore (@mattmoor) * Nathan Smith (@nsmith5) * Priya Wadhwa (@priyawadhwa) * Zack Newman (@znewman01) # v1.5.1 ## Bug Fixes * add check to make sure the go modules are in sync (https://github.com/sigstore/cosign/pull/1369) * Update verify-blob to support DSSEs (https://github.com/sigstore/cosign/pull/1355) ## Documention * docs: verify-attestation cue and rego policy doc (https://github.com/sigstore/cosign/pull/1362) * README: fix link to race conditions (https://github.com/sigstore/cosign/pull/1367) ## Others * Bump sigstore/sigstore to pick up oidc login for vault. (https://github.com/sigstore/cosign/pull/1377) * Bump google.golang.org/api from 0.65.0 to 0.66.0 (https://github.com/sigstore/cosign/pull/1371) * expose dafaults fulcio, rekor, oidc issuer urls (https://github.com/sigstore/cosign/pull/1368) * Bump cloud.google.com/go/storage from 1.18.2 to 1.19.0 (https://github.com/sigstore/cosign/pull/1365) * organize, update select deps (https://github.com/sigstore/cosign/pull/1358) * Bump go-containerregistry to pick up ACR keychain fix (https://github.com/sigstore/cosign/pull/1357) * Bump github.com/go-openapi/runtime from 0.21.0 to 0.21.1 (https://github.com/sigstore/cosign/pull/1352) * sync go modules (https://github.com/sigstore/cosign/pull/1353) ## Contributors * Batuhan Apaydın (@developer-guy) * Carlos Tadeu Panato Junior (@cpanato) * Dan Lorenc (@dlorenc) * Jake Sanders (@dekkagaijin) * Jason Hall (@imjasonh) * Mark Lodato (@MarkLodato) * Rémy Greinhofer (@rgreinho) # v1.5.0 ## Highlights * enable sbom generation when releasing (https://github.com/sigstore/cosign/pull/1261) * feat: log error to stderr (https://github.com/sigstore/cosign/pull/1260) * feat: support attach attestation (https://github.com/sigstore/cosign/pull/1253) * feat: resolve --cert from URL (https://github.com/sigstore/cosign/pull/1245) * feat: generate/upload sbom for cosign projects (https://github.com/sigstore/cosign/pull/1237) * feat: vuln attest support (https://github.com/sigstore/cosign/pull/1168) * feat: add ambient credential detection with spiffe/spire (https://github.com/sigstore/cosign/pull/1220) * feat: generate/upload sbom for cosign projects (https://github.com/sigstore/cosign/pull/1236) * feat: implement cosign download attestation (https://github.com/sigstore/cosign/pull/1216) ## Enhancements * Don't use k8schain, statically link cloud cred helpers in cosign (https://github.com/sigstore/cosign/pull/1279) * Export function to verify individual signature (https://github.com/sigstore/cosign/pull/1334) * Add suffix with digest to signature file output for recursive signing (https://github.com/sigstore/cosign/pull/1267) * Take OIDC client secret into account (https://github.com/sigstore/cosign/pull/1310) * Add --bundle flag to sign-blob and verify-blob (https://github.com/sigstore/cosign/pull/1306) * Add flag to verify OIDC issuer in certificate (https://github.com/sigstore/cosign/pull/1308) * add OSSF scorecard action (https://github.com/sigstore/cosign/pull/1318) * Add TUF timestamp to attestation bundle (https://github.com/sigstore/cosign/pull/1316) * Provide certificate flags to all verify commands (https://github.com/sigstore/cosign/pull/1305) * Bundle TUF timestamp with signature on signing (https://github.com/sigstore/cosign/pull/1294) * Add support for importing PKCShttps://github.com/sigstore/cosign/pull/8 private keys, and add validation (https://github.com/sigstore/cosign/pull/1300) * add error message (https://github.com/sigstore/cosign/pull/1296) * Move bundle out of `oci` and into `bundle` package (https://github.com/sigstore/cosign/pull/1295) * Reorganize verify-blob code and add a unit test (https://github.com/sigstore/cosign/pull/1286) * One-to-one mapping of invocation to scan result (https://github.com/sigstore/cosign/pull/1268) * refactor common utilities (https://github.com/sigstore/cosign/pull/1266) * Importing RSA and EC keypairs (https://github.com/sigstore/cosign/pull/1050) * Refactor the tuf client code. (https://github.com/sigstore/cosign/pull/1252) * Moved certificate output before checking for upload during signing (https://github.com/sigstore/cosign/pull/1255) * Remove remaining ioutil usage (https://github.com/sigstore/cosign/pull/1256) * Update the embedded TUF metadata. (https://github.com/sigstore/cosign/pull/1251) * Add support for other public key types for SCT verification, allow override for testing. (https://github.com/sigstore/cosign/pull/1241) * Log the proper remote repo for the signatures on verify (https://github.com/sigstore/cosign/pull/1243) * Do not require multiple Fulcio certs in the TUF root (https://github.com/sigstore/cosign/pull/1230) * clean up references to 'keyless' in `ephemeral.Signer` (https://github.com/sigstore/cosign/pull/1225) * create `DSSEAttestor` interface, `payload.DSSEAttestor` implementation (https://github.com/sigstore/cosign/pull/1221) * use `mutate.Signature` in the new `Signer`s (https://github.com/sigstore/cosign/pull/1213) * create `mutate` functions for `oci.Signature` (https://github.com/sigstore/cosign/pull/1199) * add a writeable `$HOME` for the `nonroot` cosigned user (https://github.com/sigstore/cosign/pull/1209) * signing attestation should private key (https://github.com/sigstore/cosign/pull/1200) * Remove the "upload" flag for "cosign initialize" (https://github.com/sigstore/cosign/pull/1201) * create KeylessSigner (https://github.com/sigstore/cosign/pull/1189) ## Bug Fixes * fix: cosign verify for vault (https://github.com/sigstore/cosign/pull/1328) * fix missing goimports (https://github.com/sigstore/cosign/pull/1327) * Fix TestSignBlobBundle (https://github.com/sigstore/cosign/pull/1320) * Fix a couple bugs in cert verification for blobs (https://github.com/sigstore/cosign/pull/1287) * Fix a few bugs in cosign initialize (https://github.com/sigstore/cosign/pull/1280) * Fix the unit tests with expired TUF metadata. (https://github.com/sigstore/cosign/pull/1270) * Fix output-file flag. (https://github.com/sigstore/cosign/pull/1264) * fix: typo in the error message (https://github.com/sigstore/cosign/pull/1250) * Fix semantic bugs in attestation verifification. (https://github.com/sigstore/cosign/pull/1249) * Fix semantic bug in DSSE specification. (https://github.com/sigstore/cosign/pull/1248) ## Others * Bump github.com/google/go-cmp from 0.5.6 to 0.5.7 (https://github.com/sigstore/cosign/pull/1343) * Bump recommended Go development version in README (https://github.com/sigstore/cosign/pull/1340) * Bump the snapshot and timestamp roles metadata from root signing. (https://github.com/sigstore/cosign/pull/1339) * Bump github.com/spiffe/go-spiffe/v2 from 2.0.0-beta.10 to 2.0.0-beta.11 (https://github.com/sigstore/cosign/pull/1336) * update go-github to v42 release (https://github.com/sigstore/cosign/pull/1335) * install latest release for ko instead of head of main branch (https://github.com/sigstore/cosign/pull/1333) * remove wrong settings in the gco auth for gh actions (https://github.com/sigstore/cosign/pull/1332) * update gcp setup for the GH action (https://github.com/sigstore/cosign/pull/1330) * update some dependencies (https://github.com/sigstore/cosign/pull/1326) * Verify checksum of downloaded utilities during CI (https://github.com/sigstore/cosign/pull/1322) * pin github actions by digest (https://github.com/sigstore/cosign/pull/1319) * Bump google.golang.org/api from 0.64.0 to 0.65.0 (https://github.com/sigstore/cosign/pull/1303) * Bump cuelang.org/go from 0.4.0 to 0.4.1 (https://github.com/sigstore/cosign/pull/1302) * Bump github.com/xanzy/go-gitlab from 0.54.2 to 0.54.3 (https://github.com/sigstore/cosign/pull/1292) * update import documentation (https://github.com/sigstore/cosign/pull/1290) * update release image to use go 1.17.6 (https://github.com/sigstore/cosign/pull/1284) * Bump google.golang.org/api. (https://github.com/sigstore/cosign/pull/1283) * Bump opa and go-gitlab. (https://github.com/sigstore/cosign/pull/1281) * Update SBOM spec to indicate compat for syft (https://github.com/sigstore/cosign/pull/1278) * Bump miekg/pkcs11 (https://github.com/sigstore/cosign/pull/1275) * Update signature spec with timestamp annotation (https://github.com/sigstore/cosign/pull/1274) * Pick up latest knative.dev/pkg, and k8s 0.22 libs (https://github.com/sigstore/cosign/pull/1269) * Bump sigstore/sigstore. (https://github.com/sigstore/cosign/pull/1247) * Spelling (https://github.com/sigstore/cosign/pull/1246) * Use ${{github.repository}} placeholder in OIDC GitHub workflow (https://github.com/sigstore/cosign/pull/1244) * update codeowners list with missing codeowners (https://github.com/sigstore/cosign/pull/1238) * update build images for release and bump cosign in the release job (https://github.com/sigstore/cosign/pull/1234) * update deps (https://github.com/sigstore/cosign/pull/1222) * nit: add comments to `Signer` interface (https://github.com/sigstore/cosign/pull/1228) * update google.golang.org/api from 0.62.0 to 0.63.0 (https://github.com/sigstore/cosign/pull/1214) * update snapshot and timestamp (https://github.com/sigstore/cosign/pull/1211) * Bump github.com/spf13/viper from 1.9.0 to 1.10.0 (https://github.com/sigstore/cosign/pull/1198) * Bump the DSSE library and handle manual changes in the API. (https://github.com/sigstore/cosign/pull/1191) * nit: drop every section title down a level (https://github.com/sigstore/cosign/pull/1188) ## Contributors * Andrew Block (@sabre1041) * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Bob Callaway (@bobcallaway) * Carlos Alexandro Becker (@caarlos0) * Carlos Tadeu Panato Junior (@cpanato) * Dan Lorenc (@dlorenc) * Hayden Blauzvern (@haydentherapper) * Hector Fernandez (@hectorj2f) * Itxaka (@Itxaka) * Ivan Wallis (@venafi-iw) * Jake Sanders (@dekkagaijin) * Jason Hall (@imjasonh) * Josh Dolitsky (@jdolitsky) * Josh Soref (@jsoref) * Matt Moore (@mattmoor) * Morten Linderud (@Foxboron) * Priya Wadhwa (@priyawadhwa) * Radoslav Gerganov (@rgerganov) * Rob Best (@ribbybibby) * Sambhav Kothari (@samj1912) * Ville Aikas (@vaikas) * Zack Newman (@znewman01) # v1.4.1 ## Highlights A whole buncha bugfixes! ## Enhancements * Files created with `--output-signature` and `--output-certificate` now created with 0600 permissions (https://github.com/sigstore/cosign/pull/1151) * Added `cosign verify-attestation --local-image` for verifying signed images with attestations from disk (https://github.com/sigstore/cosign/pull/1174) * Added the ability to fetch the TUF root over HTTP with `cosign initialize --mirror` (https://github.com/sigstore/cosign/pull/1185) ## Bug Fixes * Fixed saving and loading a signed image index to disk (https://github.com/sigstore/cosign/pull/1147) * Fixed `sign-blob --output-certificate` writing an empty file (https://github.com/sigstore/cosign/pull/1149) * Fixed assorted issues related to the initialization and use of Sigstore's TUF root of trust (https://github.com/sigstore/cosign/pull/1157) ## Contributors * Carlos Alexandro Becker (@caarlos0) * Carlos Panato (@cpanato) * Hayden Blauzvern (@haydentherapper) * Jake Sanders (@dekkagaijin) * Matt Moore (@mattmoor) * Priya Wadhwa (@priyawadhwa) * Radoslav Gerganov (@rgerganov) # v1.4.0 ## Highlights * BREAKING [COSIGN_EXPERIMENTAL]: This and future `cosign` releases will generate signatures that do not validate in older versions of `cosign`. This only applies to "keyless" experimental mode. To opt out of this behavior, use: `--fulcio-url=https://fulcio.sigstore.dev` when signing payloads (https://github.com/sigstore/cosign/pull/1127) * BREAKING [cosign/pkg]: `SignedEntryTimestamp` is now of type `[]byte`. To get the previous behavior, call `strfmt.Base64(SignedEntryTimestamp)` (https://github.com/sigstore/cosign/pull/1083) * `cosign-linux-pivkey-amd64` releases are now of the form `cosign-linux-pivkey-pkcs11key-amd64` (https://github.com/sigstore/cosign/pull/1052) * Releases are now additionally signed using the keyless workflow (https://github.com/sigstore/cosign/pull/1073, https://github.com/sigstore/cosign/pull/1111) ## Enhancements * Validate the whole attestation statement, not just the predicate (https://github.com/sigstore/cosign/pull/1035) * Added the options to replace attestations using `cosign attest --replace` (https://github.com/sigstore/cosign/pull/1039) * Added URI to `cosign verify-blob` output (https://github.com/sigstore/cosign/pull/1047) * Signatures and certificates created by `cosign sign` and `cosign sign-blob` can be output to file using the `--output-signature` and `--output-certificate` flags, respectively (https://github.com/sigstore/cosign/pull/1016, https://github.com/sigstore/cosign/pull/1093, https://github.com/sigstore/cosign/pull/1066, https://github.com/sigstore/cosign/pull/1095) * [cosign/pkg] Added the `pkg/oci/layout` package for storing signatures and attestations on disk (https://github.com/sigstore/cosign/pull/1040, https://github.com/sigstore/cosign/pull/1096) * [cosign/pkg] Added `mutate` methods to attach `oci.File`s to `oci.Signed*` objects (https://github.com/sigstore/cosign/pull/1084) * Added the `--signature-digest-algorithm` flag to `cosign verify`, allowing verification of container image signatures which were generated with a non-SHA256 signature algorithm (https://github.com/sigstore/cosign/pull/1071) * Builds should now be reproducible (https://github.com/sigstore/cosign/pull/1053) * Allows base64 files as `--cert` in `cosign verify-blob` (https://github.com/sigstore/cosign/pull/1088) * Kubernetes secrets generated for version >= 1.21 clusters have the immutable bit set (https://github.com/sigstore/cosign/pull/1091) * Added `cosign save` and `cosign load` commands to save and upload container images and associated signatures to disk (https://github.com/sigstore/cosign/pull/1094) * `cosign sign` will no longer fail to sign private images in keyless mode without `--force` (https://github.com/sigstore/cosign/pull/1116) * `cosign verify` now supports signatures stored in files and remote URLs with `--signature` (https://github.com/sigstore/cosign/pull/1068) * `cosign verify` now supports certs stored in files (https://github.com/sigstore/cosign/pull/1095) * Added support for `syft` format in `cosign attach sbom` (https://github.com/sigstore/cosign/pull/1137) ## Bug Fixes * Fixed verification of Rekor bundles for InToto attestations (https://github.com/sigstore/cosign/pull/1030) * Fixed a potential memory leak when signing and verifying with security keys (https://github.com/sigstore/cosign/pull/1113) ## Contributors * Ashley Davis (@SgtCoDFish) * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Brandon Philips (@philips) * Carlos Alexandro Becker (@caarlos0) * Carlos Panato (@cpanato) * Christian Rebischke (@shibumi) * Dan Lorenc (@dlorenc) * Erkan Zileli (@erkanzileli) * Furkan Türkal (@Dentrax) * garantir-km (@garantir-km) * Jake Sanders (@dekkagaijin) * jbpratt (@jbpratt) * Matt Moore (@mattmoor) * Mikey Strauss (@houdini91) * Naveen Srinivasan (@naveensrinivasan) * Priya Wadhwa (@priyawadhwa) * Sambhav Kothari (@samj1912) # v1.3.1 * BREAKING [cosign/pkg]: `cosign.Verify` has been removed in favor of explicit `cosign.VerifyImageSignatures` and `cosign.VerifyImageAttestations` (https://github.com/sigstore/cosign/pull/1026) ## Enhancements * Add ability for verify-blob to find signing cert in transparency log (https://github.com/sigstore/cosign/pull/991) * root policy: add optional issuer to maintainer keys (https://github.com/sigstore/cosign/pull/999) * PKCS11 signing support (https://github.com/sigstore/cosign/pull/985) * Included timeout option for uploading to Rekor (https://github.com/sigstore/cosign/pull/1001) ## Bug Fixes * Bump sigstore/sigstore to pickup a fix for azure kms (https://github.com/sigstore/cosign/pull/1011 / https://github.com/sigstore/cosign/pull/1028) ## Contributors * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Carlos Panato (@cpanato) * Dan Lorenc (@dlorenc) * Dennis Leon (@DennisDenuto) * Erkan Zileli (@erkanzileli) * Furkan Türkal (@Dentrax) * garantir-km (@garantir-km) * Jake Sanders (@dekkagaijin) * Naveen (@naveensrinivasan) # v1.3.0 * BREAKING: `verify-manifest` is now `manifest verify` (https://github.com/sigstore/cosign/pull/712) * BREAKING: `/pkg` has been heavily refactored. [Further refactoring work](https://github.com/sigstore/cosign/issues/844) will make its way into 1.4.0 * WARNING: The CLI now uses POSIX-style (double-dash `--flag`) for long-form flags. It will temporarily accept the single-dash `-flag` form with a warning, which will become an error in a future release (https://github.com/sigstore/cosign/pull/835) * Added `sget` as part of Cosign's releases (https://github.com/sigstore/cosign/pull/752) * The `copasetic` utility was unceremoniously [baleeted](https://www.youtube.com/watch?v=07h0ksKx5sM) (https://github.com/sigstore/cosign/pull/785) ## Enhancements * Began reworking `/pkg` around new abstractions for signing, verification, and storage (https://github.com/sigstore/cosign/issues/666) * Notice: refactoring of `/pkg` will continue in the next minor release (1.4.0). Please leave feedback, especially if you've been experimenting with `cosign` as a library and found it lacking (https://github.com/sigstore/cosign/issues/844) * [GGCR-style libraries](https://github.com/google/go-containerregistry#philosophy) for interacting with images now exist under `pkg/oci` (https://github.com/sigstore/cosign/pull/770) * `pkg/cosign/remote.UploadSignature` API was been removed in favor of new `pkg/oci/remote` APIs (https://github.com/sigstore/cosign/pull/774) * The function signature of `cosign.Verify` was changed so that callers must be explicit about which signatures (or attestations) to verify. For matching signatures, see also `cosign.Verify{Signatures,Attestations}` (https://github.com/sigstore/cosign/pull/782) * Removed `cremote.UploadFile` in favor of `static.NewFile` and `remote.Write` (https://github.com/sigstore/cosign/pull/797) * Innumerable other improvements to the codebase and automation ([Makin me look bad, @mattmoor](https://github.com/sigstore/cosign/commits?author=mattmoor)) * Migrated the CLI to `cobra` ([Welcome to the team, @n3wscott](https://github.com/sigstore/cosign/commits?author=n3wscott)) * Added the `--allow-insecure-registry` flag to disable TLS verification when interacting with insecure (e.g. self-signed) container registries (https://github.com/sigstore/cosign/pull/669) * 🔒 `cosigned` now includes a mutating webhook that resolves image tags to digests (https://github.com/sigstore/cosign/pull/800) * 🔒 The `cosigned` validating webhook now requires image digest references (https://github.com/sigstore/cosign/pull/799) * The `cosigned` webhook now ignores resources that are being deleted (https://github.com/sigstore/cosign/pull/803) * The `cosigned` webhook now supports resolving private images that are authenticated via `imagePullSecrets` (https://github.com/sigstore/cosign/pull/804) * `manifest verify` now supports verifying images in all Kubernetes objects that fit within `PodSpec`, `PodSpecTemplate`, or `JobSpecTemplate`, including CRDs (https://github.com/sigstore/cosign/pull/697) * Added shell auto-completion support (Clutch collab from @erkanzileli, @passcod, and @Dentrax! https://github.com/sigstore/cosign/pull/836) * `cosign` has generated Markdown docs available in the `doc/` directory (https://github.com/sigstore/cosign/pull/839) * Added support for verifying with secrets from a GitLab project (https://github.com/sigstore/cosign/pull/934) * Added a `--k8s-keychain` option that enables cosign to support ambient registry credentials based on the "k8schain" library (https://github.com/sigstore/cosign/pull/972) * CI (test) Images are now created for every architecture distroless ships on (currently: amd64, arm64, arm, s390x, ppc64le) (https://github.com/sigstore/cosign/pull/973) * `attest`: replaced `--upload` flag with a `--no-upload` flag (https://github.com/sigstore/cosign/pull/979) ## Bug Fixes * `cosigned` now verifies `CronJob` images (Terve, @vaikas https://github.com/sigstore/cosign/pull/809) * Fixed the `verify` `--cert-email` option to actually work (Sweet as, @passcod https://github.com/sigstore/cosign/pull/821) * `public-key -sk` no longer causes `error: x509: unsupported public key type: *crypto.PublicKey` (https://github.com/sigstore/cosign/pull/864) * Fixed interactive terminal support in Windows (https://github.com/sigstore/cosign/pull/871) * The `-ct` flag is no longer ignored in `upload blob` (https://github.com/sigstore/cosign/pull/910) ## Contributors * Aditya Sirish (@adityasaky) * Asra Ali (@asraa) * Axel Simon (@axelsimon) * Batuhan Apaydın (@developer-guy) * Brandon Mitchell (@sudo-bmitch) * Carlos Panato (@cpanato) * Chao Lin (@blackcat-lin) * Dan Lorenc (@dlorenc) * Dan Luhring (@luhring) * Eng Zer Jun (@Juneezee) * Erkan Zileli (@erkanzileli) * Félix Saparelli (@passcod) * Furkan Türkal (@Dentrax) * Hector Fernandez (@hectorj2f) * Ivan Font (@font) * Jake Sanders (@dekkagaijin) * Jason Hall (@imjasonh) * Jim Bugwadia (@JimBugwadia) * Joel Kamp (@mrjoelkamp) * Luke Hinds (@lukehinds) * Matt Moore (@mattmoor) * Naveen (@naveensrinivasan) * Olivier Gaumond (@oliviergaumond) * Priya Wadhwa (@priyawadhwa) * Radoslav Gerganov (@rgerganov) * Ramkumar Chinchani (@rchincha) * Rémy Greinhofer (@rgreinho) * Scott Nichols (@n3wscott) * Shubham Palriwala (@ShubhamPalriwala) * Viacheslav Vasilyev (@avoidik) * Ville Aikas (@vaikas) # v1.2.0 ## Enhancements * BREAKING: move `verify-dockerfile` to `dockerfile verify` (https://github.com/sigstore/cosign/pull/662) * Have the keyless `cosign sign` flow use a single 3LO. (https://github.com/sigstore/cosign/pull/665) * Allow to `verify-blob` from urls (https://github.com/sigstore/cosign/pull/646) * Support GCP environments without workload identity (GCB). (https://github.com/sigstore/cosign/pull/652) * Switch the release cosign container to debug. (https://github.com/sigstore/cosign/pull/649) * Add logic to detect and use ambient OIDC from exec envs. (https://github.com/sigstore/cosign/pull/644) * Add `-cert-email` flag to provide the email expected from a fulcio cert to be valid (https://github.com/sigstore/cosign/pull/622) * Add support for downloading signature from remote (https://github.com/sigstore/cosign/pull/629) * Add sbom and attestations to triangulate (https://github.com/sigstore/cosign/pull/628) * Add cosign attachment signing and verification (https://github.com/sigstore/cosign/pull/615) * Embed CT log public key (https://github.com/sigstore/cosign/pull/607) * Verify SCTs returned by fulcio (https://github.com/sigstore/cosign/pull/600) * Add extra replacement variables and GCP's role identifier (https://github.com/sigstore/cosign/pull/597) * Store attestations in the layer (payload) rather than the annotation. (https://github.com/sigstore/cosign/pull/579) * Improve documentation about predicate type and change predicate type from provenance to slsaprovenance (https://github.com/sigstore/cosign/pull/583) * Upgrade in-toto-golang to adapt SLSA Provenance (https://github.com/sigstore/cosign/pull/582) ## Bug Fixes * Fix verify-dockerfile to allow lowercase FROM (https://github.com/sigstore/cosign/pull/643) * Fix signing for the cosigned image. (https://github.com/sigstore/cosign/pull/634) * Make sure generate-key-pair doesn't overwrite existing key-pair (https://github.com/sigstore/cosign/pull/623) * helm/ci: update helm repo before installing the dependency (https://github.com/sigstore/cosign/pull/598) * Set the correct predicate type/URI for each supported predicate type. (https://github.com/sigstore/cosign/pull/592) * Warnings on admissionregistration version (https://github.com/sigstore/cosign/pull/581) * Remove unnecessary COSIGN_PASSWORD (https://github.com/sigstore/cosign/pull/572) ## Contributors * Batuhan Apaydın * Ben Walding * Carlos Alexandro Becker * Carlos Tadeu Panato Junior * Erkan Zileli * Hector Fernandez * Jake Sanders * Jason Hall * Matt Moore * Michael Lieberman * Naveen Srinivasan * Pradeep Chhetri * Sambhav Kothari * dlorenc * priyawadhwa # v1.1.0 ## Enhancements * BREAKING: The `-attestation` flag has been renamed to `-predicate` in `attest` (https://github.com/sigstore/cosign/pull/500) * Added `verify-manifest` command (https://github.com/sigstore/cosign/pull/490) * Added the ability to specify and validate well-known attestation types in `attest` with the `-type` flag (https://github.com/sigstore/cosign/pull/504) * Added `cosign init` command to setup the trusted local repository of SigStore's TUF root metadata (https://github.com/sigstore/cosign/pull/520) * Added timestamps to Cosign's custom In-Toto predicate (https://github.com/sigstore/cosign/pull/533) * `verify` now always verifies that the image exists (even when referenced by digest) before verification (https://github.com/sigstore/cosign/pull/543) ## Bug Fixes * `verify-dockerfile` no longer fails on `FROM scratch` (https://github.com/sigstore/cosign/pull/509) * Fixed reading from STDIN with `attach sbom` (https://github.com/sigstore/cosign/pull/517) * Fixed broken documentation and implementation of `-output` for `verify` and `verify-attestation` (https://github.com/sigstore/cosign/pull/546) * Fixed nil pointer error when calling `upload blob` without specifying `-f` (https://github.com/sigstore/cosign/pull/563) ## Contributors * Adolfo García Veytia (@puerco) * Anton Semjonov (@ansemjo) * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Carlos Panato (@cpanato) * Dan Lorenc (@dlorenc) * @gkovan * Hector Fernandez (@hectorj2f) * Jake Sanders (@dekkagaijin) * Jim Bugwadia (@JimBugwadia) * Jose Donizetti (@josedonizetti) * Joshua Hansen (@joshes) * Jason Hall (@imjasonh) * Priya Wadhwa (@priyawadhwa) * Russell Brown (@rjbrown57) * Stephan Renatus (@srenatus) * Li Yi (@denverdino) # v1.0.0 ## Enhancements * BREAKING: The default HSM key slot is now "signature" instead of "authentication" (https://github.com/sigstore/cosign/pull/450) * BREAKING: `--fulcio-server` is now `--fulcio-url` (https://github.com/sigstore/cosign/pull/471) * Added `-cert` flag to `sign` to allow the explicit addition of a signature certificate (https://github.com/sigstore/cosign/pull/451) * Added the `attest` command (https://github.com/sigstore/cosign/pull/458) * Added numerous flags for specifying parameters when interacting with Rekor and Fulcio (https://github.com/sigstore/cosign/pull/462) * `cosign` will now send its version string as part of the `user-agent` when interacting with a container registry (https://github.com/sigstore/cosign/pull/479) * Files containing certificates for custom Fulcio endpoints can now be specified via the `COSIGN_ROOT` environment variable (https://github.com/sigstore/cosign/pull/477) ## Bug Fixes * Fixed a situation where lower-case `as` would break `verify-dockerfile` (Complements to @Dentrax https://github.com/sigstore/cosign/pull/433) ## Contributors * Appu Goundan (@loosebazooka) * Batuhan Apaydın (@developer-guy) * Carlos Panato (@cpanato) * Dan Lorenc (@dlorenc) * Furkan Türkal (@Dentrax) * Hector Fernandez (@hectorj2f) * Jake Sanders (@dekkagaijin) * James Alseth (@jalseth) * Jason Hall (@imjasonh) * João Pereira (@joaodrp) * Luke Hinds (@lukehinds) * Tom Hennen (@TomHennen) # v0.6.0 ## Enhancements * BREAKING: Moved `cosign upload-blob` to `cosign upload blob` (https://github.com/sigstore/cosign/pull/378) * BREAKING: Moved `cosign upload` to `cosign attach signature` (https://github.com/sigstore/cosign/pull/378) * BREAKING: Moved `cosign download` to `cosign download signature` (https://github.com/sigstore/cosign/pull/392) * Added flags to specify slot, PIN, and touch policies for security keys (Thank you @ddz https://github.com/sigstore/cosign/pull/369) * Added `cosign verify-dockerfile` command (https://github.com/sigstore/cosign/pull/395) * Added SBOM support in `cosign attach` and `cosign download sbom` (https://github.com/sigstore/cosign/pull/387) * Sign & verify images using Kubernetes secrets (A muchas muchas gracias to @developer-guy and @Dentrax https://github.com/sigstore/cosign/pull/398) * Added support for AWS KMS (谢谢, @codysoyland https://github.com/sigstore/cosign/pull/426) * Numerous enhancements to our build & release process, courtesy @cpanato ## Bug Fixes * Verify entry timestamp signatures of fetched Tlog entries (https://github.com/sigstore/cosign/pull/371) ## Contributors * Asra Ali (@asraa) * Batuhan Apaydın (@developer-guy) * Carlos Panato (@cpanato) * Cody Soyland (@codysoyland) * Dan Lorenc (@dlorenc) * Dino A. Dai Zovi (@ddz) * Furkan Türkal (@Dentrax) * Jake Sanders (@dekkagaijin) * Jason Hall (@imjasonh) * Paris Zoumpouloglou (@zuBux) * Priya Wadhwa (@priyawadhwa) * Rémy Greinhofer (@rgreinho) * Russell Brown (@rjbrown57) # v0.5.0 ## Enhancements * Added `cosign copy` to easily move images and signatures between repositories (https://github.com/sigstore/cosign/pull/317) * Added `-r` flag to `cosign sign` for recursively signing multi-arch images (https://github.com/sigstore/cosign/pull/320) * Added `cosign clean` to delete signatures for an image (Thanks, @developer-guy! https://github.com/sigstore/cosign/pull/324) * Added `-k8s` flag to `cosign generate-key-pair` to create a Kubernetes secret (Hell yeah, @priyawadhwa! https://github.com/sigstore/cosign/pull/345) ## Bug Fixes * Fixed an issue with misdirected image signatures when `COSIGN_REPOSITORY` was used (https://github.com/sigstore/cosign/pull/323) ## Contributors * Balazs Zachar (@Cajga) * Batuhan Apaydın (@developer-guy) * Dan Lorenc (@dlorenc) * Furkan Turkal (@Dentrax) * Jake Sanders (@dekkagaijin) * Jon Johnson (@jonjohnsonjr) * Priya Wadhwa (@priyawadhwa) # v0.4.0 ## Action Required * Signatures created with `cosign` before v0.4.0 are not compatible with those created after * The signature image's manifest now uses OCI mediaTypes ([#300](https://github.com/sigstore/cosign/pull/300)) * The signature image's tag is now terminated with `.sig` (instead of `.cosign`, [#287](https://github.com/sigstore/cosign/pull/287)) ## Enhancements * 🎉 Added support for "offline" verification of Rekor signatures 🎉 (ありがとう, priyawadhwa! [#285](https://github.com/sigstore/cosign/pull/285)) * Support for Hashicorp vault as a KMS provider has been added (Danke, RichiCoder1! [sigstore/sigstore #44](https://github.com/sigstore/sigstore/pull/44), [sigstore/sigstore #49](https://github.com/sigstore/sigstore/pull/44)) ## Bug Fixes * GCP KMS URIs now include the key version ([#45](https://github.com/sigstore/sigstore/pull/45)) ## Contributors * Christian Pearce (@pearcec) * Dan Lorenc (@dlorenc) * Jake Sanders (@dekkagaijin) * Priya Wadhwa (@priyawadhwa) * Richard Simpson (@RichiCoder1) * Ross Timson (@rosstimson) # v0.3.1 ## Bug Fixes * Fixed CI container image breakage introduced in v0.3.0 * Fixed lack of version information in release binaries # v0.3.0 This is the third release of `cosign`! We still expect many flags, commands, and formats to change going forward, but we're getting closer. No backwards compatibility is promised or implied yet, though we are hoping to formalize this policy in the next release. See [#254](https://github.com/sigstore/cosign/issues/254) for more info. ## Enhancements * The `-output-file` flag supports writing output to a specific file * The `-key` flag now supports `kms` references and URLs, the `kms` specific flag has been removed * Yubikey/PIV hardware support is now included! * Support for signing and verifying multiple images in one invocation ## Bug Fixes * Bug fixes in KMS keypair generation * Bug fixes in key type parsing ## Contributors * Dan Lorenc * Priya Wadhwa * Ivan Font * Dependabot! * Mark Bestavros * Jake Sanders * Carlos Tadeu Panato Junior # v0.2.0 This is the second release of `cosign`! We still expect many flags, commands, and formats to change going forward, but we're getting closer. No backwards compatibility is promised or implied. ## Enhancements * The password for private keys can now be passed via the `COSIGN_PASSWORD` * KMS keys can now be used to sign and verify blobs * The `version` command can now be used to return the release version * The `public-key` command can now be used to extract the public key from KMS or a private key * The `COSIGN_REPOSITORY` environment variable can be used to store signatures in an alternate location * Tons of new EXAMPLES in our help text ## Bug Fixes * Improved error messages for command line flag verification * TONS more unit and integration testing * Too many others to count :) ## Contributors We would love to thank the contributors: * Dan Lorenc * Priya Wadhwa * Ahmet Alp Balkan * Naveen Srinivasan * Chris Norman * Jon Johnson * Kim Lewandowski * Luke Hinds * Bob Callaway * Dan POP * eminks * Mark Bestavros * Jake Sanders # v0.1.0 This is the first release of `cosign`! The main goal of this release is to release something we can start using to sign other releases of [sigstore](sigstore.dev) projects, including `cosign` itself. We expect many flags, commands, and formats to change going forward. No backwards compatibility is promised or implied. ## Enhancements This release added a feature to `cosign` called `cosign`. The `cosign` feature can be used to sign container images and blobs. Detailed documentation can be found in the [README](README.md) and the [Detailed Usage](USAGE.md). ## Bug Fixes There was no way to sign container images. Now there is! ## Contributors We would love to thank the contributors: * dlorenc * priyawadhwa * Ahmet Alp Balkan * Ivan Font * Jason Hall * Chris Norman * Jon Johnson * Kim Lewandowski * Luke Hinds * Bob Callaway cosign-2.5.0/CLI.md000066400000000000000000000035611477503325500137510ustar00rootroot00000000000000# Cosign CLI Conventions * The *primary* output of any command should be to STDOUT. The format should be described in the documentation of each command. * Output to STDERR is informational only. # Expected `cosign version` Behaviour From version 2.1.0 (as part of a [dependency update](https://github.com/sigstore/cosign/commit/40dbbd8b09bd5c30191d6e7e7ced3bbd7f6ea559)), the version metadata is [printed to standard output](https://github.com/kubernetes-sigs/release-utils/pull/76). By default it includes the package version, commit hash, git tree state, build date, Go version, compiler toolchain and current platform. ## ASCII Output The output of `cosign version` is expected to resemble this format, with the specific values being appropriate for each build of the `cosign` package. ``` $ cosign version ______ ______ _______. __ _______ .__ __. / | / __ \ / || | / _____|| \ | | | ,----'| | | | | (----`| | | | __ | \| | | | | | | | \ \ | | | | |_ | | . ` | | `----.| `--' | .----) | | | | |__| | | |\ | \______| \______/ |_______/ |__| \______| |__| \__| cosign: A tool for Container Signing, Verification and Storage in an OCI registry. GitVersion: [vX.Y.Z or devel] GitCommit: [hash or unknown] GitTreeState: [clean or dirty] BuildDate: [yyyy-MM-ddThh:mm:ss or unknown] GoVersion: go1.A.B Compiler: gc Platform: os/arch ``` ## JSON Output The output of `cosign version --json` is expected to resemble this format, with the specific values being appropriate for each build of the `cosign` package. ``` $ cosign version --json { "gitVersion": "[vX.Y.Z or devel]", "gitCommit": "[hash or unknown]", "gitTreeState": "[clean or dirty]", "buildDate": "[yyyy-MM-ddThh:mm:ss or unknown]", "goVersion": "go1.A.B", "compiler": "gc", "platform": "os/arch" } ``` cosign-2.5.0/CODEOWNERS000066400000000000000000000002511477503325500144040ustar00rootroot00000000000000* @sigstore/cosign-codeowners /.github/ @sigstore/dep-maintainers /release/ @sigstore/dep-maintainers go.mod @sigstore/dep-maintainers go.sum @sigstore/dep-maintainers cosign-2.5.0/CODE_OF_CONDUCT.md000066400000000000000000000062071477503325500156170ustar00rootroot00000000000000# Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ cosign-2.5.0/CONTRIBUTING.md000066400000000000000000000051421477503325500152460ustar00rootroot00000000000000# Contributing to Cosign Thank you for considering contributing to Cosign! We welcome any contributions, whether it's bug fixes, new features, or improvements to the existing codebase. ## Your First Pull Request Review [Sigstore's contribution guidelines](https://github.com/sigstore/community/blob/main/CONTRIBUTING.md) which includes some high level information on how to contribute to Sigstore projects. To help you get familiar with our contribution process, we have a list of [good first issues](https://github.com/sigstore/cosign/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) which are relatively limited in scope. Before working on an issue, please: 1. Check the comments to see if someone is already working on it. 1. If it's unassigned, comment that you're working on it to avoid duplication. ## Contribution Prerequisites Before running Cosign, ensure that you have [Go](https://go.dev/doc/install) installed. ## Sending a Pull Request You can find a step by step guide to submit your contribution on [Sigstore's contribution guidelines](https://github.com/sigstore/community/blob/main/CONTRIBUTING.md#pull-request-process). The following steps describe the build, unit testing, linting, and documentation processes: ### Building Cosign To build cosign locally, run this command: ```shell make cosign ``` ### Running Unit Tests To run the unit tests, execute the following command: ```shell make test ``` **Make sure all tests pass** without any failures or errors. #### Conformance tests To run the conformance tests, first install the conformance test runner: ```shell make conformance-runner ``` Then build `cosign` and `conformance` (the executable that adapts conformance tests to the cosign command), and run the tests: ```shell make cosign conformance make conformance-tests ``` ### Running Lint To run the linting checks, use the following command: ```shell make lint ``` Address any linting warnings or errors before submitting your PR. ### Document Generation If your changes require updates to project documentation, run the following: ```shell make docgen ``` Ensure that the documentation is up-to-date and reflects your changes accurately. ### Sign DCO Make sure to sign the [Developer Certificate of Origin](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff). ## Additional Documentation In addition to the README file, documentation for Cosign exists in the repository's doc folder and consists of one markdown file for each command. If you add, delete or modify a Cosign command you must also add, delete, or edit the appropriate file in the doc folder. cosign-2.5.0/COPYRIGHT.txt000066400000000000000000000010631477503325500151240ustar00rootroot00000000000000 Copyright 2021 The Sigstore Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. cosign-2.5.0/LICENSE000066400000000000000000000261351477503325500140270ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. cosign-2.5.0/Makefile000066400000000000000000000166271477503325500144670ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ifeq (,$(shell echo $$DEBUG)) else SHELL = bash -x endif # allow overwriting the default `go` value with the custom path to the go executable GOEXE ?= go # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell $(GOEXE) env GOBIN)) GOBIN=$(shell $(GOEXE) env GOPATH)/bin else GOBIN=$(shell $(GOEXE) env GOBIN) endif # Set version variables for LDFLAGS PROJECT_ID ?= projectsigstore RUNTIME_IMAGE ?= gcr.io/distroless/static GIT_TAG ?= dirty-tag GIT_VERSION ?= $(shell git describe --tags --always --dirty) GIT_HASH ?= $(shell git rev-parse HEAD) DATE_FMT = +%Y-%m-%dT%H:%M:%SZ SOURCE_DATE_EPOCH ?= $(shell git log -1 --no-show-signature --pretty=%ct) ifdef SOURCE_DATE_EPOCH BUILD_DATE ?= $(shell date -u -d "@$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u "$(DATE_FMT)") else BUILD_DATE ?= $(shell date "$(DATE_FMT)") endif GIT_TREESTATE = "clean" DIFF = $(shell git diff --quiet >/dev/null 2>&1; if [ $$? -eq 1 ]; then echo "1"; fi) ifeq ($(DIFF), 1) GIT_TREESTATE = "dirty" endif PLATFORMS=darwin linux windows ARCHITECTURES=amd64 COSIGNED_ARCHS?=all LDFLAGS=-buildid= -X sigs.k8s.io/release-utils/version.gitVersion=$(GIT_VERSION) \ -X sigs.k8s.io/release-utils/version.gitCommit=$(GIT_HASH) \ -X sigs.k8s.io/release-utils/version.gitTreeState=$(GIT_TREESTATE) \ -X sigs.k8s.io/release-utils/version.buildDate=$(BUILD_DATE) SRCS = $(shell find cmd -iname "*.go") $(shell find pkg -iname "*.go") GOLANGCI_LINT_DIR = $(shell pwd)/bin GOLANGCI_LINT_BIN = $(GOLANGCI_LINT_DIR)/golangci-lint KO_PREFIX ?= gcr.io/projectsigstore export KO_DOCKER_REPO=$(KO_PREFIX) GHCR_PREFIX ?= ghcr.io/sigstore/cosign LATEST_TAG ?= .PHONY: all lint test clean cosign conformance cross all: cosign log-%: @grep -h -E '^$*:.*?## .*$$' $(MAKEFILE_LIST) | \ awk \ 'BEGIN { \ FS = ":.*?## " \ }; \ { \ printf "\033[36m==> %s\033[0m\n", $$2 \ }' cosign: $(SRCS) CGO_ENABLED=0 $(GOEXE) build -trimpath -ldflags "$(LDFLAGS)" -o $@ ./cmd/cosign cosign-pivkey-pkcs11key: $(SRCS) CGO_ENABLED=1 $(GOEXE) build -trimpath -tags=pivkey,pkcs11key -ldflags "$(LDFLAGS)" -o cosign ./cmd/cosign install: $(SRCS) CGO_ENABLED=1 $(GOEXE) install -trimpath -ldflags "$(LDFLAGS)" ./cmd/cosign install-pivkey-pkcs11key: $(SRCS) CGO_ENABLED=1 $(GOEXE) install -trimpath -tags=pivkey,pkcs11key -ldflags "$(LDFLAGS)" ./cmd/cosign .PHONY: cross cross: $(foreach GOOS, $(PLATFORMS),\ $(foreach GOARCH, $(ARCHITECTURES), $(shell export GOOS=$(GOOS); export GOARCH=$(GOARCH); \ $(GOEXE) build -trimpath -ldflags "$(LDFLAGS)" -o cosign-$(GOOS)-$(GOARCH) ./cmd/cosign; \ shasum -a 256 cosign-$(GOOS)-$(GOARCH) > cosign-$(GOOS)-$(GOARCH).sha256 ))) \ ##################### # lint / test section ##################### golangci-lint: rm -f $(GOLANGCI_LINT_BIN) || : set -e ;\ GOBIN=$(GOLANGCI_LINT_DIR) $(GOEXE) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 ;\ lint: golangci-lint ## Run golangci-lint linter $(GOLANGCI_LINT_BIN) run -n test: $(GOEXE) test $(shell $(GOEXE) list ./... | grep -v third_party/) clean: rm -rf cosign rm -rf dist/ KOCACHE_PATH=/tmp/ko ARTIFACT_HUB_LABELS=--image-label io.artifacthub.package.readme-url="https://raw.githubusercontent.com/sigstore/cosign/main/README.md" \ --image-label io.artifacthub.package.logo-url=https://raw.githubusercontent.com/sigstore/cosign/main/images/logo.svg \ --image-label io.artifacthub.package.license=Apache-2.0 --image-label io.artifacthub.package.vendor=sigstore \ --image-label io.artifacthub.package.version=0.1.0 \ --image-label io.artifacthub.package.name=cosign \ --image-label org.opencontainers.image.created=$(BUILD_DATE) \ --image-label org.opencontainers.image.description="Container signing verification and storage in an OCI registry" \ --image-label io.artifacthub.package.alternative-locations="oci://ghcr.io/sigstore/cosign/cosign" define create_kocache_path mkdir -p $(KOCACHE_PATH) endef ################### # conformance tests ################### conformance: $(GOEXE) build -trimpath -ldflags "$(LDFLAGS)" -o $@ ./cmd/conformance CONFORMANCE_RUNNER_PATH = sigstore-conformance $(CONFORMANCE_RUNNER_PATH): git clone https://github.com/sigstore/sigstore-conformance $@ .PHONY: conformance-runner conformance-runner: $(MAKE) $(CONFORMANCE_RUNNER_PATH) conformance-runner-pull conformance-runner-build .PHONY: conformance-runner-pull conformance-runner-pull: cd $(CONFORMANCE_RUNNER_PATH) && git pull .PHONY: conformance-runner-build conformance-runner-build: cd $(CONFORMANCE_RUNNER_PATH) && $(MAKE) dev CONFORMANCE_BIN = $(shell pwd)/conformance .PHONY: conformance-tests conformance-tests: cd $(CONFORMANCE_RUNNER_PATH) && env/bin/pytest test --entrypoint=$(CONFORMANCE_BIN) ########## # ko build ########## .PHONY: ko ko: ko-cosign ko-cosign-dev .PHONY: ko-cosign ko-cosign: $(create_kocache_path) LDFLAGS="$(LDFLAGS)" GIT_HASH=$(GIT_HASH) GIT_VERSION=$(GIT_VERSION) \ KOCACHE=$(KOCACHE_PATH) ko build --base-import-paths \ --platform=all --tags $(GIT_VERSION) --tags $(GIT_HASH)$(LATEST_TAG) \ $(ARTIFACT_HUB_LABELS) --image-refs cosignImagerefs \ github.com/sigstore/cosign/v2/cmd/cosign .PHONY: ko-cosign-dev ko-cosign-dev: $(create_kocache_path) LDFLAGS="$(LDFLAGS)" GIT_HASH=$(GIT_HASH) GIT_VERSION=$(GIT_VERSION) \ KOCACHE=$(KOCACHE_PATH) KO_DEFAULTBASEIMAGE=gcr.io/distroless/static-debian12:debug-nonroot ko build --base-import-paths \ --platform=all --tags $(GIT_VERSION)-dev --tags $(GIT_HASH)-dev$(LATEST_TAG)-dev \ $(ARTIFACT_HUB_LABELS) --image-refs cosignDevImagerefs \ github.com/sigstore/cosign/v2/cmd/cosign .PHONY: ko-local ko-local: $(create_kocache_path) KO_DOCKER_REPO=ko.local LDFLAGS="$(LDFLAGS)" GIT_HASH=$(GIT_HASH) GIT_VERSION=$(GIT_VERSION) \ KOCACHE=$(KOCACHE_PATH) ko build --base-import-paths \ --tags $(GIT_VERSION) --tags $(GIT_HASH) \ $(ARTIFACT_HUB_LABELS) \ github.com/sigstore/cosign/v2/cmd/cosign .PHONY: ko-local-dev ko-local-dev: $(create_kocache_path) KO_DOCKER_REPO=ko.local/cosign-dev LDFLAGS="$(LDFLAGS)" GIT_HASH=$(GIT_HASH) GIT_VERSION=$(GIT_VERSION) \ KOCACHE=$(KOCACHE_PATH) KO_DEFAULTBASEIMAGE=gcr.io/distroless/static-debian12:debug-nonroot ko build --base-import-paths \ --tags $(GIT_VERSION) --tags $(GIT_HASH) \ $(ARTIFACT_HUB_LABELS) \ github.com/sigstore/cosign/v2/cmd/cosign ################## # help ################## help: # Display help @awk -F ':|##' \ '/^[^\t].+?:.*?##/ {\ printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF \ }' $(MAKEFILE_LIST) | sort include release/release.mk include test/ci.mk ########################## # Documentation generation ########################## .PHONY: docgen docgen: $(GOEXE) run -tags pivkey,pkcs11key,cgo ./cmd/help/ cosign-2.5.0/README.md000066400000000000000000000776301477503325500143070ustar00rootroot00000000000000

Cosign logo

# cosign Signing OCI containers (and other artifacts) using [Sigstore](https://sigstore.dev/)! [![Go Report Card](https://goreportcard.com/badge/github.com/sigstore/cosign)](https://goreportcard.com/report/github.com/sigstore/cosign) [![e2e-tests](https://github.com/sigstore/cosign/actions/workflows/e2e-tests.yml/badge.svg)](https://github.com/sigstore/cosign/actions/workflows/e2e-tests.yml) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5715/badge)](https://bestpractices.coreinfrastructure.org/projects/5715) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/sigstore/cosign/badge)](https://securityscorecards.dev/viewer/?uri=github.com/sigstore/cosign) Cosign aims to make signatures **invisible infrastructure**. Cosign supports: * "Keyless signing" with the Sigstore public good Fulcio certificate authority and Rekor transparency log (default) * Hardware and KMS signing * Signing with a cosign generated encrypted private/public keypair * Container Signing, Verification and Storage in an OCI registry. * Bring-your-own PKI ## Info `Cosign` is developed as part of the [`sigstore`](https://sigstore.dev) project. We also use a [slack channel](https://sigstore.slack.com)! Click [here](https://join.slack.com/t/sigstore/shared_invite/zt-mhs55zh0-XmY3bcfWn4XEyMqUUutbUQ) for the invite link. ## Installation For Homebrew, Arch, Nix, GitHub Action, and Kubernetes installs see the [installation docs](https://docs.sigstore.dev/cosign/system_config/installation/). For Linux and macOS binaries see the [GitHub release assets](https://github.com/sigstore/cosign/releases/latest). :rotating_light: If you are downloading releases of cosign from our GCS bucket - please see more information on the July 31, 2023 [deprecation notice](https://blog.sigstore.dev/cosign-releases-bucket-deprecation/) :rotating_light: ## Developer Installation If you have Go 1.22+, you can setup a development environment: ```shell $ git clone https://github.com/sigstore/cosign $ cd cosign $ go install ./cmd/cosign $ $(go env GOPATH)/bin/cosign ``` ## Contributing If you are interested in contributing to `cosign`, please read the [contributing documentation](./CONTRIBUTING.md). Future Cosign development will be focused the next major release which will be based on [sigstore-go](https://github.com/sigstore/sigstore-go). Maintainers will be focused on feature development within sigstore-go. Contributions to sigstore-go, particularly around bring-your-own keys and signing, are appreciated. Please see the [issue tracker](https://github.com/sigstore/sigstore-go/issues) for good first issues. Cosign 2.x is a stable release and will continue to receive periodic feature updates and bug fixes. PRs that are small in scope and size are most likely to be quickly reviewed. PRs which significantly modify or break the API will not be accepted. PRs which are significant in size but do not introduce breaking changes may be accepted, but will be considered lower priority than PRs in sigstore-go. ## Dockerfile Here is how to install and use cosign inside a Dockerfile through the ghcr.io/sigstore/cosign/cosign image: ```shell FROM ghcr.io/sigstore/cosign/cosign:v2.4.1 as cosign-bin # Source: https://github.com/chainguard-images/static FROM cgr.dev/chainguard/static:latest COPY --from=cosign-bin /ko-app/cosign /usr/local/bin/cosign ENTRYPOINT [ "cosign" ] ``` ## Quick Start This shows how to: * sign a container image with the default identity-based "keyless signing" method (see [the documentation for more information](https://docs.sigstore.dev/cosign/signing/overview/)) * verify the container image ### Sign a container and store the signature in the registry Note that you should always sign images based on their digest (`@sha256:...`) rather than a tag (`:latest`) because otherwise you might sign something you didn't intend to! ```shell cosign sign $IMAGE Generating ephemeral keys... Retrieving signed certificate... Note that there may be personally identifiable information associated with this signed artifact. This may include the email address associated with the account with which you authenticate. This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later. By typing 'y', you attest that you grant (or have permission to grant) and agree to have this information stored permanently in transparency logs. Are you sure you would like to continue? [y/N] y Your browser will now be opened to: https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=OrXitVKUZm2lEWHVt1oQWR4HZvn0rSlKhLcltglYxCY&code_challenge_method=S256&nonce=2KvOWeTFxYfxyzHtssvlIXmY6Jk&redirect_uri=http%3A%2F%2Flocalhost%3A57102%2Fauth%2Fcallback&response_type=code&scope=openid+email&state=2KvOWfbQJ1caqScgjwibzK2qJmb Successfully verified SCT... tlog entry created with index: 12086900 Pushing signature to: $IMAGE ``` Cosign will prompt you to authenticate via OIDC, where you'll sign in with your email address. Under the hood, cosign will request a code signing certificate from the Fulcio certificate authority. The subject of the certificate will match the email address you logged in with. Cosign will then store the signature and certificate in the Rekor transparency log, and upload the signature to the OCI registry alongside the image you're signing. ### Verify a container To verify the image, you'll need to pass in the expected certificate subject and certificate issuer via the `--certificate-identity` and `--certificate-oidc-issuer` flags: ``` cosign verify $IMAGE --certificate-identity=$IDENTITY --certificate-oidc-issuer=$OIDC_ISSUER ``` You can also pass in a regex for the certificate identity and issuer flags, `--certificate-identity-regexp` and `--certificate-oidc-issuer-regexp`. ### Verify a container against a public key This command returns `0` if *at least one* `cosign` formatted signature for the image is found matching the public key. See the detailed usage below for information and caveats on other signature formats. Any valid payloads are printed to stdout, in json format. Note that these signed payloads include the digest of the container image, which is how we can be sure these "detached" signatures cover the correct image. ```shell $ cosign verify --key cosign.pub $IMAGE_URI:1h The following checks were performed on these signatures: - The cosign claims were validated - The signatures were verified against the specified public key {"Critical":{"Identity":{"docker-reference":""},"Image":{"Docker-manifest-digest":"sha256:87ef60f558bad79beea6425a3b28989f01dd417164150ab3baab98dcbf04def8"},"Type":"cosign container image signature"},"Optional":null} ``` ### Verify a container in an air-gapped environment Cosign can do completely offline verification by verifying a [bundle](./specs/SIGNATURE_SPEC.md#properties) which is typically distributed as an annotation on the image manifest. As long as this annotation is present, then offline verification can be done. This bundle annotation is always included by default for keyless signing, so the default `cosign sign` functionality will include all materials needed for offline verification. To verify an image in an air-gapped environment, the image and signatures must be available locally on the filesystem. An image can be saved locally using `cosign save` (note, this step must be done with a network connection): ``` cosign initialize # This will pull in the latest TUF root cosign save $IMAGE_NAME --dir ./path/to/dir ``` Now, in an air-gapped environment, this local image can be verified: ``` cosign verify --certificate-identity $CERT_IDENTITY --certificate-oidc-issuer $CERT_OIDC_ISSUER --offline --local-image ./path/to/dir ``` You'll need to pass in expected values for `$CERT_IDENTITY` and `$CERT_OIDC_ISSUER` to correctly verify this image. If you signed with a keypair, the same command will work, assuming the public key material is present locally: ``` cosign verify --key cosign.pub --offline --local-image ./path/to/dir ``` ### What ** is not ** production ready? While parts of `cosign` are stable, we are continuing to experiment and add new features. The following feature set is not considered stable yet, but we are committed to stabilizing it over time! #### Formats/Specifications While the `cosign` code for uploading, signing, retrieving, and verifying several artifact types is stable, the format specifications for some of those types may not be considered stable yet. Some of these are developed outside of the `cosign` project, so we are waiting for them to stabilize first. These include: * The SBOM specification for storing SBOMs in a container registry * The In-Toto attestation format ## Working with Other Artifacts OCI registries are useful for storing more than just container images! `Cosign` also includes some utilities for publishing generic artifacts, including binaries, scripts, and configuration files using the OCI protocol. This section shows how to leverage these for an easy-to-use, backwards-compatible artifact distribution system that integrates well with the rest of Sigstore. See [the documentation](https://docs.sigstore.dev/cosign/signing/other_types/) for more information. ### Blobs You can publish an artifact with `cosign upload blob`: ```shell $ echo "my first artifact" > artifact $ BLOB_SUM=$(shasum -a 256 artifact | cut -d' ' -f 1) && echo "$BLOB_SUM" c69d72c98b55258f9026f984e4656f0e9fd3ef024ea3fac1d7e5c7e6249f1626 $ BLOB_NAME=my-artifact-$(uuidgen | head -c 8 | tr 'A-Z' 'a-z') $ BLOB_URI=ttl.sh/$BLOB_NAME:1h $ BLOB_URI_DIGEST=$(cosign upload blob -f artifact $BLOB_URI) && echo "$BLOB_URI_DIGEST" Uploading file from [artifact] to [ttl.sh/my-artifact-f42c22e0:5m] with media type [text/plain] File [artifact] is available directly at [ttl.sh/v2/my-artifact-f42c22e0/blobs/sha256:c69d72c98b55258f9026f984e4656f0e9fd3ef024ea3fac1d7e5c7e6249f1626] Uploaded image to: ttl.sh/my-artifact-f42c22e0@sha256:790d47850411e902aabebc3a684eeb78fcae853d4dd6e1cc554d70db7f05f99f ``` Your users can download it from the "direct" url with standard tools like curl or wget: ```shell $ curl -L ttl.sh/v2/$BLOB_NAME/blobs/sha256:$BLOB_SUM > artifact-fetched ``` The digest is baked right into the URL, so they can check that as well: ```shell $ cat artifact-fetched | shasum -a 256 c69d72c98b55258f9026f984e4656f0e9fd3ef024ea3fac1d7e5c7e6249f1626 - ``` You can sign it with the normal `cosign sign` command and flags: ```shell $ cosign sign --key cosign.key $BLOB_URI_DIGEST Enter password for private key: Pushing signature to: ttl.sh/my-artifact-f42c22e0 ``` As usual, make sure to reference any images you sign by their digest to make sure you don't sign the wrong thing! #### Tekton Bundles [Tekton](https://tekton.dev) bundles can be uploaded and managed within an OCI registry. The specification is [here](https://tekton.dev/docs/pipelines/tekton-bundle-contracts/). This means they can also be signed and verified with `cosign`. Tekton Bundles can currently be uploaded with the [tkn cli](https://github.com/tektoncd/cli), but we may add this support to `cosign` in the future. ```shell $ tkn bundle push us.gcr.io/dlorenc-vmtest2/pipeline:latest -f task-output-image.yaml Creating Tekton Bundle: - Added TaskRun: to image Pushed Tekton Bundle to us.gcr.io/dlorenc-vmtest2/pipeline@sha256:124e1fdee94fe5c5f902bc94da2d6e2fea243934c74e76c2368acdc8d3ac7155 $ cosign sign --key cosign.key us.gcr.io/dlorenc-vmtest2/pipeline@sha256:124e1fdee94fe5c5f902bc94da2d6e2fea243934c74e76c2368acdc8d3ac7155 Enter password for private key: tlog entry created with index: 5086 Pushing signature to: us.gcr.io/dlorenc-vmtest2/demo:sha256-124e1fdee94fe5c5f902bc94da2d6e2fea243934c74e76c2368acdc8d3ac7155.sig ``` #### WASM Web Assembly Modules can also be stored in an OCI registry, using this [specification](https://github.com/solo-io/wasm/tree/master/spec). Cosign can upload these using the `cosign wasm upload` command: ```shell $ cosign upload wasm -f hello.wasm us.gcr.io/dlorenc-vmtest2/wasm $ cosign sign --key cosign.key us.gcr.io/dlorenc-vmtest2/wasm@sha256:9e7a511fb3130ee4641baf1adc0400bed674d4afc3f1b81bb581c3c8f613f812 Enter password for private key: tlog entry created with index: 5198 Pushing signature to: us.gcr.io/dlorenc-vmtest2/wasm:sha256-9e7a511fb3130ee4641baf1adc0400bed674d4afc3f1b81bb581c3c8f613f812.sig ``` #### eBPF [eBPF](https://ebpf.io) modules can also be stored in an OCI registry, using this [specification](https://github.com/solo-io/bumblebee/tree/main/spec). The image below was built using the `bee` tool. More information can be found [here](https://github.com/solo-io/bumblebee/) Cosign can then sign these images as they can any other OCI image. ```shell $ bee build ./examples/tcpconnect/tcpconnect.c localhost:5000/tcpconnect:test $ bee push localhost:5000/tcpconnect:test $ cosign sign --key cosign.key localhost:5000/tcpconnect@sha256:7a91c50d922925f152fec96ed1d84b7bc6b2079c169d68826f6cf307f22d40e6 Enter password for private key: Pushing signature to: localhost:5000/tcpconnect $ cosign verify --key cosign.pub localhost:5000/tcpconnect:test Verification for localhost:5000/tcpconnect:test -- The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key [{"critical":{"identity":{"docker-reference":"localhost:5000/tcpconnect"},"image":{"docker-manifest-digest":"sha256:7a91c50d922925f152fec96ed1d84b7bc6b2079c169d68826f6cf307f22d40e6"},"type":"cosign container image signature"},"optional":null}] ``` #### In-Toto Attestations Cosign also has built-in support for [in-toto](https://in-toto.io) attestations. The specification for these is defined [here](https://github.com/in-toto/attestation). You can create and sign one from a local predicate file using the following commands: ```shell $ cosign attest --predicate --key cosign.key $IMAGE_URI_DIGEST ``` All of the standard key management systems are supported. Payloads are signed using the DSSE signing spec, defined [here](https://github.com/secure-systems-lab/dsse). To verify: ```shell $ cosign verify-attestation --key cosign.pub $IMAGE_URI ``` ## Detailed Usage See the [Usage documentation](https://docs.sigstore.dev/cosign/signing/overview/) for more information. ## Hardware-based Tokens See the [Hardware Tokens documentation](https://docs.sigstore.dev/cosign/key_management/hardware-based-tokens/) for information on how to use `cosign` with hardware. ## Registry Support `cosign` uses [go-containerregistry](https://github.com/google/go-containerregistry) for registry interactions, which has generally excellent compatibility, but some registries may have quirks. Today, `cosign` has been tested and works against the following registries: * AWS Elastic Container Registry * GCP's Artifact Registry and Container Registry * Docker Hub * Azure Container Registry * JFrog Artifactory Container Registry * The CNCF distribution/distribution Registry * GitLab Container Registry * GitHub Container Registry * The CNCF Harbor Registry * Digital Ocean Container Registry * Sonatype Nexus Container Registry * Alibaba Cloud Container Registry * Red Hat Quay Container Registry 3.6+ / Red Hat quay.io * Elastic Container Registry * IBM Cloud Container Registry * Cloudsmith Container Registry * The CNCF zot Registry * OVHcloud Managed Private Registry We aim for wide registry support. To `sign` images in registries which do not yet fully support [OCI media types](https://github.com/sigstore/cosign/blob/main/specs/SIGNATURE_SPEC.md), one may need to use `COSIGN_DOCKER_MEDIA_TYPES` to fall back to legacy equivalents. For example: ```shell COSIGN_DOCKER_MEDIA_TYPES=1 cosign sign --key cosign.key legacy-registry.example.com/my/image@$DIGEST ``` Please help test and file bugs if you see issues! Instructions can be found in the [tracking issue](https://github.com/sigstore/cosign/issues/40). ## Caveats ### Intentionally Missing Features `cosign` only generates ECDSA-P256 keys and uses SHA256 hashes, for both ephemeral keyless signing and managed key signing. Keys are stored in PEM-encoded PKCS8 format. However, you can use `cosign` to store and retrieve signatures in any format, from any algorithm. ### Things That Should Probably Change #### Payload Formats `cosign` only supports Red Hat's [simple signing](https://www.redhat.com/en/blog/container-image-signing) format for payloads. That looks like: ```json { "critical": { "identity": { "docker-reference": "testing/manifest" }, "image": { "Docker-manifest-digest": "sha256:20be...fe55" }, "type": "cosign container image signature" }, "optional": { "creator": "Bob the Builder", "timestamp": 1458239713 } } ``` **Note:** This can be generated for an image reference using `cosign generate $IMAGE_URI_DIGEST`. I'm happy to switch this format to something else if it makes sense. See https://github.com/notaryproject/nv2/issues/40 for one option. #### Registry Details `cosign` signatures are stored as separate objects in the OCI registry, with only a weak reference back to the object they "sign". This means this relationship is opaque to the registry, and signatures *will not* be deleted or garbage-collected when the image is deleted. Similarly, they **can** easily be copied from one environment to another, but this is not automatic. Multiple signatures are stored in a list which is unfortunately a race condition today. To add a signature, clients orchestrate a "read-append-write" operation, so the last write will win in the case of contention. ##### Specifying Registry `cosign` will default to storing signatures in the same repo as the image it is signing. To specify a different repo for signatures, you can set the `COSIGN_REPOSITORY` environment variable. This will replace the repo in the provided image like this: ```shell $ export COSIGN_REPOSITORY=gcr.io/my-new-repo $ cosign sign --key cosign.key $IMAGE_URI_DIGEST ``` So the signature for `gcr.io/dlorenc-vmtest2/demo` will be stored in `gcr.io/my-new-repo/demo:sha256-DIGEST.sig`. Note: different registries might expect different formats for the "repository." * To use [GCR](https://cloud.google.com/container-registry), a registry name like `gcr.io/$REPO` is sufficient, as in the example above. * To use [Artifact Registry](https://cloud.google.com/artifact-registry), specify a full image name like `$LOCATION-docker.pkg.dev/$PROJECT/$REPO/$STORAGE_IMAGE`, not just a repository. For example, ```shell $ export COSIGN_REPOSITORY=us-docker.pkg.dev/my-new-repo/demo $ cosign sign --key cosign.key $IMAGE_URI_DIGEST ``` where the `sha256-DIGEST` will match the digest for `gcr.io/dlorenc-vmtest2/demo`. Specifying just a repo like `$LOCATION-docker.pkg.dev/$PROJECT/$REPO` will not work in Artifact Registry. ## Signature Specification `cosign` is inspired by tools like [minisign](https://jedisct1.github.io/minisign/) and [signify](https://www.openbsd.org/papers/bsdcan-signify.html). Generated private keys are stored in PEM format. The keys encrypted under a password using scrypt as a KDF and nacl/secretbox for encryption. They have a PEM header of `ENCRYPTED SIGSTORE PRIVATE KEY`: ```shell -----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- ... -----END ENCRYPTED SIGSTORE PRIVATE KEY----- ``` Public keys are stored on disk in PEM-encoded standard PKIX format with a header of `PUBLIC KEY`. ``` -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELigCnlLNKgOglRTx1D7JhI7eRw99 QolE9Jo4QUxnbMy5nUuBL+UZF9qqfm/Dg1BNeHRThHzWh2ki9vAEgWEDOw== -----END PUBLIC KEY----- ``` ## Storage Specification `cosign` stores signatures in an OCI registry, and uses a naming convention (tag based on the sha256 of what we're signing) for locating the signature index.

`reg.example.com/ubuntu@sha256:703218c0465075f4425e58fac086e09e1de5c340b12976ab9eb8ad26615c3715` has signatures located at `reg.example.com/ubuntu:sha256-703218c0465075f4425e58fac086e09e1de5c340b12976ab9eb8ad26615c3715.sig` Roughly (ignoring ports in the hostname): `s/:/-/g` and `s/@/:/g` to find the signature index. See [Race conditions](#registry-details) for some caveats around this strategy. Alternative implementations could use transparency logs, local filesystem, a separate repository registry, an explicit reference to a signature index, a new registry API, grafeas, etc. ### Signing subjects `cosign` only works for artifacts stored as "manifests" in the registry today. The proposed mechanism is flexible enough to support signing arbitrary things. ### KMS Support `cosign` supports using a KMS provider to generate and sign keys. Right now cosign supports Hashicorp Vault, AWS KMS, GCP KMS, Azure Key Vault and we are hoping to support more in the future! See the [KMS docs](https://docs.sigstore.dev/cosign/key_management/overview/) for more details. ### OCI Artifacts Push an artifact to a registry using [oras](https://github.com/deislabs/oras) (in this case, `cosign` itself!): ```shell $ oras push us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact ./cosign Uploading f53604826795 cosign Pushed us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact Digest: sha256:551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef ``` Now sign it! Using `cosign` of course: ```shell $ cosign sign --key cosign.key us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact@sha256:551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef Enter password for private key: Pushing signature to: us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact:sha256-551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef.sig ``` Finally, verify `cosign` with `cosign` again: ```shell $ cosign verify --key cosign.pub us-central1-docker.pkg.dev/dlorenc-vmtest2/test/artifact@sha256:551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef The following checks were performed on each of these signatures: - The cosign claims were validated - The claims were present in the transparency log - The signatures were integrated into the transparency log when the certificate was valid - The signatures were verified against the specified public key - The code-signing certificate was verified using trusted certificate authority certificates {"Critical":{"Identity":{"docker-reference":""},"Image":{"Docker-manifest-digest":"sha256:551e6cce7ed2e5c914998f931b277bc879e675b74843e6f29bc17f3b5f692bef"},"Type":"cosign container image signature"},"Optional":null} ``` ## FAQ ### Why not use Notary v2 It's hard to answer this briefly. This post contains some comparisons: [Notary V2 and Cosign](https://medium.com/@dlorenc/notary-v2-and-cosign-b816658f044d) If you find other comparison posts, please send a PR here and we'll link them all. ### Why not use containers/image signing `containers/image` signing is close to `cosign`, and we reuse payload formats. `cosign` differs in that it signs with ECDSA-P256 keys instead of PGP, and stores signatures in the registry. ### Why not use TUF? I believe this tool is complementary to TUF, and they can be used together. I haven't tried yet, but think we can also reuse a registry for TUF storage. ## Design Requirements * No external services for signature storage, querying, or retrieval * We aim for as much registry support as possible * Everything should work over the registry API * PGP should not be required at all. * Users must be able to find all signatures for an image * Signers can sign an image after push * Multiple entities can sign an image * Signing an image does not mutate the image * Pure-go implementation ## Future Ideas ### Registry API Changes The naming convention and read-modify-write update patterns we use to store things in a registry are a bit, well, "hacky". I think they're the best (only) real option available today, but if the registry API changes we can improve these. ### Other Types `cosign` can sign anything in a registry. These examples show signing a single image, but you could also sign a multi-platform `Index`, or any other type of artifact. This includes Helm Charts, Tekton Pipelines, and anything else currently using OCI registries for distribution. This also means new artifact types can be uploaded to a registry and signed. One interesting type to store and sign would be TUF repositories. I haven't tried yet, but I'm fairly certain TUF could be implemented on top of this. ### Tag Signing `cosign` signatures protect the digests of objects stored in a registry. The optional `annotations` support (via the `-a` flag to `cosign sign`) can be used to add extra data to the payload that is signed and protected by the signature. One use-case for this might be to sign a tag->digest mapping. If you would like to attest that a specific tag (or set of tags) should point at a digest, you can run something like: ```shell $ docker push $IMAGE_URI The push refers to repository [dlorenc/demo] 994393dc58e7: Pushed 5m: digest: sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870 size: 528 $ TAG=sign-me $ cosign sign --key cosign.key -a tag=$TAG $IMAGE_URI_DIGEST Enter password for private key: Pushing signature to: dlorenc/demo:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870.sig ``` Then you can verify that the tag->digest mapping is also covered in the signature, using the `-a` flag to `cosign verify`. This example verifies that the digest `$TAG` which points to (`sha256:1304f174557314a7ed9eddb4eab12fed12cb0cd9809e4c28f29af86979a3c870`) has been signed, **and also** that the `tag` annotation has the value `sign-me`: ```shell $ cosign verify --key cosign.pub -a tag=$TAG $IMAGE_URI | jq . { "Critical": { "Identity": { "docker-reference": "" }, "Image": { "Docker-manifest-digest": "97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36" }, "Type": "cosign container image signature" }, "Optional": { "tag": "sign-me" } } ``` Timestamps could also be added here, to implement TUF-style freeze-attack prevention. ### Base Image/Layer Signing Again, `cosign` can sign anything in a registry. You could use `cosign` to sign an image that is intended to be used as a base image, and include that provenance metadata in resulting derived images. This could be used to enforce that an image was built from an authorized base image. Rough Idea: * OCI manifests have an ordered list of `layer` `Descriptors`, which can contain annotations. See [here](https://github.com/opencontainers/image-spec/blob/master/manifest.md) for the specification. * A base image is an ordered list of layers to which other layers are appended, as well as an initial configuration object that is mutated. * A derived image is free to completely delete/destroy/recreate the config from its base image, so signing the config would provided limited value. * We can sign the full set of ordered base layers, and attach that signature as an annotation to the **last** layer in the resulting child image. This example manifest manifest represents an image that has been built from a base image with two layers. One additional layer is added, forming the final image. ```json { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 7023, "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 32654, "digest": "sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0" }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 16724, "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b", "annotations": { "dev.cosign.signature.baseimage": "Ejy6ipGJjUzMDoQFePWixqPBYF0iSnIvpMWps3mlcYNSEcRRZelL7GzimKXaMjxfhy5bshNGvDT5QoUJ0tqUAg==" } }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "size": 73109, "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" } ], } ``` Note that this could be applied recursively, for multiple intermediate base images. ### Counter-Signing Cosign signatures (and their protected payloads) are stored as artifacts in a registry. These signature objects can also be signed, resulting in a new, "counter-signature" artifact. This "counter-signature" protects the signature (or set of signatures) **and** the referenced artifact, which allows it to act as an attestation to the **signature(s) themselves**. Before we sign the signature artifact, we first give it a memorable name so we can find it later. ```shell $ cosign sign --key cosign.key -a sig=original $IMAGE_URI_DIGEST Enter password for private key: Pushing signature to: dlorenc/demo:sha256-97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36.sig $ cosign verify --key cosign.pub dlorenc/demo | jq . { "Critical": { "Identity": { "docker-reference": "" }, "Image": { "Docker-manifest-digest": "97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36" }, "Type": "cosign container image signature" }, "Optional": { "sig": "original" } } ``` Now give that signature a memorable name, then sign that: ```shell $ crane tag $(cosign triangulate $IMAGE_URI) mysignature 2021/02/15 20:22:55 dlorenc/demo:mysignature: digest: sha256:71f70e5d29bde87f988740665257c35b1c6f52dafa20fab4ba16b3b1f4c6ba0e size: 556 $ cosign sign --key cosign.key -a sig=counter dlorenc/demo:mysignature Enter password for private key: Pushing signature to: dlorenc/demo:sha256-71f70e5d29bde87f988740665257c35b1c6f52dafa20fab4ba16b3b1f4c6ba0e.sig $ cosign verify --key cosign.pub dlorenc/demo:mysignature {"Critical":{"Identity":{"docker-reference":""},"Image":{"Docker-manifest-digest":"71f70e5d29bde87f988740665257c35b1c6f52dafa20fab4ba16b3b1f4c6ba0e"},"Type":"cosign container image signature"},"Optional":{"sig":"counter"}} ``` Finally, check the original signature: ```shell $ crane manifest dlorenc/demo@sha256:71f70e5d29bde87f988740665257c35b1c6f52dafa20fab4ba16b3b1f4c6ba0e { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 233, "digest": "sha256:3b25a088710d03f39be26629d22eb68cd277a01673b9cb461c4c24fbf8c81c89" }, "layers": [ { "mediaType": "application/vnd.oci.descriptor.v1+json", "size": 217, "digest": "sha256:0e79a356609f038089088ec46fd95f4649d04de989487220b1a0adbcc63fadae", "annotations": { "dev.sigstore.cosign/signature": "5uNZKEP9rm8zxAL0VVX7McMmyArzLqtxMTNPjPO2ns+5GJpBeXg+i9ILU+WjmGAKBCqiexTxzLC1/nkOzD4cDA==" } } ] } ``` ## Release Cadence We cut releases as needed. Patch releases are cut to fix small bugs. Minor releases are cut periodically when there are multiple bugs fixed or features added. Major releases will be released when there are breaking features. ## Security Should you discover any security issues, please refer to sigstore's [security process](https://github.com/sigstore/.github/blob/main/SECURITY.md) ## PEM files in GitHub Release Assets The GitHub release assets for cosign contain a PEM file produced by [GoReleaser](https://github.com/sigstore/cosign/blob/ac999344eb381ae91455b0a9c5c267e747608d76/.goreleaser.yml#L166) while signing the cosign blob that is used to verify the integrity of the release binaries. This file is not used by cosign itself, but is provided for users who wish to verify the integrity of the release binaries. By default, cosign output these PEM files in [base64 encoded format](https://github.com/sigstore/cosign/blob/main/doc/cosign_sign-blob.md#options), this approach might be good for air-gapped environments where the PEM file is stored in a file system. So, you should decode these PEM files before using them to verify the blobs. cosign-2.5.0/VERSIONING.md000066400000000000000000000225541477503325500150300ustar00rootroot00000000000000# Cosign Versioning Policy This is the versioning policy regarding the stability of Cosign, both the API and CLI. **tl;dr (CLI):** semver for specific behaviors, but with deprecations. **tl;dr (API):** no guarantees at all (for now). Principles: - Provide well-versioned, very reliable tools/libraries that people can write scripts/code against. - Provide places for experimentation and rapid-development, not covered under versioning guarantees. - Be explicit about what is and isn’t covered. - Better to promise a weaker guarantee and stick to it consistently than to be vague about what is/isn’t covered and technically comply, or not comply at all. ## Cosign CLI The Cosign CLI’s version numbers are of the form `MAJOR.MINOR.PATCH`, but it is explicitly not covered by [semantic versioning][semver] guarantees. Instead, you should interpret version numbers as follows: **Major version increases.** These are "large changes." You should expect to need to rewrite scripts after these. There are no compatibility guarantees about the behavior or shape of the CLI. These should be infrequent and correspond to big improvements in the user experience. **Minor version increases.** These are "small changes." If you’ve been paying attention to and fixing deprecation warnings (see ["Deprecation"](#deprecation)), your scripts should continue to work (provided they only depend on explicitly guaranteed Cosign behavior; see "What's covered?"). **Patch version increases.** These are "bug fix" releases only, and should always be adopted.. [semver]: https://semver.org ### What's covered? Only behavior explicitly listed here is covered (to counter [Hyrum's Law][hyrums-law]). * Any documented behavior (i.e., behavior described in the output of `cosign -h`). If the wording is ambiguous, the behavior may change as long as the corresponding documentation changes to make it less ambiguous. * Output to standard output, *when described in the documentation*. Each command should describe its default output format (ideally with examples). * Any scripts should *only* rely on standard output. * Consumers using compliant parsers will continue to be able parse the output of Cosign. * New fields may be added, but fields will not be removed. * Additional conventions documented in [CLI.md](CLI.md). What’s *not* covered? * Output to standard error is *not* covered and may change at any time. * If the Sigstore API changes, the output of Cosign may change. * Fixes to security bugs (for instance, something invalid used to pass verification, but now it does not). However, "security features" should not break backwards-compatibility. * Any "unstable" commands. Violations of this are generally considered bugs; we will issue patch releases for each affected minor version. In some (rare) cases, an exception may be made with the approval of the Cosign maintainers as long as the decision results in an update to this versioning policy. [hyrums-law]: https://www.hyrumslaw.com/ ### Feature stability It’s important to enable rapid iteration on the development of new Cosign features while providing stability to users of more-mature features. As such, the Cosign CLI will support the following release levels with corresponding guarantees: * **Experimental**: No guarantees. May change in a backwards-incompatible way or be removed at any time without warning. * Generally available (**GA**): follows the guidelines in this document. Users must explicitly indicate that they want to opt-in to experimental behavior by setting the environment variable `COSIGN_EXPERIMENTAL=1`. ### Supported versions **Full support.** We fully support only the latest release of the Cosign CLI. **Security support.** We will backport security fixes to all major versions released in the past year. We may backport security fixes, depending on severity, to all minor versions released in the last year. We may choose to backport security "features" as well. ### Deprecation Any behavior may be deprecated at any time with sufficient warning. For GA features, that period is 6 months from the Cosign release in which the deprecation was first announced; the first subsequent Cosign release should remove that feature. A new major version voids all deprecation timeline guarantees, and any deprecated behaviors can be removed at that time. Deprecation requires: * When users invoke the deprecated behavior, print a deprecation message to standard error. * The message should look something like: ``` WARNING: $BEHAVIOR is deprecated and will be removed in a Cosign release soon after $DEPRECATION_DATE (see $GITHUB_ISSUE_LINK). Instead, please $ALTERNATIVE. ``` * If we can’t tell whether a user is using the deprecated behavior (for instance, if we’re going to remove a field), print the message every time. * There MAY be a way to turn off the message. In many cases, this will just be by using the alternative. Otherwise, we can consider adding a flag (which is now subject to a deprecation period of its own). * A release note at the introduction of the deprecation, along with the removal of the behavior. * Any documentation that refers to the deprecated behavior in the Cosign repository should have the same information as in the message. * Other documentation will be updated on a best-effort basis. For a list of currently deprecated behavior, search the codebase (all deprecated behavior will use a shared "deprecation" library and therefore be easy to find). ### Rationale/background Currently, many folks assume that the Cosign CLI follows semantic versioning. We have a bunch of breaking changes blocked on a 2.0 release. Even after that point, there are a number of proposed breaking changes/overhauls proposed. But there’s hesitancy to publish another major version. Further, semver doesn’t lend itself to a regular deprecation train (marking things deprecated, then ripping out after a fixed period of time); rather, in semver, you always have to wait for a new major version, which batches together a bunch of proposed deprecated behavior. Approaches considered include: * Follow semver. Bump major versions frequently (this is why Chrome is at version 107 and Firefox is at 106, and gcloud is at version 409). I think this is actually way more user-hostile; it effectively means "there are no compatibility guarantees." * Follow semver. Avoid major version releases. I think that Sigstore and Cosign are developing too rapidly to commit to avoid breaking things. Further, this winds up preventing incremental improvements to the CLI in favor of huge changes. For instance, right now with the v2 work underway the codebase is in an awkward spot and would take a fair bit of work/branch management to push a minor version release; realistically, the next release will be v2 even if we have bug fixes we could push before then. ## API support for old Cosign versions Starting with Cosign CLI v2, the Sigstore infrastructure should support old Cosign versions for one year past release, and six months past the availability of a suitable replacement. Any breakage here will be considered a bug and should be rolled back or fixed. We will run end-to-end tests for all prior supported minor versions. ## Cosign API The Cosign API is versioned independently of the Cosign CLI. Version numbers are `MAJOR.MINOR.PATCH`. For now, there are **no stability guarantees**: * The library will undergo a serious refactor to make using it much safer/easier. * Most callers (anybody not dealing with OCI registries/containers) will want [sigstore-go][] instead. * At a future major version, the library will follow semantic versioning (and this policy will be updated). * After that point, only code in pkg/ will be covered by semver; Cosign will follow semver strictly. * Functionality should be private (either language-private or in a "private" package like `internal/` or `cmd/`) by default, and exposing it publicly will be a deliberate choice. * In-development features will live under a new `pkg/experimental/` directory, which will not be covered by semver. * Updating a library is safer than updating a CLI tool, so we will bump major versions liberally (though not excessively). ### Rationale/background We are not currently maintaining the Cosign API as if it’s a 1.0 library. There are breaking changes all the time, and contributors don’t even seem to notice (users might, but they haven’t complained much)! So effectively it's got no versioning policy as-is; this just makes that official. The only reason it’s marked 1.0 is because the CLI was marked 1.0; it certainly isn’t ready for semver guarantees ([which Golang requires][go-semver]). Also, it doesn’t make sense to tie the versions of the API and CLI to each other. If I wanted to do a big refactor of the API that didn't affect the CLI, that might require a major version bump even if the CLI was entirely unchanged. Soon, [sigstore-go][] will be the default Golang API for Sigstore; this library will have a well-thought-out API and be stable. After that point, we can refactor Cosign to use it (see [Sigstore in Golang][sigstore-in-golang]) and then clean up the Cosign API for those who need OCI support. [go-semver]: https://go.dev/doc/modules/release-workflow#breaking [sigstore-go]: https://github.com/sigstore/sigstore-go [sigstore-in-golang]: https://docs.google.com/document/d/1aZfk1TlzcuaO0uz76M9D26-gAvoZLn0oCAKvkbuhcPM/edit cosign-2.5.0/artifacthub-repo.yml000066400000000000000000000013451477503325500170000ustar00rootroot00000000000000# # Copyright 2022 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. repositoryID: 0344601c-282a-4857-a852-d9207291ccfe owners: - name: cpanato email: ctadeu@gmail.com - name: dlorenc email: lorenc.d@gmail.com cosign-2.5.0/cmd/000077500000000000000000000000001477503325500135565ustar00rootroot00000000000000cosign-2.5.0/cmd/conformance/000077500000000000000000000000001477503325500160505ustar00rootroot00000000000000cosign-2.5.0/cmd/conformance/main.go000066400000000000000000000064541477503325500173340ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "log" "os" "os/exec" "path/filepath" "strings" "github.com/sigstore/sigstore-go/pkg/bundle" ) var bundlePath *string var certOIDC *string var certSAN *string var identityToken *string var trustedRootPath *string func usage() { fmt.Println("Usage:") fmt.Printf("\t%s sign-bundle --identity-token TOKEN --bundle FILE FILE\n", os.Args[0]) fmt.Printf("\t%s verify-bundle --bundle FILE --certificate-identity IDENTITY --certificate-oidc-issuer URL [--trusted-root FILE] FILE\n", os.Args[0]) } func parseArgs() { for i := 2; i < len(os.Args); { switch os.Args[i] { // TODO: support staging (see https://github.com/sigstore/cosign/issues/2434) // // Today cosign signing does not yet use sigstore-go, and so we would // need to make some clever invocation of `cosign initialize` to // support staging. Instead it might make sense to wait for cosign // signing to use sigstore-go. case "--bundle": bundlePath = &os.Args[i+1] i += 2 case "--certificate-oidc-issuer": certOIDC = &os.Args[i+1] i += 2 case "--certificate-identity": certSAN = &os.Args[i+1] i += 2 case "--identity-token": identityToken = &os.Args[i+1] i += 2 case "--trusted-root": trustedRootPath = &os.Args[i+1] i += 2 default: i++ } } } func main() { if len(os.Args) < 2 { usage() os.Exit(1) } parseArgs() args := []string{} switch os.Args[1] { case "sign-bundle": args = append(args, "sign-blob") args = append(args, "-y") case "verify-bundle": args = append(args, "verify-blob") // How do we know if we should expect signed timestamps or not? // Let's crack open the bundle if bundlePath != nil { b, err := bundle.LoadJSONFromPath(*bundlePath) if err != nil { log.Fatal(err) } ts, err := b.Timestamps() if err != nil { log.Fatal(err) } if len(ts) > 0 { args = append(args, "--use-signed-timestamps") } } default: log.Fatalf("Unsupported command %s", os.Args[1]) } if bundlePath != nil { args = append(args, "--bundle", *bundlePath) args = append(args, "--new-bundle-format") } if identityToken != nil { args = append(args, "--identity-token", *identityToken) } if certSAN != nil { args = append(args, "--certificate-identity", *certSAN) } if certOIDC != nil { args = append(args, "--certificate-oidc-issuer", *certOIDC) } if trustedRootPath != nil { args = append(args, "--trusted-root", *trustedRootPath) } args = append(args, os.Args[len(os.Args)-1]) dir := filepath.Dir(os.Args[0]) cmd := exec.Command(filepath.Join(dir, "cosign"), args...) // #nosec G204 var out strings.Builder cmd.Stdout = &out cmd.Stderr = &out err := cmd.Run() fmt.Println(out.String()) if err != nil { log.Fatal(err) } } cosign-2.5.0/cmd/cosign/000077500000000000000000000000001477503325500150405ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/000077500000000000000000000000001477503325500156075ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/attach.go000066400000000000000000000111131477503325500173770ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "os" "github.com/sigstore/cosign/v2/cmd/cosign/cli/attach" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/spf13/cobra" ) func Attach() *cobra.Command { cmd := &cobra.Command{ Use: "attach", Short: "Provides utilities for attaching artifacts to other artifacts in a registry", } cmd.AddCommand( attachSignature(), attachSBOM(), attachAttestation(), ) return cmd } func attachSignature() *cobra.Command { o := &options.AttachSignatureOptions{} cmd := &cobra.Command{ Use: "signature", Short: "Attach signatures to the supplied container image", Example: ` cosign attach signature [--payload ] [--signature < path>] [--rekor-response < path>] cosign attach signature command attaches payload, signature, rekor-bundle, etc in a new layer of provided image. # Attach signature can attach payload to a supplied image cosign attach signature --payload $IMAGE # Attach signature can attach payload, signature to a supplied image cosign attach signature --payload --signature $IMAGE # Attach signature can attach payload, signature, time stamped response to a supplied image cosign attach signature --payload --signature --tsr= $IMAGE # Attach signature attaches payload, signature and rekor-bundle via rekor-response to a supplied image cosign attach signature --payload --signature --rekor-response $IMAGE # Attach signature attaches payload, signature and rekor-bundle directly to a supplied image cosign attach signature --payload --signature --rekor-response $IMAGE`, PersistentPreRun: options.BindViper, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return attach.SignatureCmd(cmd.Context(), o.Registry, o.Signature, o.Payload, o.Cert, o.CertChain, o.TimeStampedSig, o.RekorBundle, args[0]) }, } o.AddFlags(cmd) return cmd } func attachSBOM() *cobra.Command { o := &options.AttachSBOMOptions{} cmd := &cobra.Command{ Use: "sbom", Short: "DEPRECATED: Attach sbom to the supplied container image", Long: "Attach sbom to the supplied container image\n\n" + options.SBOMAttachmentDeprecation, Example: " cosign attach sbom ", Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { fmt.Fprintln(os.Stderr, options.SBOMAttachmentDeprecation) mediaType, err := o.MediaType() if err != nil { return err } fmt.Fprintf(os.Stderr, "WARNING: Attaching SBOMs this way does not sign them. To sign them, use 'cosign attest --predicate %s --key '.\n", o.SBOM) return attach.SBOMCmd(cmd.Context(), o.Registry, o.RegistryExperimental, o.SBOM, mediaType, args[0]) }, } o.AddFlags(cmd) return cmd } func attachAttestation() *cobra.Command { o := &options.AttachAttestationOptions{} cmd := &cobra.Command{ Use: "attestation", Short: "Attach attestation to the supplied container image", Example: ` cosign attach attestation --attestation # attach attestations from multiple files to a container image cosign attach attestation --attestation --attestation # attach attestation from bundle files in form of JSONLines to a container image # https://github.com/in-toto/attestation/blob/main/spec/v1.0-draft/bundle.md cosign attach attestation --attestation `, Args: cobra.MinimumNArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return attach.AttestationCmd(cmd.Context(), o.Registry, o.Attestations, args[0]) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/attach/000077500000000000000000000000001477503325500170535ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/attach/attach.go000066400000000000000000000065711477503325500206570ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attach import ( "context" "encoding/json" "fmt" "os" "github.com/google/go-containerregistry/pkg/name" ssldsse "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" ) func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, signedPayloads []string, imageRef string) error { ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } for _, payload := range signedPayloads { if err := attachAttestation(ctx, ociremoteOpts, payload, imageRef, regOpts.NameOptions()); err != nil { return fmt.Errorf("attaching payload from %s: %w", payload, err) } } return nil } func attachAttestation(ctx context.Context, remoteOpts []ociremote.Option, signedPayload, imageRef string, nameOpts []name.Option) error { fmt.Fprintf(os.Stderr, "Using payload from: %s", signedPayload) attestationFile, err := os.Open(signedPayload) if err != nil { return err } defer attestationFile.Close() env := ssldsse.Envelope{} decoder := json.NewDecoder(attestationFile) for decoder.More() { if err := decoder.Decode(&env); err != nil { return err } payload, err := json.Marshal(env) if err != nil { return err } if env.PayloadType != types.IntotoPayloadType { return fmt.Errorf("invalid payloadType %s on envelope. Expected %s", env.PayloadType, types.IntotoPayloadType) } if len(env.Signatures) == 0 { return fmt.Errorf("could not attach attestation without having signatures") } ref, err := name.ParseReference(imageRef, nameOpts...) if err != nil { return err } if _, ok := ref.(name.Digest); !ok { msg := fmt.Sprintf(ui.TagReferenceMessage, imageRef) ui.Warnf(ctx, msg) } digest, err := ociremote.ResolveDigest(ref, remoteOpts...) if err != nil { return err } // Overwrite "ref" with a digest to avoid a race where we use a tag // multiple times, and it potentially points to different things at // each access. ref = digest // nolint opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} att, err := static.NewAttestation(payload, opts...) if err != nil { return err } se, err := ociremote.SignedEntity(digest, remoteOpts...) if err != nil { return err } newSE, err := mutate.AttachAttestationToEntity(se, att) if err != nil { return err } // Publish the signatures associated with this entity err = ociremote.WriteAttestations(digest.Repository, newSE, remoteOpts...) if err != nil { return err } } return nil } cosign-2.5.0/cmd/cosign/cli/attach/sbom.go000066400000000000000000000110741477503325500203450ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attach import ( "context" "errors" "fmt" "io" "net/http" "os" "path/filepath" "github.com/google/go-containerregistry/pkg/logs" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" ocistatic "github.com/google/go-containerregistry/pkg/v1/static" ocitypes "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote" "github.com/sigstore/cosign/v2/internal/ui" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/static" ) func SBOMCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts options.RegistryExperimentalOptions, sbomRef string, sbomType ocitypes.MediaType, imageRef string) error { if regExpOpts.RegistryReferrersMode == options.RegistryReferrersModeOCI11 { return sbomCmdOCIExperimental(ctx, regOpts, sbomRef, sbomType, imageRef) } ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } b, err := sbomBytes(sbomRef) if err != nil { return err } remoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return err } dstRef, err := ociremote.SBOMTag(ref, remoteOpts...) if err != nil { return err } ui.Infof(ctx, "Uploading SBOM file for [%s] to [%s] with mediaType [%s].\n", ref.Name(), dstRef.Name(), sbomType) img, err := static.NewFile(b, static.WithLayerMediaType(sbomType)) if err != nil { return err } return remote.Write(dstRef, img, regOpts.GetRegistryClientOpts(ctx)...) } func sbomCmdOCIExperimental(ctx context.Context, regOpts options.RegistryOptions, sbomRef string, sbomType ocitypes.MediaType, imageRef string) error { var dig name.Digest ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } if digr, ok := ref.(name.Digest); ok { dig = digr } else { desc, err := remote.Head(ref, regOpts.GetRegistryClientOpts(ctx)...) if err != nil { return err } dig = ref.Context().Digest(desc.Digest.String()) } artifactType := ociexperimental.ArtifactType("sbom") desc, err := remote.Head(dig, regOpts.GetRegistryClientOpts(ctx)...) var terr *transport.Error if errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound { h, err := v1.NewHash(dig.DigestStr()) if err != nil { return err } // The subject doesn't exist, attach to it as if it's an empty OCI image. logs.Progress.Println("subject doesn't exist, attaching to empty image") desc = &v1.Descriptor{ ArtifactType: artifactType, MediaType: ocitypes.OCIManifestSchema1, Size: 0, Digest: h, } } else if err != nil { return err } b, err := sbomBytes(sbomRef) if err != nil { return err } empty := mutate.MediaType( mutate.ConfigMediaType(empty.Image, ocitypes.MediaType(artifactType)), ocitypes.OCIManifestSchema1) att, err := mutate.AppendLayers(empty, ocistatic.NewLayer(b, sbomType)) if err != nil { return err } att = mutate.Subject(att, *desc).(v1.Image) attdig, err := att.Digest() if err != nil { return err } dstRef := ref.Context().Digest(attdig.String()) fmt.Fprintf(os.Stderr, "Uploading SBOM file for [%s] to [%s] with config.mediaType [%s] layers[0].mediaType [%s].\n", ref.Name(), dstRef.String(), artifactType, sbomType) return remote.Write(dstRef, att, regOpts.GetRegistryClientOpts(ctx)...) } func sbomBytes(sbomRef string) ([]byte, error) { // sbomRef can be "-", a string or a file. switch signatureType(sbomRef) { case StdinSignature: return io.ReadAll(os.Stdin) case RawSignature: return []byte(sbomRef), nil case FileSignature: return os.ReadFile(filepath.Clean(sbomRef)) default: return nil, errors.New("unknown SBOM arg type") } } cosign-2.5.0/cmd/cosign/cli/attach/sig.go000066400000000000000000000101561477503325500201670ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attach import ( "context" "encoding/json" "errors" "io" "os" "path/filepath" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/static" ) func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, payloadRef, certRef, certChainRef, timeStampedSigRef, rekorBundleRef, imageRef string) error { b64SigBytes, err := signatureBytes(sigRef) if err != nil { return err } else if len(b64SigBytes) == 0 { return errors.New("empty signature") } ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return err } digest, err := ociremote.ResolveDigest(ref, ociremoteOpts...) if err != nil { return err } // Overwrite "ref" with a digest to avoid a race where we use a tag // multiple times, and it potentially points to different things at // each access. ref = digest // nolint var payload []byte if payloadRef == "" { payload, err = cosign.ObsoletePayload(ctx, digest) } else { payload, err = os.ReadFile(filepath.Clean(payloadRef)) } if err != nil { return err } sig, err := static.NewSignature(payload, string(b64SigBytes)) if err != nil { return err } var cert []byte var certChain []byte var timeStampedSig []byte var rekorBundle *bundle.RekorBundle if certRef != "" { cert, err = os.ReadFile(filepath.Clean(certRef)) if err != nil { return err } } if certChainRef != "" { certChain, err = os.ReadFile(filepath.Clean(certChainRef)) if err != nil { return err } } if timeStampedSigRef != "" { timeStampedSig, err = os.ReadFile(filepath.Clean(timeStampedSigRef)) if err != nil { return err } } tsBundle := bundle.TimestampToRFC3161Timestamp(timeStampedSig) if rekorBundleRef != "" { rekorBundleByte, err := os.ReadFile(filepath.Clean(rekorBundleRef)) if err != nil { return err } var localCosignPayload cosign.LocalSignedPayload err = json.Unmarshal(rekorBundleByte, &localCosignPayload) if err != nil { return err } rekorBundle = localCosignPayload.Bundle } newSig, err := mutate.Signature(sig, mutate.WithCertChain(cert, certChain), mutate.WithRFC3161Timestamp(tsBundle), mutate.WithBundle(rekorBundle)) if err != nil { return err } se, err := ociremote.SignedEntity(digest, ociremoteOpts...) if err != nil { return err } // Attach the signature to the entity. newSE, err := mutate.AttachSignatureToEntity(se, newSig) if err != nil { return err } // Publish the signatures associated with this entity return ociremote.WriteSignatures(digest.Repository, newSE, ociremoteOpts...) } type SignatureArgType uint8 const ( StdinSignature SignatureArgType = iota RawSignature SignatureArgType = iota FileSignature SignatureArgType = iota ) func signatureBytes(sigRef string) ([]byte, error) { // sigRef can be "-", a string or a file. switch signatureType(sigRef) { case StdinSignature: return io.ReadAll(os.Stdin) case RawSignature: return []byte(sigRef), nil case FileSignature: return os.ReadFile(filepath.Clean(sigRef)) default: return nil, errors.New("unknown signature arg type") } } func signatureType(sigRef string) SignatureArgType { if sigRef == "-" { return StdinSignature } return FileSignature } cosign-2.5.0/cmd/cosign/cli/attest.go000066400000000000000000000116661477503325500174540ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/attest" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func Attest() *cobra.Command { o := &options.AttestOptions{} cmd := &cobra.Command{ Use: "attest", Short: "Attest the supplied container image.", Example: ` cosign attest --key | [--predicate ] [--a key=value] [--no-upload=true|false] [--record-creation-timestamp=true|false] [--f] [--r] # attach an attestation to a container image Google sign-in cosign attest --timeout 90s --predicate --type # attach an attestation to a container image with a local key pair file cosign attest --predicate --type --key cosign.key # attach an attestation to a container image with a key pair stored in Azure Key Vault cosign attest --predicate --type --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # attach an attestation to a container image with a key pair stored in AWS KMS cosign attest --predicate --type --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # attach an attestation to a container image with a key pair stored in Google Cloud KMS cosign attest --predicate --type --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY]/versions/[VERSION] # attach an attestation to a container image with a key pair stored in Hashicorp Vault cosign attest --predicate --type --key hashivault://[KEY] # attach an attestation to a container image with a local key pair file, including a certificate and certificate chain cosign attest --predicate --type --key cosign.key --cert cosign.crt --cert-chain chain.crt # attach an attestation to a container image which does not fully support OCI media types COSIGN_DOCKER_MEDIA_TYPES=1 cosign attest --predicate --type --key cosign.key legacy-registry.example.com/my/image # supply attestation via stdin echo | cosign attest --predicate - # attach an attestation to a container image and honor the creation timestamp of the signature cosign attest --predicate --type --key cosign.key --record-creation-timestamp `, Args: cobra.MinimumNArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { oidcClientSecret, err := o.OIDC.ClientSecret() if err != nil { return err } ko := options.KeyOpts{ KeyRef: o.Key, PassFunc: generate.GetPass, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, FulcioURL: o.Fulcio.URL, IDToken: o.Fulcio.IdentityToken, FulcioAuthFlow: o.Fulcio.AuthFlow, InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, RekorURL: o.Rekor.URL, OIDCIssuer: o.OIDC.Issuer, OIDCClientID: o.OIDC.ClientID, OIDCClientSecret: oidcClientSecret, OIDCRedirectURL: o.OIDC.RedirectURL, OIDCProvider: o.OIDC.Provider, SkipConfirmation: o.SkipConfirmation, TSAClientCACert: o.TSAClientCACert, TSAClientKey: o.TSAClientKey, TSAClientCert: o.TSAClientCert, TSAServerName: o.TSAServerName, TSAServerURL: o.TSAServerURL, NewBundleFormat: o.NewBundleFormat, } attestCommand := attest.AttestCommand{ KeyOpts: ko, RegistryOptions: o.Registry, CertPath: o.Cert, CertChainPath: o.CertChain, NoUpload: o.NoUpload, PredicatePath: o.Predicate.Path, PredicateType: o.Predicate.Type, Replace: o.Replace, Timeout: ro.Timeout, TlogUpload: o.TlogUpload, RekorEntryType: o.RekorEntryType, RecordCreationTimestamp: o.RecordCreationTimestamp, } for _, img := range args { if err := attestCommand.Exec(cmd.Context(), img); err != nil { return fmt.Errorf("signing %s: %w", img, err) } } return nil }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/attest/000077500000000000000000000000001477503325500171135ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/attest/attest.go000066400000000000000000000204621477503325500207520ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attest import ( "bytes" "context" _ "crypto/sha256" // for `crypto.SHA256` "encoding/json" "fmt" "os" "time" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" tsaclient "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/attestation" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" cremote "github.com/sigstore/cosign/v2/pkg/cosign/remote" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error) func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*models.LogEntryAnon, error) { rekorBytes, err := sv.Bytes(ctx) if err != nil { return nil, err } rekorClient, err := rekor.NewClient(rekorURL) if err != nil { return nil, err } entry, err := upload(rekorClient, rekorBytes) if err != nil { return nil, err } fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex) return entry, nil } // nolint type AttestCommand struct { options.KeyOpts options.RegistryOptions CertPath string CertChainPath string NoUpload bool PredicatePath string PredicateType string Replace bool Timeout time.Duration TlogUpload bool TSAServerURL string RekorEntryType string RecordCreationTimestamp bool } // nolint func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { // We can't have both a key and a security key if options.NOf(c.KeyRef, c.Sk) > 1 { return &options.KeyParseError{} } if c.PredicatePath == "" { return fmt.Errorf("predicate cannot be empty") } if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" { return fmt.Errorf("unknown value for rekor-entry-type") } predicateURI, err := options.ParsePredicateType(c.PredicateType) if err != nil { return err } ref, err := name.ParseReference(imageRef, c.NameOptions()...) if err != nil { return fmt.Errorf("parsing reference: %w", err) } if _, ok := ref.(name.Digest); !ok { msg := fmt.Sprintf(ui.TagReferenceMessage, imageRef) ui.Warnf(ctx, msg) } if c.Timeout != 0 { var cancelFn context.CancelFunc ctx, cancelFn = context.WithTimeout(ctx, c.Timeout) defer cancelFn() } ociremoteOpts, err := c.RegistryOptions.ClientOpts(ctx) if err != nil { return err } digest, err := ociremote.ResolveDigest(ref, ociremoteOpts...) if err != nil { return err } h, _ := v1.NewHash(digest.Identifier()) // Overwrite "ref" with a digest to avoid a race where we use a tag // multiple times, and it potentially points to different things at // each access. ref = digest // nolint sv, err := sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts) if err != nil { return fmt.Errorf("getting signer: %w", err) } defer sv.Close() wrapped := dsse.WrapSigner(sv, types.IntotoPayloadType) dd := cremote.NewDupeDetector(sv) predicate, err := predicateReader(c.PredicatePath) if err != nil { return fmt.Errorf("getting predicate reader: %w", err) } defer predicate.Close() sh, err := attestation.GenerateStatement(attestation.GenerateOpts{ Predicate: predicate, Type: c.PredicateType, Digest: h.Hex, Repo: digest.Repository.String(), }) if err != nil { return err } payload, err := json.Marshal(sh) if err != nil { return err } signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx)) if err != nil { return fmt.Errorf("signing: %w", err) } if c.NoUpload { fmt.Println(string(signedPayload)) return nil } opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} if sv.Cert != nil { opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) } var timestampBytes []byte var tsaPayload []byte if c.KeyOpts.TSAServerURL != "" { // We need to decide what signature to send to the timestamp authority. // // Historically, cosign sent `signedPayload`, which is the entire JSON DSSE // Envelope. However, when sigstore clients are verifying a bundle they // will use the DSSE Sig field, so we choose what signature to send to // the timestamp authority based on our output format. if c.KeyOpts.NewBundleFormat { tsaPayload, err = getEnvelopeSigBytes(signedPayload) if err != nil { return err } } else { tsaPayload = signedPayload } tc := tsaclient.NewTSAClient(c.KeyOpts.TSAServerURL) if c.KeyOpts.TSAClientCert != "" { tc = tsaclient.NewTSAClientMTLS(c.KeyOpts.TSAServerURL, c.KeyOpts.TSAClientCACert, c.KeyOpts.TSAClientCert, c.KeyOpts.TSAClientKey, c.KeyOpts.TSAServerName, ) } timestampBytes, err = tsa.GetTimestampedSignature(tsaPayload, tc) if err != nil { return err } bundle := cbundle.TimestampToRFC3161Timestamp(timestampBytes) opts = append(opts, static.WithRFC3161Timestamp(bundle)) } predicateType, err := options.ParsePredicateType(c.PredicateType) if err != nil { return err } predicateTypeAnnotation := map[string]string{ "predicateType": predicateType, } // Add predicateType as manifest annotation opts = append(opts, static.WithAnnotations(predicateTypeAnnotation)) // Check whether we should be uploading to the transparency log shouldUpload, err := sign.ShouldUploadToTlog(ctx, c.KeyOpts, digest, c.TlogUpload) if err != nil { return fmt.Errorf("should upload to tlog: %w", err) } var rekorEntry *models.LogEntryAnon if shouldUpload { rekorEntry, err = uploadToTlog(ctx, sv, c.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { if c.RekorEntryType == "intoto" { return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b) } else { return cosign.TLogUploadDSSEEnvelope(ctx, r, signedPayload, b) } }) if err != nil { return err } opts = append(opts, static.WithBundle(cbundle.EntryToBundle(rekorEntry))) } sig, err := static.NewAttestation(signedPayload, opts...) if err != nil { return err } if c.KeyOpts.NewBundleFormat { signerBytes, err := sv.Bytes(ctx) if err != nil { return err } bundleBytes, err := makeNewBundle(sv, rekorEntry, payload, signedPayload, signerBytes, timestampBytes) if err != nil { return err } return ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, predicateType, ociremoteOpts...) } // We don't actually need to access the remote entity to attach things to it // so we use a placeholder here. se := ociremote.SignedUnknown(digest, ociremoteOpts...) signOpts := []mutate.SignOption{ mutate.WithDupeDetector(dd), mutate.WithRecordCreationTimestamp(c.RecordCreationTimestamp), } if c.Replace { ro := cremote.NewReplaceOp(predicateURI) signOpts = append(signOpts, mutate.WithReplaceOp(ro)) } // Attach the attestation to the entity. newSE, err := mutate.AttachAttestationToEntity(se, sig, signOpts...) if err != nil { return err } // Publish the attestations associated with this entity return ociremote.WriteAttestations(digest.Repository, newSE, ociremoteOpts...) } cosign-2.5.0/cmd/cosign/cli/attest/attest_blob.go000066400000000000000000000235711477503325500217540ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attest import ( "bytes" "context" "crypto" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "os" "path" "path/filepath" "strings" "time" "errors" "github.com/secure-systems-lab/go-securesystemslib/dsse" "google.golang.org/protobuf/encoding/protojson" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" tsaclient "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/attestation" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/types" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protodsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" sigstoredsse "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) // nolint type AttestBlobCommand struct { options.KeyOpts CertPath string CertChainPath string ArtifactHash string PredicatePath string PredicateType string TlogUpload bool Timeout time.Duration OutputSignature string OutputAttestation string OutputCertificate string RekorEntryType string } // nolint func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error { // We can't have both a key and a security key if options.NOf(c.KeyRef, c.Sk) > 1 { return &options.KeyParseError{} } if c.PredicatePath == "" { return fmt.Errorf("predicate cannot be empty") } if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" { return fmt.Errorf("unknown value for rekor-entry-type") } if c.Timeout != 0 { var cancelFn context.CancelFunc ctx, cancelFn = context.WithTimeout(ctx, c.Timeout) defer cancelFn() } if c.TSAServerURL != "" && c.RFC3161TimestampPath == "" && !c.NewBundleFormat { return errors.New("expected either new bundle or an rfc3161-timestamp path when using a TSA server") } var artifact []byte var hexDigest string var err error if c.ArtifactHash == "" { if artifactPath == "-" { artifact, err = io.ReadAll(os.Stdin) } else { fmt.Fprintln(os.Stderr, "Using payload from:", artifactPath) artifact, err = os.ReadFile(filepath.Clean(artifactPath)) } if err != nil { return err } } if c.ArtifactHash == "" { digest, _, err := signature.ComputeDigestForSigning(bytes.NewReader(artifact), crypto.SHA256, []crypto.Hash{crypto.SHA256, crypto.SHA384}) if err != nil { return err } hexDigest = strings.ToLower(hex.EncodeToString(digest)) } else { hexDigest = c.ArtifactHash } predicate, err := predicateReader(c.PredicatePath) if err != nil { return fmt.Errorf("getting predicate reader: %w", err) } defer predicate.Close() sv, err := sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts) if err != nil { return fmt.Errorf("getting signer: %w", err) } defer sv.Close() wrapped := sigstoredsse.WrapSigner(sv, types.IntotoPayloadType) base := path.Base(artifactPath) sh, err := attestation.GenerateStatement(attestation.GenerateOpts{ Predicate: predicate, Type: c.PredicateType, Digest: hexDigest, Repo: base, }) if err != nil { return err } payload, err := json.Marshal(sh) if err != nil { return err } sig, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx)) if err != nil { return fmt.Errorf("signing: %w", err) } var rfc3161Timestamp *cbundle.RFC3161Timestamp var timestampBytes []byte var tsaPayload []byte var rekorEntry *models.LogEntryAnon if c.KeyOpts.TSAServerURL != "" { tc := tsaclient.NewTSAClient(c.KeyOpts.TSAServerURL) if c.TSAClientCert != "" { tc = tsaclient.NewTSAClientMTLS(c.KeyOpts.TSAServerURL, c.KeyOpts.TSAClientCACert, c.KeyOpts.TSAClientCert, c.KeyOpts.TSAClientKey, c.KeyOpts.TSAServerName, ) } // We need to decide what signature to send to the timestamp authority. // // Historically, cosign sent `sig`, which is the entire JSON DSSE // Envelope. However, when sigstore clients are verifying a bundle they // will use the DSSE Sig field, so we choose what signature to send to // the timestamp authority based on our output format. if c.NewBundleFormat { tsaPayload, err = getEnvelopeSigBytes(sig) if err != nil { return err } } else { tsaPayload = sig } timestampBytes, err = tsa.GetTimestampedSignature(tsaPayload, tc) if err != nil { return err } rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(timestampBytes) // TODO: Consider uploading RFC3161 TS to Rekor if rfc3161Timestamp == nil { return fmt.Errorf("rfc3161 timestamp is nil") } if c.RFC3161TimestampPath != "" { ts, err := json.Marshal(rfc3161Timestamp) if err != nil { return err } if err := os.WriteFile(c.RFC3161TimestampPath, ts, 0600); err != nil { return fmt.Errorf("create RFC3161 timestamp file: %w", err) } fmt.Fprintln(os.Stderr, "RFC3161 timestamp bundle written to file ", c.RFC3161TimestampPath) } } signer, err := sv.Bytes(ctx) if err != nil { return err } shouldUpload, err := sign.ShouldUploadToTlog(ctx, c.KeyOpts, nil, c.TlogUpload) if err != nil { return fmt.Errorf("upload to tlog: %w", err) } signedPayload := cosign.LocalSignedPayload{} if shouldUpload { rekorClient, err := rekor.NewClient(c.RekorURL) if err != nil { return err } if c.RekorEntryType == "intoto" { rekorEntry, err = cosign.TLogUploadInTotoAttestation(ctx, rekorClient, sig, signer) } else { rekorEntry, err = cosign.TLogUploadDSSEEnvelope(ctx, rekorClient, sig, signer) } if err != nil { return err } fmt.Fprintln(os.Stderr, "tlog entry created with index:", *rekorEntry.LogIndex) signedPayload.Bundle = cbundle.EntryToBundle(rekorEntry) } if c.BundlePath != "" { var contents []byte if c.NewBundleFormat { contents, err = makeNewBundle(sv, rekorEntry, payload, sig, signer, timestampBytes) if err != nil { return err } } else { signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig) signedPayload.Cert = base64.StdEncoding.EncodeToString(signer) contents, err = json.Marshal(signedPayload) if err != nil { return err } } if err := os.WriteFile(c.BundlePath, contents, 0600); err != nil { return fmt.Errorf("create bundle file: %w", err) } fmt.Fprintln(os.Stderr, "Bundle wrote in the file ", c.BundlePath) } if c.OutputSignature != "" { if err := os.WriteFile(c.OutputSignature, sig, 0600); err != nil { return fmt.Errorf("create signature file: %w", err) } fmt.Fprintf(os.Stderr, "Signature written in %s\n", c.OutputSignature) } else { fmt.Fprintln(os.Stdout, string(sig)) } if c.OutputAttestation != "" { if err := os.WriteFile(c.OutputAttestation, payload, 0600); err != nil { return fmt.Errorf("create signature file: %w", err) } fmt.Fprintf(os.Stderr, "Attestation written in %s\n", c.OutputAttestation) } if c.OutputCertificate != "" { signer, err := sv.Bytes(ctx) if err != nil { return fmt.Errorf("error getting signer: %w", err) } cert, err := cryptoutils.UnmarshalCertificatesFromPEM(signer) // signer is a certificate if err != nil { fmt.Fprintln(os.Stderr, "Could not output signer certificate. Was a certificate used? ", err) return nil } if len(cert) != 1 { fmt.Fprintln(os.Stderr, "Could not output signer certificate. Expected a single certificate") return nil } bts := signer if err := os.WriteFile(c.OutputCertificate, bts, 0600); err != nil { return fmt.Errorf("create certificate file: %w", err) } fmt.Fprintln(os.Stderr, "Certificate written to file ", c.OutputCertificate) } return nil } func makeNewBundle(sv *sign.SignerVerifier, rekorEntry *models.LogEntryAnon, payload, sig, signer, timestampBytes []byte) ([]byte, error) { // Determine if signature is certificate or not var hint string var rawCert []byte cert, err := cryptoutils.UnmarshalCertificatesFromPEM(signer) if err != nil || len(cert) == 0 { pubKey, err := sv.PublicKey() if err != nil { return nil, err } pkixPubKey, err := x509.MarshalPKIXPublicKey(pubKey) if err != nil { return nil, err } hashedBytes := sha256.Sum256(pkixPubKey) hint = base64.StdEncoding.EncodeToString(hashedBytes[:]) } else { rawCert = cert[0].Raw } bundle, err := cbundle.MakeProtobufBundle(hint, rawCert, rekorEntry, timestampBytes) if err != nil { return nil, err } var envelope dsse.Envelope err = json.Unmarshal(sig, &envelope) if err != nil { return nil, err } if len(envelope.Signatures) == 0 { return nil, fmt.Errorf("no signature in DSSE envelope") } sigBytes, err := base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig) if err != nil { return nil, err } bundle.Content = &protobundle.Bundle_DsseEnvelope{ DsseEnvelope: &protodsse.Envelope{ Payload: payload, PayloadType: envelope.PayloadType, Signatures: []*protodsse.Signature{ { Sig: sigBytes, }, }, }, } contents, err := protojson.Marshal(bundle) if err != nil { return nil, err } return contents, nil } cosign-2.5.0/cmd/cosign/cli/attest/attest_blob_test.go000066400000000000000000000230011477503325500227770ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attest import ( "bytes" "context" "crypto" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/json" "encoding/pem" "os" "path/filepath" "strings" "testing" "errors" "github.com/in-toto/in-toto-golang/in_toto" ssldsse "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/secure-systems-lab/go-securesystemslib/encrypted" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/test" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" ) // TestAttestBlobCmdLocalKeyAndSk verifies the AttestBlobCmd returns an error // if both a local key path and a sk are specified func TestAttestBlobCmdLocalKeyAndSk(t *testing.T) { ctx := context.Background() for _, ko := range []options.KeyOpts{ // local and sk keys { KeyRef: "testLocalPath", PassFunc: generate.GetPass, Sk: true, }, } { at := AttestBlobCommand{ KeyOpts: ko, } err := at.Exec(ctx, "some/path") if (errors.Is(err, &options.KeyParseError{}) == false) { t.Fatal("expected KeyParseError") } } } func writeFile(t *testing.T, td string, blob string, name string) string { // Write blob to disk blobPath := filepath.Join(td, name) if err := os.WriteFile(blobPath, []byte(blob), 0644); err != nil { t.Fatal(err) } return blobPath } func makeSLSA02PredicateFile(t *testing.T, td string) string { predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` return writeFile(t, td, predicate, "predicate02.json") } func makeSLSA1PredicateFile(t *testing.T, td string) string { predicate := `{ "buildDefinition": {}, "runDetails": {} }` return writeFile(t, td, predicate, "predicate1.json") } // TestAttestBlobCmdWithCert verifies the AttestBlobCmd checks // that the cmd correctly matches the signing key with the cert // provided. func TestAttestBlobCmdLocalKeyAndCert(t *testing.T) { td := t.TempDir() rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) pemChain := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) subCertPem := writeFile(t, td, string(pemChain), "other_cert.pem") pemChain = append(pemChain, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw})...) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) otherRootCert, _, _ := test.GenerateRootCa() pemOtherRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: otherRootCert.Raw}) otherRootPem := writeFile(t, td, string(pemOtherRoot), "other_root_cert.pem") x509Encoded, _ := x509.MarshalPKCS8PrivateKey(privKey) encBytes, _ := encrypted.Encrypt(x509Encoded, nil) keyPem := pem.EncodeToMemory(&pem.Block{ Type: cosign.CosignPrivateKeyPemType, Bytes: encBytes}) keyRef := writeFile(t, td, string(keyPem), "key.pem") certRef := writeFile(t, td, string(pemLeaf), "cert.pem") chainRef := writeFile(t, td, string(pemChain), "chain.pem") blob := writeFile(t, td, "foo", "foo.txt") predicates := map[string]string{} predicates["slsaprovenance"] = makeSLSA02PredicateFile(t, td) predicates["slsaprovenance1"] = makeSLSA1PredicateFile(t, td) for predicateType, predicatePath := range predicates { t.Run(predicateType, func(t *testing.T) { ctx := context.Background() for _, tc := range []struct { name string keyref string certref string certchainref string newBundle bool errString string }{ { name: "no cert", keyref: keyRef, }, { name: "cert matches key", keyref: keyRef, certref: certRef, }, { name: "new bundle generation", keyref: keyRef, certref: certRef, newBundle: true, }, { name: "fail: cert no match key", keyref: keyRef, certref: subCertPem, errString: "public key in certificate does not match the provided public key", }, { name: "cert chain matches key", keyref: keyRef, certref: certRef, certchainref: chainRef, }, { name: "cert chain partial", keyref: keyRef, certref: certRef, certchainref: subCertPem, }, { name: "fail: cert chain bad", keyref: keyRef, certref: certRef, certchainref: otherRootPem, errString: "unable to validate certificate chain", }, } { t.Run(tc.name, func(t *testing.T) { keyOpts := options.KeyOpts{KeyRef: tc.keyref} if tc.newBundle { keyOpts.NewBundleFormat = true } at := AttestBlobCommand{ KeyOpts: keyOpts, CertPath: tc.certref, CertChainPath: tc.certchainref, PredicatePath: predicatePath, PredicateType: predicateType, RekorEntryType: "dsse", } err := at.Exec(ctx, blob) if err != nil { if tc.errString == "" { t.Fatalf("unexpected error %v", err) } if !strings.Contains(err.Error(), tc.errString) { t.Fatalf("expected error %v got %v", tc.errString, err) } return } if tc.errString != "" { t.Fatalf("expected error %v", tc.errString) } }) } }) } } // TestAttestBlob tests the main functionality -- does the command produce // a validly signed DSSE envelope? (Using an on disk key) func TestAttestBlob(t *testing.T) { ctx := context.Background() td := t.TempDir() keys, _ := cosign.GenerateKeyPair(nil) keyRef := writeFile(t, td, string(keys.PrivateBytes), "key.pem") pubKeyRef := writeFile(t, td, string(keys.PublicBytes), "key.pub") blob := []byte("foo") blobPath := writeFile(t, td, string(blob), "foo.txt") digest, _, _ := signature.ComputeDigestForSigning(bytes.NewReader(blob), crypto.SHA256, []crypto.Hash{crypto.SHA256, crypto.SHA384}) blobDigest := strings.ToLower(hex.EncodeToString(digest)) predicates := map[string]string{} predicates["slsaprovenance"] = makeSLSA02PredicateFile(t, td) predicates["slsaprovenance1"] = makeSLSA1PredicateFile(t, td) for predicateType, predicatePath := range predicates { t.Run(predicateType, func(t *testing.T) { dssePath := filepath.Join(td, "dsse.intoto.jsonl") at := AttestBlobCommand{ KeyOpts: options.KeyOpts{KeyRef: keyRef}, PredicatePath: predicatePath, PredicateType: predicateType, OutputSignature: dssePath, RekorEntryType: "dsse", } err := at.Exec(ctx, blobPath) if err != nil { t.Fatal(err) } // Load the attestation. dsseBytes, _ := os.ReadFile(dssePath) env := &ssldsse.Envelope{} if err := json.Unmarshal(dsseBytes, env); err != nil { t.Fatal(err) } if len(env.Signatures) != 1 { t.Fatalf("expected 1 signature, got %d", len(env.Signatures)) } // Verify the subject digest decodedPredicate, err := base64.StdEncoding.DecodeString(env.Payload) if err != nil { t.Fatalf("decoding dsse payload: %v", err) } var statement in_toto.Statement if err := json.Unmarshal(decodedPredicate, &statement); err != nil { t.Fatalf("decoding predicate: %v", err) } if statement.Subject == nil || len(statement.Subject) != 1 { t.Fatalf("expected one subject in intoto statement") } if statement.Subject[0].Digest["sha256"] != blobDigest { t.Fatalf("expected matching digest") } if statement.PredicateType != options.PredicateTypeMap[predicateType] { t.Fatalf("expected matching predicate type") } // Load a verifier and DSSE verify verifier, _ := signature.LoadVerifierFromPEMFile(pubKeyRef, crypto.SHA256) dssev, err := ssldsse.NewEnvelopeVerifier(&dsse.VerifierAdapter{SignatureVerifier: verifier}) if err != nil { t.Fatalf("new envelope verifier: %v", err) } if _, err := dssev.Verify(ctx, env); err != nil { t.Fatalf("dsse verify: %v", err) } }) } } func TestBadRekorEntryType(t *testing.T) { ctx := context.Background() td := t.TempDir() keys, _ := cosign.GenerateKeyPair(nil) keyRef := writeFile(t, td, string(keys.PrivateBytes), "key.pem") blob := []byte("foo") blobPath := writeFile(t, td, string(blob), "foo.txt") predicates := map[string]string{} predicates["slsaprovenance"] = makeSLSA02PredicateFile(t, td) predicates["slsaprovenance1"] = makeSLSA1PredicateFile(t, td) for predicateType, predicatePath := range predicates { t.Run(predicateType, func(t *testing.T) { dssePath := filepath.Join(td, "dsse.intoto.jsonl") at := AttestBlobCommand{ KeyOpts: options.KeyOpts{KeyRef: keyRef}, PredicatePath: predicatePath, PredicateType: predicateType, OutputSignature: dssePath, RekorEntryType: "badvalue", } err := at.Exec(ctx, blobPath) if err == nil || err.Error() != "unknown value for rekor-entry-type" { t.Fatal("expected an error due to unknown rekor entry type") } }) } } cosign-2.5.0/cmd/cosign/cli/attest/common.go000066400000000000000000000026201477503325500207320ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attest import ( "encoding/base64" "encoding/json" "fmt" "io" "os" "github.com/secure-systems-lab/go-securesystemslib/dsse" ) func predicateReader(predicatePath string) (io.ReadCloser, error) { if predicatePath == "-" { fmt.Fprintln(os.Stderr, "Using payload from: standard input") return os.Stdin, nil } fmt.Fprintln(os.Stderr, "Using payload from:", predicatePath) f, err := os.Open(predicatePath) if err != nil { return nil, err } return f, nil } func getEnvelopeSigBytes(envelopeBytes []byte) ([]byte, error) { var envelope dsse.Envelope err := json.Unmarshal(envelopeBytes, &envelope) if err != nil { return nil, err } if len(envelope.Signatures) == 0 { return nil, fmt.Errorf("envelope has no signatures") } return base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig) } cosign-2.5.0/cmd/cosign/cli/attest/common_test.go000066400000000000000000000031451477503325500217740ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attest import ( "os" "path" "testing" "github.com/stretchr/testify/require" ) func TestPredicateReader(t *testing.T) { cases := []struct { name string path string wantErr bool wantStdin bool createFile bool }{ { name: "standard input", path: "-", wantStdin: true, }, { name: "regular file", path: "payload.json", createFile: true, }, { name: "missing file", path: "payload.json", wantErr: true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { pf := tc.path if tc.createFile { pf = path.Join(t.TempDir(), tc.path) err := os.WriteFile(pf, []byte("payload"), 0644) require.NoError(t, err) } got, err := predicateReader(pf) if err == nil { defer got.Close() } if tc.wantErr { require.Error(t, err) return } require.NoError(t, err) if tc.wantStdin { require.Same(t, os.Stdin, got) } else { require.NotSame(t, os.Stdin, got) } }) } } cosign-2.5.0/cmd/cosign/cli/attest_blob.go000066400000000000000000000077721477503325500204550ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/attest" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/spf13/cobra" ) func AttestBlob() *cobra.Command { o := &options.AttestBlobOptions{} cmd := &cobra.Command{ Use: "attest-blob", Short: "Attest the supplied blob.", Example: ` cosign attest-blob --key | [--predicate ] [--a key=value] [--f] [--r] # attach an attestation to a blob with a local key pair file and print the attestation cosign attest-blob --predicate --type --key cosign.key --output-attestation # attach an attestation to a blob with a key pair stored in Azure Key Vault cosign attest-blob --predicate --type --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # attach an attestation to a blob with a key pair stored in AWS KMS cosign attest-blob --predicate --type --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # attach an attestation to a blob with a key pair stored in Google Cloud KMS cosign attest-blob --predicate --type --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY]/versions/[VERSION] # attach an attestation to a blob with a key pair stored in Hashicorp Vault cosign attest-blob --predicate --type --key hashivault://[KEY] # supply attestation via stdin echo | cosign attest-blob --predicate - --yes`, Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { oidcClientSecret, err := o.OIDC.ClientSecret() if err != nil { return err } ko := options.KeyOpts{ KeyRef: o.Key, PassFunc: generate.GetPass, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, FulcioURL: o.Fulcio.URL, IDToken: o.Fulcio.IdentityToken, FulcioAuthFlow: o.Fulcio.AuthFlow, InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, RekorURL: o.Rekor.URL, OIDCIssuer: o.OIDC.Issuer, OIDCClientID: o.OIDC.ClientID, OIDCClientSecret: oidcClientSecret, OIDCRedirectURL: o.OIDC.RedirectURL, OIDCProvider: o.OIDC.Provider, SkipConfirmation: o.SkipConfirmation, TSAClientCACert: o.TSAClientCACert, TSAClientKey: o.TSAClientKey, TSAClientCert: o.TSAClientCert, TSAServerName: o.TSAServerName, TSAServerURL: o.TSAServerURL, RFC3161TimestampPath: o.RFC3161TimestampPath, BundlePath: o.BundlePath, NewBundleFormat: o.NewBundleFormat, } v := attest.AttestBlobCommand{ KeyOpts: ko, CertPath: o.Cert, CertChainPath: o.CertChain, ArtifactHash: o.Hash, TlogUpload: o.TlogUpload, PredicateType: o.Predicate.Type, PredicatePath: o.Predicate.Path, OutputSignature: o.OutputSignature, OutputAttestation: o.OutputAttestation, OutputCertificate: o.OutputCertificate, Timeout: ro.Timeout, RekorEntryType: o.RekorEntryType, } return v.Exec(cmd.Context(), args[0]) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/bundle.go000066400000000000000000000037131477503325500174130ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "context" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/bundle" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func Bundle() *cobra.Command { cmd := &cobra.Command{ Use: "bundle", Short: "Interact with a Sigstore protobuf bundle", Long: "Tools for interacting with a Sigstore protobuf bundle", } cmd.AddCommand(bundleCreate()) return cmd } func bundleCreate() *cobra.Command { o := &options.BundleCreateOptions{} cmd := &cobra.Command{ Use: "create", Short: "Create a Sigstore protobuf bundle", Long: "Create a Sigstore protobuf bundle by supplying signed material", RunE: func(cmd *cobra.Command, _ []string) error { bundleCreateCmd := &bundle.CreateCmd{ Artifact: o.Artifact, AttestationPath: o.AttestationPath, BundlePath: o.BundlePath, CertificatePath: o.CertificatePath, IgnoreTlog: o.IgnoreTlog, KeyRef: o.KeyRef, Out: o.Out, RekorURL: o.RekorURL, RFC3161TimestampPath: o.RFC3161TimestampPath, SignaturePath: o.SignaturePath, Sk: o.Sk, Slot: o.Slot, } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) defer cancel() return bundleCreateCmd.Exec(ctx) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/bundle/000077500000000000000000000000001477503325500170605ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/bundle/bundle.go000066400000000000000000000122611477503325500206620ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bundle import ( "context" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "os" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" sigs "github.com/sigstore/cosign/v2/pkg/signature" ) type CreateCmd struct { Artifact string AttestationPath string BundlePath string CertificatePath string IgnoreTlog bool KeyRef string Out string RekorURL string RFC3161TimestampPath string SignaturePath string Sk bool Slot string } func (c *CreateCmd) Exec(ctx context.Context) (err error) { if c.Artifact == "" { return fmt.Errorf("must supply --artifact") } // We require some signature if options.NOf(c.BundlePath, c.SignaturePath) == 0 { return fmt.Errorf("must at least supply signature via --bundle or --signature") } var cert *x509.Certificate var envelope dsse.Envelope var rekorClient *client.Rekor var sigBytes, signedTimestamp []byte var sigVerifier signature.Verifier if c.BundlePath != "" { b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath) if err != nil { return err } if b.Cert != "" { certPEM, err := base64.StdEncoding.DecodeString(b.Cert) if err != nil { return err } certs, err := cryptoutils.UnmarshalCertificatesFromPEM(certPEM) if err != nil { return err } if len(certs) == 0 { return fmt.Errorf("no certs found in bundle") } cert = certs[0] } if b.Base64Signature != "" { // Could be a DSSE envelope or plain signature signature, err := base64.StdEncoding.DecodeString(b.Base64Signature) if err != nil { return err } // See if DSSE JSON unmashalling succeeds err = json.Unmarshal(signature, &envelope) if err != nil { // Guess that it is a plain signature sigBytes = signature } } } if c.SignaturePath != "" { signatureB64, err := os.ReadFile(c.SignaturePath) if err != nil { return err } sigBytes, err = base64.StdEncoding.DecodeString(string(signatureB64)) if err != nil { return err } } if c.RFC3161TimestampPath != "" { timestampBytes, err := os.ReadFile(c.RFC3161TimestampPath) if err != nil { return err } var rfc3161Timestamp bundle.RFC3161Timestamp err = json.Unmarshal(timestampBytes, &rfc3161Timestamp) if err != nil { return err } signedTimestamp = rfc3161Timestamp.SignedRFC3161Timestamp } if c.CertificatePath != "" { certBytes, err := os.ReadFile(c.CertificatePath) if err != nil { return err } certDecoded, err := base64.StdEncoding.DecodeString(string(certBytes)) if err != nil { return err } block, _ := pem.Decode(certDecoded) if block == nil { return fmt.Errorf("unable to decode provided certificate") } cert, err = x509.ParseCertificate(block.Bytes) if err != nil { return err } } if c.AttestationPath != "" { attestationBytes, err := os.ReadFile(c.AttestationPath) if err != nil { return err } err = json.Unmarshal(attestationBytes, &envelope) if err != nil { return err } } if c.KeyRef != "" { sigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef) if err != nil { return fmt.Errorf("loading public key: %w", err) } pkcs11Key, ok := sigVerifier.(*pkcs11key.Key) if ok { defer pkcs11Key.Close() } } else if c.Sk { sk, err := pivkey.GetKeyWithSlot(c.Slot) if err != nil { return fmt.Errorf("opening piv token: %w", err) } defer sk.Close() sigVerifier, err = sk.Verifier() if err != nil { return fmt.Errorf("loading public key from token: %w", err) } } if c.RekorURL != "" { rekorClient, err = rekor.NewClient(c.RekorURL) if err != nil { return err } } bundle, err := verify.AssembleNewBundle(ctx, sigBytes, signedTimestamp, &envelope, c.Artifact, cert, c.IgnoreTlog, sigVerifier, nil, rekorClient) if err != nil { return err } bundleBytes, err := bundle.MarshalJSON() if err != nil { return err } if c.Out != "" { err = os.WriteFile(c.Out, bundleBytes, 0600) if err != nil { return err } } else { fmt.Println(string(bundleBytes)) } return nil } cosign-2.5.0/cmd/cosign/cli/bundle/bundle_test.go000066400000000000000000000076441477503325500217320ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bundle import ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "os" "path/filepath" "testing" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/test" sgBundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore/pkg/cryptoutils" ) func TestCreateCmd(t *testing.T) { ctx := context.Background() artifact := "hello world" digest := sha256.Sum256([]byte(artifact)) td := t.TempDir() artifactPath := filepath.Join(td, "artifact") err := os.WriteFile(artifactPath, []byte(artifact), 0600) checkErr(t, err) // Test signing with a key privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) checkErr(t, err) sigBytes, err := privateKey.Sign(rand.Reader, digest[:], crypto.SHA256) checkErr(t, err) signature := base64.StdEncoding.EncodeToString(sigBytes) sigPath := filepath.Join(td, "sig") err = os.WriteFile(sigPath, []byte(signature), 0600) checkErr(t, err) publicKeyPath := filepath.Join(td, "key.pub") pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) checkErr(t, err) pemBlock := &pem.Block{ Type: "PUBLIC KEY", Bytes: pubKeyBytes, } err = os.WriteFile(publicKeyPath, pem.EncodeToMemory(pemBlock), 0600) checkErr(t, err) outPath := filepath.Join(td, "bundle.sigstore.json") bundleCreate := CreateCmd{ Artifact: artifactPath, KeyRef: publicKeyPath, IgnoreTlog: true, Out: outPath, SignaturePath: sigPath, } err = bundleCreate.Exec(ctx) checkErr(t, err) b, err := sgBundle.LoadJSONFromPath(outPath) checkErr(t, err) if b.VerificationMaterial == nil { t.Fatal("bundle does not have verification material") } if b.VerificationMaterial.GetPublicKey() == nil { t.Fatal("bundle verification material does not have public key") } if b.GetMessageSignature() == nil { t.Fatal("bundle does not have message signature") } // Test using an identity certificate in an old bundle format rootCert, rootKey, _ := test.GenerateRootCa() leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) sigBytes, err = privKey.Sign(rand.Reader, digest[:], crypto.SHA256) checkErr(t, err) signedPayload := cosign.LocalSignedPayload{} signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sigBytes) certBytes, err := cryptoutils.MarshalCertificateToPEM(leafCert) checkErr(t, err) signedPayload.Cert = base64.StdEncoding.EncodeToString(certBytes) bundleContents, err := json.Marshal(signedPayload) checkErr(t, err) bundlePath := filepath.Join(td, "old-bundle.json") err = os.WriteFile(bundlePath, bundleContents, 0600) checkErr(t, err) bundleCreate = CreateCmd{ Artifact: artifactPath, BundlePath: bundlePath, IgnoreTlog: true, Out: outPath, } err = bundleCreate.Exec(ctx) checkErr(t, err) b, err = sgBundle.LoadJSONFromPath(outPath) checkErr(t, err) if b.VerificationMaterial == nil { t.Fatal("bundle does not have verification material") } if b.VerificationMaterial.GetCertificate() == nil { t.Fatal("bundle verification material does not have certificate") } if b.GetMessageSignature() == nil { t.Fatal("bundle does not have message signature") } } func checkErr(t *testing.T, err error) { if err != nil { t.Fatal(err) } } cosign-2.5.0/cmd/cosign/cli/clean.go000066400000000000000000000107001477503325500172160ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "context" "errors" "fmt" "net/http" "os" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/internal/ui" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) func Clean() *cobra.Command { c := &options.CleanOptions{} cmd := &cobra.Command{ Use: "clean", Short: "Remove all signatures from an image.", Example: " cosign clean ", Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return CleanCmd(cmd.Context(), c.Registry, c.CleanType, args[0], c.Force) }, } c.AddFlags(cmd) return cmd } func CleanCmd(ctx context.Context, regOpts options.RegistryOptions, cleanType options.CleanType, imageRef string, force bool) error { if !force { ui.Warnf(ctx, prompt(cleanType)) if err := ui.ConfirmContinue(ctx); err != nil { return err } } ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } remoteOpts := regOpts.GetRegistryClientOpts(ctx) sigRef, err := ociremote.SignatureTag(ref, ociremote.WithRemoteOptions(remoteOpts...)) if err != nil { return err } attRef, err := ociremote.AttestationTag(ref, ociremote.WithRemoteOptions(remoteOpts...)) if err != nil { return err } sbomRef, err := ociremote.SBOMTag(ref, ociremote.WithRemoteOptions(remoteOpts...)) if err != nil { return err } var cleanTags []name.Tag switch cleanType { case options.CleanTypeSignature: cleanTags = []name.Tag{sigRef} case options.CleanTypeSbom: cleanTags = []name.Tag{sbomRef} case options.CleanTypeAttestation: cleanTags = []name.Tag{attRef} case options.CleanTypeAll: cleanTags = []name.Tag{sigRef, attRef, sbomRef} default: panic("invalid CleanType value") } for _, t := range cleanTags { if err := remote.Delete(t, remoteOpts...); err != nil { var te *transport.Error switch { case errors.As(err, &te) && te.StatusCode == http.StatusNotFound: // If the tag doesn't exist, some registries may // respond with a 404, which shouldn't be considered an // error. case errors.As(err, &te) && te.StatusCode == http.StatusBadRequest: // Docker registry >=v2.3 requires does not allow deleting the OCI object name directly, must use the digest instead. // See https://github.com/distribution/distribution/blob/main/docs/content/spec/api.md#deleting-an-image if err := deleteByDigest(t, remoteOpts...); err != nil { if errors.As(err, &te) && te.StatusCode == http.StatusNotFound { //nolint: revive } else { fmt.Fprintf(os.Stderr, "could not delete %s by digest from %s:\n%v\n", t, imageRef, err) } } else { fmt.Fprintf(os.Stderr, "Removed %s from %s\n", t, imageRef) } default: fmt.Fprintf(os.Stderr, "could not delete %s from %s:\n%v\n", t, imageRef, err) } } else { fmt.Fprintf(os.Stderr, "Removed %s from %s\n", t, imageRef) } } return nil } func deleteByDigest(tag name.Tag, opts ...remote.Option) error { digestTag, err := ociremote.DockerContentDigest(tag, ociremote.WithRemoteOptions(opts...)) if err != nil { return err } return remote.Delete(digestTag, opts...) } func prompt(cleanType options.CleanType) string { switch cleanType { case options.CleanTypeSignature: return "this will remove all signatures from the image" case options.CleanTypeSbom: return "this will remove all SBOMs from the image" case options.CleanTypeAttestation: return "this will remove all attestations from the image" case options.CleanTypeAll: return "this will remove all signatures, SBOMs and attestations from the image" } panic("invalid CleanType value") } cosign-2.5.0/cmd/cosign/cli/commands.go000066400000000000000000000067701477503325500177510ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "os" "github.com/google/go-containerregistry/pkg/logs" "github.com/spf13/cobra" "github.com/spf13/pflag" "sigs.k8s.io/release-utils/version" cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/templates" cobracompletefig "github.com/withfig/autocomplete-tools/integrations/cobra" ) var ( ro = &options.RootOptions{} ) func normalizeCertificateFlags(_ *pflag.FlagSet, name string) pflag.NormalizedName { switch name { case "cert": name = "certificate" case "cert-email": name = "certificate-email" case "cert-chain": name = "certificate-chain" case "cert-oidc-issuer": name = "certificate-oidc-issuer" case "output-cert": name = "output-certificate" case "cert-identity": name = "certificate-identity" } return pflag.NormalizedName(name) } func New() *cobra.Command { var ( out, stdout *os.File ) cmd := &cobra.Command{ Use: "cosign", Short: "A tool for Container Signing, Verification and Storage in an OCI registry.", DisableAutoGenTag: true, SilenceUsage: true, // Don't show usage on errors PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { if ro.OutputFile != "" { var err error out, err = os.Create(ro.OutputFile) if err != nil { return fmt.Errorf("error creating output file %s: %w", ro.OutputFile, err) } stdout = os.Stdout os.Stdout = out // TODO: don't do this. cmd.SetOut(out) } if ro.Verbose { logs.Debug.SetOutput(os.Stderr) } return nil }, PersistentPostRun: func(_ *cobra.Command, _ []string) { if out != nil { _ = out.Close() } os.Stdout = stdout }, } ro.AddFlags(cmd) templates.SetCustomUsageFunc(cmd) // Add sub-commands. cmd.AddCommand(Attach()) cmd.AddCommand(Attest()) cmd.AddCommand(AttestBlob()) cmd.AddCommand(Bundle()) cmd.AddCommand(Clean()) cmd.AddCommand(Debug()) cmd.AddCommand(Tree()) cmd.AddCommand(Completion()) cmd.AddCommand(Copy()) cmd.AddCommand(Dockerfile()) cmd.AddCommand(Download()) cmd.AddCommand(Generate()) cmd.AddCommand(GenerateKeyPair()) cmd.AddCommand(ImportKeyPair()) cmd.AddCommand(Initialize()) cmd.AddCommand(Load()) cmd.AddCommand(Manifest()) cmd.AddCommand(PIVTool()) cmd.AddCommand(PKCS11Tool()) cmd.AddCommand(PublicKey()) cmd.AddCommand(Save()) cmd.AddCommand(Sign()) cmd.AddCommand(SignBlob()) cmd.AddCommand(Upload()) cmd.AddCommand(Verify()) cmd.AddCommand(VerifyAttestation()) cmd.AddCommand(VerifyBlob()) cmd.AddCommand(VerifyBlobAttestation()) cmd.AddCommand(Triangulate()) cmd.AddCommand(TrustedRoot()) cmd.AddCommand(Env()) cmd.AddCommand(version.WithFont("starwars")) cmd.AddCommand(cranecmd.NewCmdAuthLogin("cosign")) cmd.SetGlobalNormalizationFunc(normalizeCertificateFlags) cmd.AddCommand(cobracompletefig.CreateCompletionSpecCommand()) return cmd } cosign-2.5.0/cmd/cosign/cli/completion.go000066400000000000000000000046151477503325500203150ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "os" "github.com/spf13/cobra" ) func Completion() *cobra.Command { completionCmd := &cobra.Command{ Use: "completion [bash|zsh|fish|powershell]", Short: "Generate completion script", Long: `To load completions: Bash: $ source <(cosign completion bash) # To load completions for each session, execute once: # Linux: $ cosign completion bash > /etc/bash_completion.d/cosign # macOS: $ cosign completion bash > /usr/local/etc/bash_completion.d/cosign Zsh: # If shell completion is not already enabled in your environment, # you will need to enable it. You can execute the following once: $ echo "autoload -U compinit; compinit" >> ~/.zshrc # To load completions for each session, execute once: $ cosign completion zsh > "${fpath[1]}/_cosign" # You will need to start a new shell for this setup to take effect. fish: $ cosign completion fish | source # To load completions for each session, execute once: $ cosign completion fish > ~/.config/fish/completions/cosign.fish PowerShell: PS> cosign completion powershell | Out-String | Invoke-Expression # To load completions for every new session, run: PS> cosign completion powershell > cosign.ps1 # and source this file from your PowerShell profile. `, DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Run: func(cmd *cobra.Command, args []string) { switch args[0] { case "bash": _ = cmd.Root().GenBashCompletion(os.Stdout) case "zsh": _ = cmd.Root().GenZshCompletion(os.Stdout) case "fish": _ = cmd.Root().GenFishCompletion(os.Stdout, true) case "powershell": _ = cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) } }, } return completionCmd } cosign-2.5.0/cmd/cosign/cli/copy.go000066400000000000000000000034701477503325500171140ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/copy" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func Copy() *cobra.Command { o := &options.CopyOptions{} cmd := &cobra.Command{ Use: "copy", Short: "Copy the supplied container image and signatures.", Example: ` cosign copy # copy a container image and its signatures cosign copy example.com/src:latest example.com/dest:latest # copy the signatures only cosign copy --only=sig example.com/src example.com/dest # copy the signatures, attestations, sbom only cosign copy --only=sig,att,sbom example.com/src example.com/dest # overwrite destination image and signatures cosign copy -f example.com/src example.com/dest # copy a container image and its signatures for a specific platform cosign copy --platform=linux/amd64 example.com/src:latest example.com/dest:latest`, Args: cobra.ExactArgs(2), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return copy.CopyCmd(cmd.Context(), o.Registry, args[0], args[1], o.SignatureOnly, o.Force, o.CopyOnly, o.Platform) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/copy/000077500000000000000000000000001477503325500165615ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/copy/copy.go000066400000000000000000000132131477503325500200620ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package copy import ( "context" "errors" "fmt" "net/http" "os" "runtime" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci" ociplatform "github.com/sigstore/cosign/v2/pkg/oci/platform" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/walk" "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/util/sets" ) // CopyCmd implements the logic to copy the supplied container image and signatures. // nolint func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstImg string, sigOnly, force bool, copyOnly []string, platform string) error { no := regOpts.NameOptions() srcRef, err := name.ParseReference(srcImg, no...) if err != nil { return err } srcRepoRef := srcRef.Context() dstRef, err := name.ParseReference(dstImg, no...) if err != nil { return err } dstRepoRef := dstRef.Context() ociRemoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return err } remoteOpts := regOpts.GetRegistryClientOpts(ctx) pusher, err := remote.NewPusher(remoteOpts...) if err != nil { return err } ociRemoteOpts = append(ociRemoteOpts, ociremote.WithRemoteOptions(remoteOpts...)) g, gctx := errgroup.WithContext(ctx) g.SetLimit(runtime.GOMAXPROCS(0)) root, err := ociremote.SignedEntity(srcRef, ociRemoteOpts...) if err != nil { return err } root, err = ociplatform.SignedEntityForPlatform(root, platform) if err != nil { return err } onlyFlagSet := false tags, err := parseOnlyOpt(copyOnly, sigOnly) if err != nil { return err } if len(tags) > 0 { onlyFlagSet = true } else { tags = []tagMap{ociremote.SignatureTag, ociremote.AttestationTag, ociremote.SBOMTag} } if err := walk.SignedEntity(gctx, root, func(ctx context.Context, se oci.SignedEntity) error { // Both of the SignedEntity types implement Digest() h, err := se.Digest() if err != nil { return err } srcDigest := srcRepoRef.Digest(h.String()) copyTag := func(tm tagMap) error { src, err := tm(srcDigest, ociRemoteOpts...) if err != nil { return err } dst := dstRepoRef.Tag(src.Identifier()) g.Go(func() error { return remoteCopy(ctx, pusher, src, dst, force, remoteOpts...) }) return nil } for _, tm := range tags { if err := copyTag(tm); err != nil { return err } } // Copy the entity itself. g.Go(func() error { dst := dstRepoRef.Tag(srcDigest.Identifier()) dst = dst.Tag(fmt.Sprint(regOpts.RefOpts.TagPrefix, h.Algorithm, "-", h.Hex)) return remoteCopy(ctx, pusher, srcDigest, dst, force, remoteOpts...) }) return nil }); err != nil { return err } // Wait for everything to be copied over. if err := g.Wait(); err != nil { return err } // If we're only copying sig/att/sbom, we have nothing left to do. if onlyFlagSet { return nil } // Now that everything has been copied over, update the tag. h, err := root.Digest() if err != nil { return err } return remoteCopy(ctx, pusher, srcRepoRef.Digest(h.String()), dstRef, force, remoteOpts...) } func descriptorsEqual(a, b *v1.Descriptor) bool { if a == nil || b == nil { return a == nil && b == nil } return a.Digest == b.Digest } type tagMap func(name.Reference, ...ociremote.Option) (name.Tag, error) func remoteCopy(ctx context.Context, pusher *remote.Pusher, src, dest name.Reference, overwrite bool, opts ...remote.Option) error { got, err := remote.Get(src, opts...) if err != nil { var te *transport.Error if errors.As(err, &te) && te.StatusCode == http.StatusNotFound { // We do not treat 404s on the source image as errors because we are // trying many flavors of tag (sig, sbom, att) and only a subset of // these are likely to exist, especially when we're talking about a // multi-arch image. return nil } return err } if !overwrite { if dstDesc, err := remote.Head(dest, opts...); err == nil { if descriptorsEqual(&got.Descriptor, dstDesc) { return nil } return fmt.Errorf("image %q already exists. Use `-f` to overwrite", dest.Name()) } } fmt.Fprintf(os.Stderr, "Copying %s to %s...\n", src, dest) return pusher.Push(ctx, dest, got) } func parseOnlyOpt(onlyFlag []string, sigOnly bool) ([]tagMap, error) { var tags []tagMap tagSet := sets.New(onlyFlag...) if sigOnly { fmt.Fprintf(os.Stderr, "--sig-only is deprecated, use --only=sig instead") tagSet.Insert("sig") } validTags := []string{"sig", "sbom", "att"} validTagsSet := sets.New(validTags...) for tag := range tagSet { if !validTagsSet.Has(tag) { return nil, fmt.Errorf("invalid value for --only: %s, only the following values are supported: %s", tag, validTags) } } if tagSet.Has("sig") { tags = append(tags, ociremote.SignatureTag) } if tagSet.Has("sbom") { tags = append(tags, ociremote.SBOMTag) } if tagSet.Has("att") { tags = append(tags, ociremote.AttestationTag) } return tags, nil } cosign-2.5.0/cmd/cosign/cli/copy/copy_test.go000066400000000000000000000105021477503325500211170ustar00rootroot00000000000000// Copyright 2023 the Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package copy import ( "context" "reflect" "testing" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) func TestCopyAttachmentTagPrefix(t *testing.T) { ctx := context.Background() refOpts := options.ReferenceOptions{ TagPrefix: "test-tag", } srcImg := "alpine" destImg := "test-alpine" err := CopyCmd(ctx, options.RegistryOptions{ RefOpts: refOpts, }, srcImg, destImg, false, true, []string{}, "") if err == nil { t.Fatal("failed to copy with attachment-tag-prefix") } } func TestCopyPlatformOpt(t *testing.T) { ctx := context.Background() srcImg := "alpine" destImg := "test-alpine" err := CopyCmd(ctx, options.RegistryOptions{}, srcImg, destImg, false, true, []string{}, "linux/amd64") if err == nil { t.Fatal("failed to copy with platform") } } func TestParseOnlyOpt(t *testing.T) { tests := []struct { only []string sigOnly bool expectErr bool expectTagMap []tagMap }{ { only: []string{"bogus"}, sigOnly: true, expectErr: true, }, { only: []string{}, sigOnly: true, expectErr: false, expectTagMap: []tagMap{ociremote.SignatureTag}, }, { only: []string{"sig"}, sigOnly: false, expectErr: false, expectTagMap: []tagMap{ociremote.SignatureTag}, }, { only: []string{"sig"}, sigOnly: true, expectErr: false, expectTagMap: []tagMap{ociremote.SignatureTag}, }, { only: []string{"sbom"}, sigOnly: true, expectErr: false, expectTagMap: []tagMap{ociremote.SBOMTag, ociremote.SignatureTag}, }, { only: []string{"att"}, sigOnly: true, expectErr: false, expectTagMap: []tagMap{ociremote.AttestationTag, ociremote.SignatureTag}, }, { only: []string{"sbom"}, sigOnly: false, expectErr: false, expectTagMap: []tagMap{ociremote.SBOMTag}, }, { only: []string{"att"}, sigOnly: false, expectErr: false, expectTagMap: []tagMap{ociremote.AttestationTag}, }, { only: []string{"att", "sbom"}, sigOnly: false, expectErr: false, expectTagMap: []tagMap{ociremote.AttestationTag, ociremote.SBOMTag}, }, { only: []string{"sig", "sbom"}, sigOnly: false, expectErr: false, expectTagMap: []tagMap{ociremote.SignatureTag, ociremote.SBOMTag}, }, { only: []string{"sig", "att"}, sigOnly: false, expectErr: false, expectTagMap: []tagMap{ociremote.SignatureTag, ociremote.AttestationTag}, }, { only: []string{"sig", "att", "sbom"}, sigOnly: false, expectErr: false, expectTagMap: []tagMap{ociremote.SignatureTag, ociremote.AttestationTag, ociremote.SBOMTag}, }, { only: []string{"sig", "att", "sbom", "bad"}, sigOnly: false, expectErr: true, }, } for _, test := range tests { result, err := parseOnlyOpt(test.only, test.sigOnly) if (err != nil) != test.expectErr { t.Errorf("unexpected failure from parseOnlyOpt: expectErr=%v, err = %v", test.expectErr, err) } else if !compareTagMaps(result, test.expectTagMap) { t.Errorf("result tag map did not match expected value: result: %v expected: %v", result, test.expectTagMap) } } } func compareTagMaps(slice1, slice2 []tagMap) bool { if len(slice1) != len(slice2) { return false // Different lengths can't be equal } for _, fn1 := range slice1 { found := false for _, fn2 := range slice2 { if reflect.DeepEqual(reflect.ValueOf(fn1), reflect.ValueOf(fn2)) { found = true break // Found a match, move to the next fn1 } } if !found { return false // fn1 not found in slice2 } } return true // All functions in slice1 found in slice2 } cosign-2.5.0/cmd/cosign/cli/debug.go000066400000000000000000000022541477503325500172270ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/debug" "github.com/spf13/cobra" ) func Debug() *cobra.Command { cmd := &cobra.Command{ Use: "debug", Hidden: true, RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Help() }, } cmd.AddCommand(debugProviders()) return cmd } func debugProviders() *cobra.Command { cmd := &cobra.Command{ Use: "providers", Short: "Show enabled/disabled OIDC providers.", RunE: func(cmd *cobra.Command, _ []string) error { return debug.ProviderCmd(cmd.Context(), cmd.OutOrStdout()) }, } return cmd } cosign-2.5.0/cmd/cosign/cli/debug/000077500000000000000000000000001477503325500166755ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/debug/provider.go000066400000000000000000000015571477503325500210660ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package debug import ( "context" "fmt" "io" "github.com/sigstore/cosign/v2/pkg/providers" ) func ProviderCmd(ctx context.Context, w io.Writer) error { for _, p := range providers.Providers() { fmt.Fprintf(w, "%s: %t\n", p.Name, p.Provider.Enabled(ctx)) } return nil } cosign-2.5.0/cmd/cosign/cli/dockerfile.go000066400000000000000000000113221477503325500202440ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "github.com/sigstore/cosign/v2/cmd/cosign/cli/dockerfile" "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func Dockerfile() *cobra.Command { cmd := &cobra.Command{ Use: "dockerfile", Short: "Provides utilities for discovering images in and performing operations on Dockerfiles", } cmd.AddCommand( dockerfileVerify(), ) return cmd } func dockerfileVerify() *cobra.Command { o := &options.VerifyDockerfileOptions{} cmd := &cobra.Command{ Use: "verify", Short: "Verify a signature on the base image specified in the Dockerfile", PersistentPreRun: options.BindViper, Long: `Verify signature and annotations on images in a Dockerfile by checking claims against the transparency log. Shell-like variables in the Dockerfile's FROM lines will be substituted with values from the OS ENV.`, Example: ` cosign dockerfile verify --key || # verify cosign claims and signing certificates on the FROM images in the Dockerfile cosign dockerfile verify # only verify the base image (the last FROM image) cosign dockerfile verify --base-image-only # additionally verify specified annotations cosign dockerfile verify -a key1=val1 -a key2=val2 # verify images with public key cosign dockerfile verify --key cosign.pub # verify images with public key provided by URL cosign dockerfile verify --key https://host.for/ # verify images with public key stored in Azure Key Vault cosign dockerfile verify --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # verify images with public key stored in AWS KMS cosign dockerfile verify --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # verify images with public key stored in Google Cloud KMS cosign dockerfile verify --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # verify images with public key stored in Hashicorp Vault cosign dockerfile verify --key hashivault://[KEY] `, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { annotations, err := o.AnnotationsMap() if err != nil { return err } v := &dockerfile.VerifyDockerfileCommand{ VerifyCommand: verify.VerifyCommand{ RegistryOptions: o.Registry, CertVerifyOptions: o.CertVerify, CheckClaims: o.CheckClaims, KeyRef: o.Key, CertRef: o.CertVerify.Cert, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, CertChain: o.CertVerify.CertChain, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, Output: o.Output, RekorURL: o.Rekor.URL, Attachment: o.Attachment, Annotations: annotations, LocalImage: o.LocalImage, Offline: o.CommonVerifyOptions.Offline, TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath, IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog, MaxWorkers: o.CommonVerifyOptions.MaxWorkers, }, BaseOnly: o.BaseImageOnly, } if o.CommonVerifyOptions.MaxWorkers == 0 { return fmt.Errorf("please set the --max-worker flag to a value that is greater than 0") } return v.Exec(cmd.Context(), args) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/dockerfile/000077500000000000000000000000001477503325500177165ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/dockerfile/verify.go000066400000000000000000000120311477503325500215460ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package dockerfile import ( "bufio" "context" "errors" "flag" "fmt" "io" "os" "strings" "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/sigstore/cosign/v2/internal/ui" ) // VerifyCommand verifies a signature on a supplied container image // nolint type VerifyDockerfileCommand struct { verify.VerifyCommand BaseOnly bool } // Exec runs the verification command func (c *VerifyDockerfileCommand) Exec(ctx context.Context, args []string) error { if len(args) != 1 { return flag.ErrHelp } dockerfile, err := os.Open(args[0]) if err != nil { return fmt.Errorf("could not open Dockerfile: %w", err) } defer dockerfile.Close() fc := newFinderCache() images, err := fc.getImagesFromDockerfile(ctx, dockerfile) if err != nil { return fmt.Errorf("failed extracting images from Dockerfile: %w", err) } if len(images) == 0 { return errors.New("no images found in Dockerfile") } if c.BaseOnly { images = images[len(images)-1:] } fmt.Fprintf(os.Stderr, "Extracted image(s): %s\n", strings.Join(images, ", ")) return c.VerifyCommand.Exec(ctx, images) } type finderCache struct { Env map[string]string Stages []string } func newFinderCache() *finderCache { return &finderCache{ Env: map[string]string{}, Stages: []string{}, } } func (fc *finderCache) isStage(input string) (found bool) { for _, s := range fc.Stages { if s == input { found = true break } } return found } func (fc *finderCache) getImagesFromDockerfile(ctx context.Context, dockerfile io.Reader) ([]string, error) { var images []string fileScanner := bufio.NewScanner(dockerfile) for fileScanner.Scan() { line := strings.TrimSpace(fileScanner.Text()) lineUpper := strings.ToUpper(line) switch { case strings.HasPrefix(lineUpper, "FROM"): switch image := fc.getImageFromLine(line); image { case "scratch": ui.Infof(ctx, "- scratch image ignored") default: images = append(images, image) } case strings.HasPrefix(lineUpper, "COPY"): if image := fc.getImageFromCopyLine(line); image != "" { images = append(images, image) } case strings.HasPrefix(lineUpper, "ENV") || strings.HasPrefix(lineUpper, "ARG"): fc.getEnvAndArgs(line) } } if err := fileScanner.Err(); err != nil { return nil, err } return images, nil } func (fc *finderCache) getImageFromLine(line string) string { line = strings.TrimPrefix(line, "FROM") // Remove "FROM" prefix line = os.Expand(line, func(key string) string { // Substitute templated vars if val, ok := fc.Env[key]; ok { return val } // NOTE not using pkg/cosign/env due to env not relating to cosign //nolint:forbidigo if val, ok := os.LookupEnv(key); ok { return val } return "" }) fields := strings.Fields(line) for i := len(fields) - 1; i > 0; i-- { // Remove the "AS" portion of line if strings.EqualFold(fields[i], "AS") { fc.Stages = append(fc.Stages, fields[i+1]) fields = fields[:i] break } } return fields[len(fields)-1] // The image should be the last portion of the line that remains } func (fc *finderCache) getImageFromCopyLine(line string) string { line = strings.TrimPrefix(line, "COPY") // Remove "COPY" prefix line = os.Expand(line, func(key string) string { // Substitute templated vars if val, ok := fc.Env[key]; ok { return val } // NOTE not using pkg/cosign/env due to env not relating to cosign //nolint:forbidigo if val, ok := os.LookupEnv(key); ok { return val } return "" }) var image string fields := strings.Fields(line) for _, f := range fields { if !strings.Contains(f, "--from=") { continue } split := strings.Split(f, "=") if len(split) < 2 { continue } if fc.isStage(split[1]) { continue } image = split[1] break } return image } func (fc *finderCache) getEnvAndArgs(line string) { line = strings.TrimPrefix(line, "ENV") // Remove "ENV" prefix line = strings.TrimPrefix(line, "ARG") // Remove "ARG" prefix line = os.Expand(line, func(key string) string { // Substitute templated vars if val, ok := fc.Env[key]; ok { return val } // NOTE not using pkg/cosign/env due to env not relating to cosign //nolint:forbidigo if val, ok := os.LookupEnv(key); ok { return val } return "" }) fields := strings.Fields(line) for _, f := range fields { keyvalue := strings.Split(f, "=") if len(keyvalue) < 2 { continue } key := strings.Trim(keyvalue[0], " ") value := strings.Trim(keyvalue[1], " ") fc.Env[key] = value } } cosign-2.5.0/cmd/cosign/cli/dockerfile/verify_test.go000066400000000000000000000113371477503325500226150ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package dockerfile import ( "context" "os" "reflect" "strings" "testing" ) func TestGetImagesFromDockerfile(t *testing.T) { testCases := []struct { name string fileContents string env map[string]string expected []string }{ { name: "plain", fileContents: `FROM gcr.io/test/image`, expected: []string{"gcr.io/test/image"}, }, { name: "tag", fileContents: `FROM gcr.io/test/image:latest`, expected: []string{"gcr.io/test/image:latest"}, }, { name: "tag with as", fileContents: `FROM gcr.io/test/image:1.16.5 as build`, expected: []string{"gcr.io/test/image:1.16.5"}, }, { name: "digest", fileContents: `FROM gcr.io/test/image@sha256:d131624e6f5d8695e9aea7a0439f7bac0fcc50051282e0c3d4d627cab8845ba5`, expected: []string{"gcr.io/test/image@sha256:d131624e6f5d8695e9aea7a0439f7bac0fcc50051282e0c3d4d627cab8845ba5"}, }, { name: "fancy-from", fileContents: `FROM --platform=linux/arm64 gcr.io/fancy/test/image AS fancy`, expected: []string{"gcr.io/fancy/test/image"}, }, { name: "multistage", fileContents: `FROM build_image_1 RUN script1 FROM build_image_2 RUN script2 FROM runtime_image CMD bin`, expected: []string{"build_image_1", "build_image_2", "runtime_image"}, }, { name: "with-arg", fileContents: `FROM gcr.io/${TEST_IMAGE_REPO_PATH}`, env: map[string]string{ "TEST_IMAGE_REPO_PATH": "env/var/test/repo", }, expected: []string{"gcr.io/env/var/test/repo"}, }, { name: "with-value-from-arg", fileContents: `ARG IMAGE=gcr.io/someorg/someimage FROM ${IMAGE}`, expected: []string{"gcr.io/someorg/someimage"}, }, { name: "with-value-from-env", fileContents: `ENV IMAGE=gcr.io/someorg/someimage FROM ${IMAGE}`, expected: []string{"gcr.io/someorg/someimage"}, }, { name: "with-multiple-values-from-env", fileContents: `ENV IMAGE_ONE=gcr.io/someorg/someimage IMAGE_TWO=gcr.io/someorg/coolimage FROM ${IMAGE_ONE} FROM ${IMAGE_TWO}`, expected: []string{"gcr.io/someorg/someimage", "gcr.io/someorg/coolimage"}, }, { name: "with-value-from-arg-from-env", fileContents: `ARG IMAGE=${THING} FROM ${IMAGE}`, expected: []string{"gcr.io/someorg/coolimage"}, env: map[string]string{ "THING": "gcr.io/someorg/coolimage", }, }, { name: "image-in-copy", fileContents: `COPY --from=gcr.io/someorg/someimage /var/www/html /app`, expected: []string{"gcr.io/someorg/someimage"}, }, { name: "image-in-copy-with-env", fileContents: `ENV IMAGE_HERE=gcr.io/someorg/someimage COPY --from=${IMAGE_HERE} /var/www/html /app`, expected: []string{"gcr.io/someorg/someimage"}, }, { name: "copy-dont-include-prepare-stage-as-images", fileContents: `FROM gcr.io/someorg/coolimage AS prepare FROM gcr.io/someorg/someimage AS final COPY --from=prepare /app /app`, expected: []string{"gcr.io/someorg/coolimage", "gcr.io/someorg/someimage"}, }, { name: "gauntlet", fileContents: `FROM gcr.io/${TEST_IMAGE_REPO_PATH}/one AS one RUN script1 FROM gcr.io/$TEST_IMAGE_REPO_PATH/${TEST_SUBREPO}:latest RUN script2 FROM --platform=linux/amd64 gcr.io/${TEST_IMAGE_REPO_PATH}/$TEST_RUNTIME_SUBREPO COPY --from=gcr.io/someorg/someimage /etc/config /app/etc/config CMD bin`, env: map[string]string{ "TEST_IMAGE_REPO_PATH": "gauntlet/test", "TEST_SUBREPO": "two", "TEST_RUNTIME_SUBREPO": "runtime", "SOMETHING_ELSE": "something/else", }, expected: []string{"gcr.io/gauntlet/test/one", "gcr.io/gauntlet/test/two:latest", "gcr.io/gauntlet/test/runtime", "gcr.io/someorg/someimage"}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { for k, v := range tc.env { os.Setenv(k, v) defer os.Unsetenv(k) } fc := newFinderCache() ctx := context.Background() got, err := fc.getImagesFromDockerfile(ctx, strings.NewReader(tc.fileContents)) if err != nil { t.Fatalf("getImagesFromDockerfile returned error: %v", err) } if !reflect.DeepEqual(tc.expected, got) { t.Errorf("getImagesFromDockerfile returned %v, wanted %v", got, tc.expected) } }) } } cosign-2.5.0/cmd/cosign/cli/download.go000066400000000000000000000061231477503325500177470ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "os" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/download" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func Download() *cobra.Command { cmd := &cobra.Command{ Use: "download", Short: "Provides utilities for downloading artifacts and attached artifacts in a registry", } cmd.AddCommand( downloadSignature(), downloadSBOM(), downloadAttestation(), ) return cmd } func downloadSignature() *cobra.Command { o := &options.RegistryOptions{} cmd := &cobra.Command{ Use: "signature", Short: "Download signatures from the supplied container image", Example: " cosign download signature ", Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return download.SignatureCmd(cmd.Context(), *o, args[0]) }, } o.AddFlags(cmd) return cmd } func downloadSBOM() *cobra.Command { o := &options.RegistryOptions{} do := &options.SBOMDownloadOptions{} cmd := &cobra.Command{ Use: "sbom", Short: "DEPRECATED: Download SBOMs from the supplied container image", Long: "Download SBOMs from the supplied container image\n\n" + options.SBOMAttachmentDeprecation, Example: " cosign download sbom ", Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { fmt.Fprintln(os.Stderr, options.SBOMAttachmentDeprecation) fmt.Fprintln(os.Stderr, "WARNING: Downloading SBOMs this way does not ensure its authenticity. If you want to ensure a tamper-proof SBOM, download it using 'cosign download attestation '.") _, err := download.SBOMCmd(cmd.Context(), *o, *do, args[0], cmd.OutOrStdout()) return err }, } do.AddFlags(cmd) o.AddFlags(cmd) return cmd } func downloadAttestation() *cobra.Command { o := &options.RegistryOptions{} ao := &options.AttestationDownloadOptions{} cmd := &cobra.Command{ Use: "attestation", Short: "Download in-toto attestations from the supplied container image", Example: " cosign download attestation [--predicate-type]", Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return download.AttestationCmd(cmd.Context(), *o, *ao, args[0]) }, } o.AddFlags(cmd) ao.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/download/000077500000000000000000000000001477503325500174165ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/download/attestation.go000066400000000000000000000042341477503325500223070ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package download import ( "context" "encoding/json" "errors" "fmt" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci/platform" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOptions options.AttestationDownloadOptions, imageRef string) error { ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return err } var predicateType string if attOptions.PredicateType != "" { predicateType, err = options.ParsePredicateType(attOptions.PredicateType) if err != nil { return err } } se, err := ociremote.SignedEntity(ref, ociremoteOpts...) var entityNotFoundError *ociremote.EntityNotFoundError if err != nil { if errors.As(err, &entityNotFoundError) { if digest, ok := ref.(name.Digest); ok { // We don't need to access the original image to download the attached attestation se = ociremote.SignedUnknown(digest) } else { return err } } else { return err } } se, err = platform.SignedEntityForPlatform(se, attOptions.Platform) if err != nil { return err } attestations, err := cosign.FetchAttestations(se, predicateType) if err != nil { return err } for _, att := range attestations { b, err := json.Marshal(att) if err != nil { return err } fmt.Println(string(b)) } return nil } cosign-2.5.0/cmd/cosign/cli/download/sbom.go000066400000000000000000000055511477503325500207130ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package download import ( "context" "errors" "fmt" "io" "os" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/platform" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) func SBOMCmd( ctx context.Context, regOpts options.RegistryOptions, dnOpts options.SBOMDownloadOptions, imageRef string, out io.Writer, ) ([]string, error) { ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return nil, err } ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return nil, err } se, err := ociremote.SignedEntity(ref, ociremoteOpts...) var entityNotFoundError *ociremote.EntityNotFoundError if err != nil { if errors.As(err, &entityNotFoundError) { // We don't need to access the original image to download the attached sbom if digest, ok := ref.(name.Digest); ok { se = ociremote.SignedUnknown(digest) } else { return nil, err } } else { return nil, err } } se, err = platform.SignedEntityForPlatform(se, dnOpts.Platform) if err != nil { return nil, err } idx, isIndex := se.(oci.SignedImageIndex) file, err := se.Attachment("sbom") if errors.Is(err, ociremote.ErrImageNotFound) { if !isIndex { return nil, errors.New("no sbom attached to reference") } // Help the user with the available architectures pl, err := platform.GetIndexPlatforms(idx) if len(pl) > 0 && err == nil { fmt.Fprintf( os.Stderr, "\nThis multiarch image does not have an SBOM attached at the index level.\n"+ "Try using --platform with one of the following architectures:\n%s\n\n", pl.String(), ) } return nil, fmt.Errorf("no SBOM found attached to image index") } else if err != nil { return nil, fmt.Errorf("getting sbom attachment: %w", err) } // "attach sbom" attaches a single static.NewFile sboms := make([]string, 0, 1) mt, err := file.FileMediaType() if err != nil { return nil, err } fmt.Fprintf(os.Stderr, "Found SBOM of media type: %s\n", mt) sbom, err := file.Payload() if err != nil { return nil, err } sboms = append(sboms, string(sbom)) if _, err := out.Write(sbom); err != nil { return nil, err } return sboms, nil } cosign-2.5.0/cmd/cosign/cli/download/signature.go000066400000000000000000000025141477503325500217500ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package download import ( "context" "encoding/json" "fmt" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" ) func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string) error { ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return err } signatures, err := cosign.FetchSignaturesForReference(ctx, ref, ociremoteOpts...) if err != nil { return err } for _, sig := range signatures { b, err := json.Marshal(sig) if err != nil { return err } fmt.Println(string(b)) } return nil } cosign-2.5.0/cmd/cosign/cli/env.go000066400000000000000000000101241477503325500167240ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "os" "sort" "strings" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) func Env() *cobra.Command { o := &options.EnvOptions{} cmd := &cobra.Command{ Use: "env", Short: "Prints Cosign environment variables", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { envVars := env.EnvironmentVariables() printEnv(envVars, getEnv(), getEnviron(), o.ShowDescriptions, o.ShowSensitiveValues) return nil }, } o.AddFlags(cmd) return cmd } // NB: the purpose of those types and functions is to make it possible to swap function for testing purposes type envGetter func(env.Variable) string type environGetter func() []string func getEnv() envGetter { return env.Getenv } func getEnviron() environGetter { return os.Environ } // NB: printEnv intentionally takes map of env vars to make it easier to unit test it func printEnv(envVars map[env.Variable]env.VariableOpts, envGet envGetter, environGet environGetter, showDescription, showSensitive bool) { // Sort keys to print them in a predictable order keys := sortEnvKeys(envVars) // Print known/registered environment variables for _, e := range keys { opts := envVars[e] // Get value of environment variable val := envGet(e) // If showDescription is set, print description for that variable if showDescription { fmt.Printf("# %s %s\n", e.String(), opts.Description) fmt.Printf("# Expects: %s\n", opts.Expects) } // If variable is sensitive, and we don't want to show sensitive values, // print environment variable name and some asterisk symbols. // If sensitive variable isn't set or doesn't have any value, we'll just // print like non-sensitive variable if opts.Sensitive && !showSensitive && val != "" { fmt.Printf("%s=******\n", e.String()) } else { fmt.Printf("%s=%s\n", e.String(), val) } } // Print not registered environment variables nonRegEnv := map[string]string{} for _, e := range environGet() { // Prefixes to look for. err on the side of showing too much rather // than too little. We'll only output things that have values set. for _, prefix := range []string{ // We want to print eventually non-registered cosign variables (even if this shouldn't happen) "COSIGN_", // Can modify Sigstore/TUF client behavior - https://github.com/sigstore/sigstore/blob/35d6a82c15183f7fe7a07eca45e17e378aa32126/pkg/tuf/client.go#L52 "SIGSTORE_", "TUF_", } { if strings.HasPrefix(e, prefix) { // os.Environ returns key=value pairs, so we split by = envSplit := strings.Split(e, "=") key := envSplit[0] // Skip registered environment variables (those are already printed above) if _, ok := envVars[env.Variable(key)]; ok { continue } val := "" if len(envSplit) == 2 { val = envSplit[1] } nonRegEnv[key] = val } } } if len(nonRegEnv) > 0 && showDescription { fmt.Println("# Environment variables below are not registered with cosign,\n# but might still influence cosign's behavior.") } for key, val := range nonRegEnv { if !showSensitive && val != "" { fmt.Printf("%s=******\n", key) } else { fmt.Printf("%s=%s\n", key, val) } } } func sortEnvKeys(envVars map[env.Variable]env.VariableOpts) []env.Variable { keys := []env.Variable{} for k := range envVars { keys = append(keys, k) } sort.Slice(keys, func(i, j int) bool { return strings.Compare(keys[i].String(), keys[j].String()) < 0 }) return keys } cosign-2.5.0/cmd/cosign/cli/env_test.go000066400000000000000000000176621477503325500200010ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "io" "os" "testing" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) const ( VariableTest1 env.Variable = "COSIGN_TEST1" VariableTest2 env.Variable = "COSIGN_TEST2" VariableTest3 env.Variable = "COSIGN_TEST3" expectedWithoutDescription = `COSIGN_TEST1=abcd COSIGN_TEST2= ` expectedWithDescription = `# COSIGN_TEST1 is the first test variable # Expects: test1 value COSIGN_TEST1=abcd # COSIGN_TEST2 is the second test variable # Expects: test2 value COSIGN_TEST2= ` expectedWithHiddenSensitive = `# COSIGN_TEST1 is the first test variable # Expects: test1 value COSIGN_TEST1=abcd # COSIGN_TEST2 is the second test variable # Expects: test2 value COSIGN_TEST2=****** ` expectedWithSensitive = `# COSIGN_TEST1 is the first test variable # Expects: test1 value COSIGN_TEST1=abcd # COSIGN_TEST2 is the second test variable # Expects: test2 value COSIGN_TEST2=1234 ` expectedSensitiveWithoutDescription = `COSIGN_TEST1=abcd COSIGN_TEST2=1234 ` expectedWithNonRegisteredEnv = `# COSIGN_TEST1 is the first test variable # Expects: test1 value COSIGN_TEST1=abcd # COSIGN_TEST2 is the second test variable # Expects: test2 value COSIGN_TEST2= # Environment variables below are not registered with cosign, # but might still influence cosign's behavior. COSIGN_TEST3=****** ` expectedWithNonRegisteredEnvSensitive = `# COSIGN_TEST1 is the first test variable # Expects: test1 value COSIGN_TEST1=abcd # COSIGN_TEST2 is the second test variable # Expects: test2 value COSIGN_TEST2= # Environment variables below are not registered with cosign, # but might still influence cosign's behavior. COSIGN_TEST3=abcd ` expectedWithNonRegisteredEnvNoDesc = `COSIGN_TEST1=abcd COSIGN_TEST2= COSIGN_TEST3=****** ` expectedWithNonRegisteredEnvNoDescSensitive = `COSIGN_TEST1=abcd COSIGN_TEST2= COSIGN_TEST3=abcd ` expectedWithNonRegisteredEnvNoDescEmpty = `COSIGN_TEST1=abcd COSIGN_TEST2= COSIGN_TEST3= ` ) var ( testingEnvVars = map[string]string{} ) func tGetEnv() envGetter { return func(key env.Variable) string { return testingEnvVars[key.String()] } } func tGetEnviron() environGetter { return func() []string { var s []string for k, v := range testingEnvVars { s = append(s, fmt.Sprintf("%s=%s", k, v)) } return s } } func TestPrintEnv(t *testing.T) { variables := map[env.Variable]env.VariableOpts{ VariableTest1: { Description: "is the first test variable", Expects: "test1 value", Sensitive: false, }, VariableTest2: { Description: "is the second test variable", Expects: "test2 value", Sensitive: true, }, } tests := []struct { name string environmentVariables map[string]string registeredVariables map[env.Variable]env.VariableOpts showDescriptions bool showSensitiveValues bool expectedOutput string }{ { name: "no descriptions and sensitive variables", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", }, registeredVariables: variables, showDescriptions: false, showSensitiveValues: false, expectedOutput: expectedWithoutDescription, }, { name: "descriptions but sensitive variable is unset", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", }, registeredVariables: variables, showDescriptions: true, showSensitiveValues: false, expectedOutput: expectedWithDescription, }, { name: "sensitive variable is non-empty but show sensitive variables is disabled", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "1234", }, registeredVariables: variables, showDescriptions: true, showSensitiveValues: false, expectedOutput: expectedWithHiddenSensitive, }, { name: "sensitive variable is empty", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", }, registeredVariables: variables, showDescriptions: true, showSensitiveValues: true, expectedOutput: expectedWithDescription, }, { name: "sensitive variable is non-empty and show sensitive variables is enabled", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "1234", }, registeredVariables: variables, showDescriptions: true, showSensitiveValues: true, expectedOutput: expectedWithSensitive, }, { name: "sensitive variable is non-empty but show descriptions is disabled", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "1234", }, registeredVariables: variables, showDescriptions: false, showSensitiveValues: true, expectedOutput: expectedSensitiveWithoutDescription, }, { name: "print unregistered variable with description", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", "COSIGN_TEST3": "abcd", }, registeredVariables: variables, showDescriptions: true, showSensitiveValues: false, expectedOutput: expectedWithNonRegisteredEnv, }, { name: "print unregistered variable with description (sensitive enabled)", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", "COSIGN_TEST3": "abcd", }, registeredVariables: variables, showDescriptions: true, showSensitiveValues: true, expectedOutput: expectedWithNonRegisteredEnvSensitive, }, { name: "print unregistered variable without description", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", "COSIGN_TEST3": "abcd", }, registeredVariables: variables, showDescriptions: false, showSensitiveValues: false, expectedOutput: expectedWithNonRegisteredEnvNoDesc, }, { name: "print unregistered variable without description (sensitive enabled)", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", "COSIGN_TEST3": "abcd", }, registeredVariables: variables, showDescriptions: false, showSensitiveValues: true, expectedOutput: expectedWithNonRegisteredEnvNoDescSensitive, }, { name: "print empty unregistered variable", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", "COSIGN_TEST3": "", }, registeredVariables: variables, showDescriptions: false, showSensitiveValues: false, expectedOutput: expectedWithNonRegisteredEnvNoDescEmpty, }, { name: "print empty unregistered variable (sensitive enabled)", environmentVariables: map[string]string{ "COSIGN_TEST1": "abcd", "COSIGN_TEST2": "", "COSIGN_TEST3": "", }, registeredVariables: variables, showDescriptions: false, showSensitiveValues: false, expectedOutput: expectedWithNonRegisteredEnvNoDescEmpty, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Set needed environment variables testingEnvVars = tt.environmentVariables orgStdout := os.Stdout r, w, _ := os.Pipe() os.Stdout = w printEnv(tt.registeredVariables, tGetEnv(), tGetEnviron(), tt.showDescriptions, tt.showSensitiveValues) w.Close() out, _ := io.ReadAll(r) os.Stdout = orgStdout if tt.expectedOutput != string(out) { t.Errorf("Expected to get %q\n, but got %q", tt.expectedOutput, string(out)) } }) } } cosign-2.5.0/cmd/cosign/cli/fulcio/000077500000000000000000000000001477503325500170705ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/fulcio/depcheck_test.go000066400000000000000000000021631477503325500222260ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcio_test import ( "testing" "github.com/depcheck-test/depcheck-test/depcheck" ) func TestNoDeps(t *testing.T) { depcheck.AssertNoDependency(t, map[string][]string{ "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio": { // Avoid pulling in a variety of things that are massive dependencies. "github.com/google/trillian", "github.com/envoyproxy/go-control-plane", "github.com/gogo/protobuf/protoc-gen-gogo", "github.com/grpc-ecosystem/go-grpc-middleware", "github.com/jhump/protoreflect", }, }) } cosign-2.5.0/cmd/cosign/cli/fulcio/fulcio.go000066400000000000000000000146211477503325500207040ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcio import ( "context" "crypto" "crypto/x509" "fmt" "net/url" "os" "strings" "github.com/go-jose/go-jose/v3/jwt" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign/privacy" "github.com/sigstore/cosign/v2/internal/pkg/cosign/fulcio/fulcioroots" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/providers" "github.com/sigstore/fulcio/pkg/api" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/oauthflow" "github.com/sigstore/sigstore/pkg/signature" "golang.org/x/term" ) const ( flowNormal = "normal" flowDevice = "device" flowToken = "token" flowClientCredentials = "client_credentials" ) type oidcConnector interface { OIDConnect(string, string, string, string) (*oauthflow.OIDCIDToken, error) } type realConnector struct { flow oauthflow.TokenGetter } func (rf *realConnector) OIDConnect(url, clientID, secret, redirectURL string) (*oauthflow.OIDCIDToken, error) { return oauthflow.OIDConnect(url, clientID, secret, redirectURL, rf.flow) } func getCertForOauthID(sv signature.SignerVerifier, fc api.LegacyClient, connector oidcConnector, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string) (*api.CertificateResponse, error) { tok, err := connector.OIDConnect(oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL) if err != nil { return nil, err } publicKey, err := sv.PublicKey() if err != nil { return nil, err } pubBytes, err := cryptoutils.MarshalPublicKeyToPEM(publicKey) if err != nil { return nil, err } // Sign the email address as part of the request proof, err := sv.SignMessage(strings.NewReader(tok.Subject)) if err != nil { return nil, err } cr := api.CertificateRequest{ PublicKey: api.Key{ Content: pubBytes, }, SignedEmailAddress: proof, } return fc.SigningCert(cr, tok.RawString) } // GetCert returns the PEM-encoded signature of the OIDC identity returned as part of an interactive oauth2 flow plus the PEM-encoded cert chain. func GetCert(_ context.Context, sv signature.SignerVerifier, idToken, flow, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string, fClient api.LegacyClient) (*api.CertificateResponse, error) { c := &realConnector{} switch flow { case flowClientCredentials: c.flow = oauthflow.NewClientCredentialsFlow(oidcIssuer) case flowDevice: c.flow = oauthflow.NewDeviceFlowTokenGetterForIssuer(oidcIssuer) case flowNormal: c.flow = oauthflow.DefaultIDTokenGetter case flowToken: c.flow = &oauthflow.StaticTokenGetter{RawToken: idToken} default: return nil, fmt.Errorf("unsupported oauth flow: %s", flow) } return getCertForOauthID(sv, fClient, c, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL) } type Signer struct { Cert []byte Chain []byte SCT []byte signature.SignerVerifier } func NewSigner(ctx context.Context, ko options.KeyOpts, signer signature.SignerVerifier) (*Signer, error) { fClient, err := NewClient(ko.FulcioURL) if err != nil { return nil, fmt.Errorf("creating Fulcio client: %w", err) } idToken, err := idToken(ko.IDToken) if err != nil { return nil, fmt.Errorf("getting id token: %w", err) } var provider providers.Interface // If token is not set in the options, get one from the provders if idToken == "" && providers.Enabled(ctx) && !ko.OIDCDisableProviders { if ko.OIDCProvider != "" { provider, err = providers.ProvideFrom(ctx, ko.OIDCProvider) if err != nil { return nil, fmt.Errorf("getting provider: %w", err) } idToken, err = provider.Provide(ctx, "sigstore") } else { idToken, err = providers.Provide(ctx, "sigstore") } if err != nil { return nil, fmt.Errorf("fetching ambient OIDC credentials: %w", err) } } fmt.Fprintln(os.Stderr, "Retrieving signed certificate...") var flow string switch { case ko.FulcioAuthFlow != "": // Caller manually set flow option. flow = ko.FulcioAuthFlow case idToken != "": flow = flowToken case !term.IsTerminal(0): fmt.Fprintln(os.Stderr, "Non-interactive mode detected, using device flow.") flow = flowDevice default: var statementErr error privacy.StatementOnce.Do(func() { ui.Infof(ctx, privacy.Statement) ui.Infof(ctx, privacy.StatementConfirmation) if !ko.SkipConfirmation { if err := ui.ConfirmContinue(ctx); err != nil { statementErr = err } } }) if statementErr != nil { return nil, statementErr } flow = flowNormal } Resp, err := GetCert(ctx, signer, idToken, flow, ko.OIDCIssuer, ko.OIDCClientID, ko.OIDCClientSecret, ko.OIDCRedirectURL, fClient) // TODO, use the chain. if err != nil { return nil, fmt.Errorf("retrieving cert: %w", err) } f := &Signer{ SignerVerifier: signer, Cert: Resp.CertPEM, Chain: Resp.ChainPEM, SCT: Resp.SCT, } return f, nil } func (f *Signer) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) { //nolint: revive return f.SignerVerifier.PublicKey() } var _ signature.Signer = &Signer{} func GetRoots() (*x509.CertPool, error) { return fulcioroots.Get() } func GetIntermediates() (*x509.CertPool, error) { return fulcioroots.GetIntermediates() } func NewClient(fulcioURL string) (api.LegacyClient, error) { fulcioServer, err := url.Parse(fulcioURL) if err != nil { return nil, err } fClient := api.NewClient(fulcioServer, api.WithUserAgent(options.UserAgent())) return fClient, nil } // idToken allows users to either pass in an identity token directly // or a path to an identity token via the --identity-token flag func idToken(s string) (string, error) { // If this is a valid raw token or is empty, just return it if _, err := jwt.ParseSigned(s); err == nil || s == "" { return s, nil } // Otherwise, if this is a path to a token return the contents c, err := os.ReadFile(s) return string(c), err } cosign-2.5.0/cmd/cosign/cli/fulcio/fulcio_test.go000066400000000000000000000142351477503325500217440ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcio import ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/pem" "errors" "net/http" "net/http/httptest" "testing" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/test" "github.com/sigstore/fulcio/pkg/api" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/oauthflow" "github.com/sigstore/sigstore/pkg/signature" ) type testFlow struct { idt *oauthflow.OIDCIDToken email string err error } func (tf *testFlow) OIDConnect(url, clientID, secret, redirectURL string) (*oauthflow.OIDCIDToken, error) { //nolint: revive if tf.err != nil { return nil, tf.err } return tf.idt, nil } type testClient struct { payload api.CertificateResponse rootResp api.RootResponse err error } var _ api.LegacyClient = (*testClient)(nil) func (p *testClient) SigningCert(cr api.CertificateRequest, token string) (*api.CertificateResponse, error) { //nolint: revive return &p.payload, p.err } func (p *testClient) RootCert() (*api.RootResponse, error) { return &p.rootResp, p.err } func TestGetCertForOauthID(t *testing.T) { testKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("Could not generate ecdsa keypair for test: %v", err) } sv, err := signature.LoadECDSASignerVerifier(testKey, crypto.SHA256) if err != nil { t.Fatalf("Could not create a signer: %v", err) } testCases := []struct { desc string email string accessToken string tokenGetterErr error idTokenEmailErr error signingCertErr error expectErr bool }{{ desc: "happy case", email: "example@oidc.id", accessToken: "abc123foobar", }, { desc: "getIDToken error", email: "example@oidc.id", accessToken: "abc123foobar", tokenGetterErr: errors.New("getIDToken() failed"), expectErr: true, }, { desc: "SigningCert error", email: "example@oidc.id", accessToken: "abc123foobar", signingCertErr: errors.New("SigningCert() failed"), expectErr: true, }} for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { expectedCertPem := &pem.Block{ Type: "CERTIFICATE", Bytes: []byte("d34db33fd34db33fd34db33fd34db33f"), } expectedCertBytes := pem.EncodeToMemory(expectedCertPem) expectedExtraBytes := []byte("0123456789abcdef") tscp := &testClient{ payload: api.CertificateResponse{ CertPEM: expectedCertBytes, ChainPEM: expectedExtraBytes, }, err: tc.signingCertErr, } tf := testFlow{ email: tc.email, idt: &oauthflow.OIDCIDToken{ RawString: tc.accessToken, }, err: tc.tokenGetterErr, } resp, err := getCertForOauthID(sv, tscp, &tf, "", "", "", "") if err != nil { if !tc.expectErr { t.Fatalf("getCertForOauthID returned error: %v", err) } return } if tc.expectErr { t.Fatalf("getCertForOauthID got: %q, %q wanted error", resp.CertPEM, resp.ChainPEM) } expectedCert := string(expectedCertBytes) actualCert := string(resp.CertPEM) if actualCert != expectedCert { t.Errorf("getCertForOauthID returned cert %q, wanted %q", actualCert, expectedCert) } expectedChain := string(expectedExtraBytes) actualChain := string(resp.ChainPEM) if actualChain != expectedChain { t.Errorf("getCertForOauthID returned chain %q, wanted %q", actualChain, expectedChain) } }) } } func TestNewClient(t *testing.T) { t.Parallel() expectedUserAgent := options.UserAgent() requestReceived := false testServer := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { requestReceived = true file := []byte{} got := r.UserAgent() if got != expectedUserAgent { t.Errorf("wanted User-Agent %q, got %q", expectedUserAgent, got) } w.WriteHeader(http.StatusOK) _, _ = w.Write(file) })) defer testServer.Close() client, err := NewClient(testServer.URL) if err != nil { t.Error(err) } _, _ = client.SigningCert(api.CertificateRequest{}, "") if !requestReceived { t.Fatal("no requests were received") } } func TestNewSigner(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) pemChain, _ := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, rootCert}) testServer := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusCreated) _, _ = w.Write(pemChain) })) defer testServer.Close() // success: Generate a random key and create a corresponding // SignerVerifier. ctx := context.TODO() ko := options.KeyOpts{ OIDCDisableProviders: true, // random test token IDToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", FulcioURL: testServer.URL, FulcioAuthFlow: "token", } privKey, err := cosign.GeneratePrivateKey() if err != nil { t.Fatal(err) } sv, err := signature.LoadECDSASignerVerifier(privKey, crypto.SHA256) if err != nil { t.Fatal(err) } signer, err := NewSigner(ctx, ko, sv) if err != nil { t.Fatalf("unexpected error creating signer: %v", err) } responsePEMChain := string(signer.Cert) + string(signer.Chain) if responsePEMChain != string(pemChain) { t.Fatalf("response certificates not equal, got %v, expected %v", responsePEMChain, pemChain) } if signer.SignerVerifier == nil { t.Fatalf("missing signer/verifier") } } cosign-2.5.0/cmd/cosign/cli/fulcio/fulcioverifier/000077500000000000000000000000001477503325500221055ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier.go000066400000000000000000000027501477503325500254550ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcioverifier import ( "context" "fmt" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/sigstore/pkg/signature" ) func NewSigner(ctx context.Context, ko options.KeyOpts, signer signature.SignerVerifier) (*fulcio.Signer, error) { fs, err := fulcio.NewSigner(ctx, ko, signer) if err != nil { return nil, err } // Grab the PublicKeys for the CTFE, either from tuf or env. pubKeys, err := cosign.GetCTLogPubs(ctx) if err != nil { return nil, fmt.Errorf("getting CTFE public keys: %w", err) } // verify the sct if err := cosign.VerifySCT(ctx, fs.Cert, fs.Chain, fs.SCT, pubKeys); err != nil { return nil, fmt.Errorf("verifying SCT: %w", err) } ui.Infof(ctx, "Successfully verified SCT...") return fs, nil } cosign-2.5.0/cmd/cosign/cli/generate.go000066400000000000000000000035461477503325500177400ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func Generate() *cobra.Command { o := &options.GenerateOptions{} cmd := &cobra.Command{ Use: "generate", Short: "Generates (unsigned) signature payloads from the supplied container image.", Long: `Generates an unsigned payload from the supplied container image and flags. This payload matches the one generated by the "cosign sign" command and can be used if you need to sign payloads with your own tooling or algorithms.`, Example: ` cosign generate [--a key=value] # Generate a simple payload for an image cosign generate # Generate a payload with specific annotations cosign generate -a foo=bar # Use this payload in another tool gpg --output image.sig --detach-sig <(cosign generate )`, Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { annotationMap, err := o.AnnotationsMap() if err != nil { return err } return generate.GenerateCmd(cmd.Context(), o.Registry, args[0], annotationMap.Annotations, cmd.OutOrStdout()) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/generate/000077500000000000000000000000001477503325500174015ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/generate/generate.go000066400000000000000000000031251477503325500215230ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package generate import ( "context" "io" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/sigstore/pkg/signature/payload" ) // nolint func GenerateCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string, annotations map[string]interface{}, w io.Writer) error { ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return err } digest, err := ociremote.ResolveDigest(ref, ociremoteOpts...) if err != nil { return err } // Overwrite "ref" with a digest to avoid a race where we use a tag // multiple times, and it potentially points to different things at // each access. ref = digest json, err := (&payload.Cosign{Image: digest, Annotations: annotations}).MarshalJSON() if err != nil { return err } w.Write(json) return nil } cosign-2.5.0/cmd/cosign/cli/generate/generate_key_pair.go000066400000000000000000000077271477503325500234220ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package generate import ( "context" "crypto" "errors" "fmt" "io" "os" "strings" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/cosign/v2/pkg/cosign/git" "github.com/sigstore/cosign/v2/pkg/cosign/git/github" "github.com/sigstore/cosign/v2/pkg/cosign/git/gitlab" icos "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/kubernetes" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature/kms" ) var ( // Read is for fuzzing Read = readPasswordFn ) // nolint func GenerateKeyPairCmd(ctx context.Context, kmsVal string, outputKeyPrefixVal string, args []string) error { privateKeyFileName := outputKeyPrefixVal + ".key" publicKeyFileName := outputKeyPrefixVal + ".pub" if kmsVal != "" { k, err := kms.Get(ctx, kmsVal, crypto.SHA256) if err != nil { return err } pubKey, err := k.CreateKey(ctx, k.DefaultAlgorithm()) if err != nil { return fmt.Errorf("creating key: %w", err) } pemBytes, err := cryptoutils.MarshalPublicKeyToPEM(pubKey) if err != nil { return err } if err := os.WriteFile(publicKeyFileName, pemBytes, 0600); err != nil { return err } fmt.Fprintln(os.Stderr, "Public key written to", publicKeyFileName) return nil } if len(args) > 0 { split := strings.Split(args[0], "://") if len(split) < 2 { return errors.New("could not parse scheme, use :// format") } provider, targetRef := split[0], split[1] switch provider { case "k8s": return kubernetes.KeyPairSecret(ctx, targetRef, GetPass) case gitlab.ReferenceScheme, github.ReferenceScheme: return git.GetProvider(provider).PutSecret(ctx, targetRef, GetPass) } return fmt.Errorf("undefined provider: %s", provider) } keys, err := cosign.GenerateKeyPair(GetPass) if err != nil { return err } fileExists, err := icos.FileExists(privateKeyFileName) if err != nil { return fmt.Errorf("failed checking if %s exists: %w", privateKeyFileName, err) } if fileExists { ui.Warnf(ctx, "File %s already exists. Overwrite?", privateKeyFileName) if err := ui.ConfirmContinue(ctx); err != nil { return err } return writeKeyFiles(privateKeyFileName, publicKeyFileName, keys) } return writeKeyFiles(privateKeyFileName, publicKeyFileName, keys) } func writeKeyFiles(privateKeyFileName string, publicKeyFileName string, keys *cosign.KeysBytes) error { // TODO: make sure the perms are locked down first. if err := os.WriteFile(privateKeyFileName, keys.PrivateBytes, 0600); err != nil { return err } fmt.Fprintln(os.Stderr, "Private key written to", privateKeyFileName) if err := os.WriteFile(publicKeyFileName, keys.PublicBytes, 0644); err != nil { //nolint: gosec return err } // #nosec G306 fmt.Fprintln(os.Stderr, "Public key written to", publicKeyFileName) return nil } func GetPass(confirm bool) ([]byte, error) { read := Read(confirm) return read() } func readPasswordFn(confirm bool) func() ([]byte, error) { pw, ok := env.LookupEnv(env.VariablePassword) switch { case ok: return func() ([]byte, error) { return []byte(pw), nil } case cosign.IsTerminal(): return func() ([]byte, error) { return cosign.GetPassFromTerm(confirm) } // Handle piped in passwords. default: return func() ([]byte, error) { return io.ReadAll(os.Stdin) } } } cosign-2.5.0/cmd/cosign/cli/generate/generate_key_pair_test.go000066400000000000000000000045531477503325500244530ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package generate import ( "context" "os" "testing" "github.com/google/go-cmp/cmp" icos "github.com/sigstore/cosign/v2/internal/pkg/cosign" ) func TestReadPasswordFn_env(t *testing.T) { t.Setenv("COSIGN_PASSWORD", "foo") b, err := readPasswordFn(true)() if err != nil { t.Fatalf("unexpected error: %v", err) } if diff := cmp.Diff("foo", string(b)); diff != "" { t.Fatal(diff) } } func TestReadPasswordFn_envEmptyVal(t *testing.T) { t.Setenv("COSIGN_PASSWORD", "") b, err := readPasswordFn(true)() if err != nil { t.Fatalf("unexpected error: %v", err) } if len(b) > 0 { t.Fatalf("expected empty string; got %q", string(b)) } } func TestGenerationOfKeys(t *testing.T) { var privateKeyName = "my-test.key" var publicKeyName = "my-test.pub" t.Setenv("COSIGN_PASSWORD", "test") // we pass in a custom name `my-test` because the GenerateKeyPairCmd // doesn't care where the value comes from, only that it has a value. // be default it's set to `cosign`, but this is done by the CLI flag // framework if there is no value set by the user when running the // command. GenerateKeyPairCmd(context.Background(), "", "my-test", nil) checkIfFileExistsThenDelete(privateKeyName, t) checkIfFileExistsThenDelete(publicKeyName, t) } func checkIfFileExistsThenDelete(fileName string, t *testing.T) { fileExists, err := icos.FileExists(fileName) if err != nil { t.Fatalf("failed checking if %s exists: %v", fileName, err) } if !fileExists { t.Fatalf("key generation for key %s failed", fileName) } t.Logf("key generation for key %s succeeded", fileName) deleteKeyFile(fileName, t) } func deleteKeyFile(fileName string, t *testing.T) { t.Cleanup(func() { t.Logf("Removing keyfile %s...", fileName) os.Remove(fileName) t.Logf("Removed keyfile %s", fileName) }) } cosign-2.5.0/cmd/cosign/cli/generate_key_pair.go000066400000000000000000000047141477503325500216210ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func GenerateKeyPair() *cobra.Command { o := &options.GenerateKeyPairOptions{} cmd := &cobra.Command{ Use: "generate-key-pair", Short: "Generates a key-pair.", Long: "Generates a key-pair for signing.", Example: ` cosign generate-key-pair [--kms KMSPATH] # generate key-pair and write to cosign.key and cosign.pub files cosign generate-key-pair # generate key-pair and write to custom named my-name.key and my-name.pub files cosign generate-key-pair --output-key-prefix my-name # generate a key-pair in Azure Key Vault cosign generate-key-pair --kms azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # generate a key-pair in AWS KMS cosign generate-key-pair --kms awskms://[ENDPOINT]/[ID/ALIAS/ARN] # generate a key-pair in Google Cloud KMS cosign generate-key-pair --kms gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # generate a key-pair in Hashicorp Vault cosign generate-key-pair --kms hashivault://[KEY] # generate a key-pair in Kubernetes Secret cosign generate-key-pair k8s://[NAMESPACE]/[NAME] # generate a key-pair in GitHub cosign generate-key-pair github://[OWNER]/[PROJECT_NAME] # generate a key-pair in GitLab with project name cosign generate-key-pair gitlab://[OWNER]/[PROJECT_NAME] # generate a key-pair in GitLab with project id cosign generate-key-pair gitlab://[PROJECT_ID] CAVEATS: This command interactively prompts for a password. You can use the COSIGN_PASSWORD environment variable to provide one.`, PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return generate.GenerateKeyPairCmd(cmd.Context(), o.KMS, o.OutputKeyPrefix, args) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/import_key_pair.go000066400000000000000000000033321477503325500213340ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/importkeypair" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func ImportKeyPair() *cobra.Command { o := &options.ImportKeyPairOptions{} cmd := &cobra.Command{ Use: "import-key-pair", Short: "Imports a PEM-encoded RSA or EC private key.", Long: "Imports a PEM-encoded RSA or EC private key for signing.", Example: ` cosign import-key-pair --key openssl.key --output-key-prefix my-key # import PEM-encoded RSA or EC private key and write to import-cosign.key and import-cosign.pub files cosign import-key-pair --key # import PEM-encoded RSA or EC private key and write to my-key.key and my-key.pub files cosign import-key-pair --key --output-key-prefix my-key CAVEATS: This command interactively prompts for a password. You can use the COSIGN_PASSWORD environment variable to provide one.`, PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return importkeypair.ImportKeyPairCmd(cmd.Context(), *o, args) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/importkeypair/000077500000000000000000000000001477503325500205065ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/importkeypair/import_key_pair.go000066400000000000000000000047541477503325500242440ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package importkeypair import ( "context" "fmt" "io" "os" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" icos "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) var ( // Read is for fuzzing Read = readPasswordFn ) // nolint func ImportKeyPairCmd(ctx context.Context, o options.ImportKeyPairOptions, args []string) error { keys, err := cosign.ImportKeyPair(o.Key, GetPass) if err != nil { return err } privateKeyFileName := o.OutputKeyPrefix + ".key" publicKeyFileName := o.OutputKeyPrefix + ".pub" fileExists, err := icos.FileExists(privateKeyFileName) if err != nil { return fmt.Errorf("failed checking if %s exists: %w", privateKeyFileName, err) } if fileExists { ui.Warnf(ctx, "File %s already exists. Overwrite?", privateKeyFileName) if !o.SkipConfirmation { if err := ui.ConfirmContinue(ctx); err != nil { return err } } } // TODO: make sure the perms are locked down first. if err := os.WriteFile(privateKeyFileName, keys.PrivateBytes, 0600); err != nil { return err } fmt.Fprintln(os.Stderr, "Private key written to", privateKeyFileName) if err := os.WriteFile(publicKeyFileName, keys.PublicBytes, 0644); err != nil { return err } // #nosec G306 fmt.Fprintln(os.Stderr, "Public key written to", publicKeyFileName) return nil } func GetPass(confirm bool) ([]byte, error) { read := Read(confirm) return read() } func readPasswordFn(confirm bool) func() ([]byte, error) { pw, ok := env.LookupEnv(env.VariablePassword) switch { case ok: return func() ([]byte, error) { return []byte(pw), nil } case cosign.IsTerminal(): return func() ([]byte, error) { return cosign.GetPassFromTerm(confirm) } // Handle piped in passwords. default: return func() ([]byte, error) { return io.ReadAll(os.Stdin) } } } cosign-2.5.0/cmd/cosign/cli/importkeypair/import_key_pair_test.go000066400000000000000000000066571477503325500253070ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package importkeypair import ( "context" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "os" "testing" "github.com/google/go-cmp/cmp" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" icos "github.com/sigstore/cosign/v2/internal/pkg/cosign" ) func TestReadPasswordFn_env(t *testing.T) { t.Setenv("COSIGN_PASSWORD", "foo") b, err := readPasswordFn(true)() if err != nil { t.Fatalf("unexpected error: %v", err) } if diff := cmp.Diff("foo", string(b)); diff != "" { t.Fatal(diff) } } func TestReadPasswordFn_envEmptyVal(t *testing.T) { t.Setenv("COSIGN_PASSWORD", "") b, err := readPasswordFn(true)() if err != nil { t.Fatalf("unexpected error: %v", err) } if len(b) > 0 { t.Fatalf("expected empty string; got %q", string(b)) } } func TestImportOfKeys(t *testing.T) { t.Setenv("COSIGN_PASSWORD", "test") privateKeyFileName := "my-private-key.pem" createTemporaryPrivateKeyForImporting(privateKeyFileName) // we pass in a custom outVal `my-test` because the ImportKeyPairCmd // doesn't care where the value comes from, only that it has a value. // be default it's set to `import-cosign`, but this is done by the CLI flag // framework if there is no value set by the user when running the // command. outputtedKeyPairFileName := "my-test" ImportKeyPairCmd(context.Background(), options.ImportKeyPairOptions{ Key: privateKeyFileName, OutputKeyPrefix: outputtedKeyPairFileName, SkipConfirmation: false, }, nil) // removes temporary RSA private key used for test checkIfFileExistsThenDelete(privateKeyFileName, t) // checks if the outputted key pairs have been created based on // the imported RSA private key, and then deletes them as per test // cleanup checkIfFileExistsThenDelete(outputtedKeyPairFileName+".key", t) checkIfFileExistsThenDelete(outputtedKeyPairFileName+".pub", t) } func createTemporaryPrivateKeyForImporting(privateKeyName string) { bitSize := 4096 // Generate RSA key. key, err := rsa.GenerateKey(rand.Reader, bitSize) if err != nil { panic(err) } // Encode private key to PKCS#1 ASN.1 PEM. keyPEM := pem.EncodeToMemory( &pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key), }, ) // Write private key to file. if err := os.WriteFile(privateKeyName, keyPEM, 0700); err != nil { panic(err) } } func checkIfFileExistsThenDelete(fileName string, t *testing.T) { fileExists, err := icos.FileExists(fileName) if err != nil { t.Fatalf("failed checking if %s exists: %v", fileName, err) } if !fileExists { t.Fatalf("key generation for key %s failed", fileName) } t.Logf("key generation for key %s succeeded", fileName) deleteKeyFile(fileName, t) } func deleteKeyFile(fileName string, t *testing.T) { t.Cleanup(func() { t.Logf("Removing keyfile %s...", fileName) os.Remove(fileName) t.Logf("Removed keyfile %s", fileName) }) } cosign-2.5.0/cmd/cosign/cli/initialize.go000066400000000000000000000047301477503325500203030ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/initialize" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func Initialize() *cobra.Command { o := &options.InitializeOptions{} cmd := &cobra.Command{ Use: "initialize", Short: "Initializes SigStore root to retrieve trusted certificate and key targets for verification.", Long: `Initializes SigStore root to retrieve trusted certificate and key targets for verification. The following options are used by default: - The current trusted Sigstore TUF root is embedded inside cosign at the time of release. - SigStore remote TUF repository is pulled from the CDN mirror at tuf-repo-cdn.sigstore.dev. To provide an out-of-band trusted initial root.json, use the -root flag with a file or URL reference. This will enable you to point cosign to a separate TUF root. Any updated TUF repository will be written to $HOME/.sigstore/root/. Trusted keys and certificate used in cosign verification (e.g. verifying Fulcio issued certificates with Fulcio root CA) are pulled form the trusted metadata.`, Example: `cosign initialize --mirror --out # initialize root with distributed root keys, default mirror, and default out path. cosign initialize # initialize with an out-of-band root key file, using the default mirror. cosign initialize --root # initialize with an out-of-band root key file and custom repository mirror. cosign initialize --mirror --root # initialize with an out-of-band root key file and custom repository mirror while verifying root checksum. cosign initialize --mirror --root --root-checksum `, PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, _ []string) error { return initialize.DoInitializeWithRootChecksum(cmd.Context(), o.Root, o.Mirror, o.RootChecksum) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/initialize/000077500000000000000000000000001477503325500177505ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/initialize/init.go000066400000000000000000000041641477503325500212470ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package initialize import ( "context" _ "embed" // To enable the `go:embed` directive. "encoding/json" "fmt" "os" "strings" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/sigstore/pkg/tuf" ) func DoInitialize(ctx context.Context, root, mirror string) error { return doInitialize(ctx, root, mirror, "", true) } func DoInitializeWithRootChecksum(ctx context.Context, root, mirror, rootChecksum string) error { return doInitialize(ctx, root, mirror, rootChecksum, false) } func doInitialize(ctx context.Context, root, mirror, rootChecksum string, forceSkipChecksumValidation bool) error { // Get the initial trusted root contents. var rootFileBytes []byte var err error if root != "" { if !forceSkipChecksumValidation { if rootChecksum == "" && (strings.HasPrefix(root, "http://") || strings.HasPrefix(root, "https://")) { fmt.Fprintln(os.Stderr, options.RootWithoutChecksumDeprecation) } } verifyChecksum := !forceSkipChecksumValidation && (rootChecksum != "") if verifyChecksum { rootFileBytes, err = blob.LoadFileOrURLWithChecksum(root, rootChecksum) } else { rootFileBytes, err = blob.LoadFileOrURL(root) } if err != nil { return err } } if err := tuf.Initialize(ctx, mirror, rootFileBytes); err != nil { return err } status, err := tuf.GetRootStatus(ctx) if err != nil { return err } b, err := json.MarshalIndent(status, "", "\t") if err != nil { return err } fmt.Println("Root status: \n", string(b)) return nil } cosign-2.5.0/cmd/cosign/cli/load.go000066400000000000000000000036461477503325500170660ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "context" "fmt" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci/layout" "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/spf13/cobra" ) func Load() *cobra.Command { o := &options.LoadOptions{} cmd := &cobra.Command{ Use: "load", Short: "Load a signed image on disk to a remote registry", Long: "Load a signed image on disk to a remote registry", Example: ` cosign load --dir `, Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return LoadCmd(cmd.Context(), *o, args[0]) }, } o.AddFlags(cmd) return cmd } func LoadCmd(ctx context.Context, opts options.LoadOptions, imageRef string) error { ref, err := name.ParseReference(imageRef) if err != nil { return fmt.Errorf("parsing image name %s: %w", imageRef, err) } // get the signed image from disk sii, err := layout.SignedImageIndex(opts.Directory) if err != nil { return fmt.Errorf("signed image index: %w", err) } ociremoteOpts, err := opts.Registry.ClientOpts(ctx) if err != nil { return err } return remote.WriteSignedImageIndexImages(ref, sii, ociremoteOpts...) } cosign-2.5.0/cmd/cosign/cli/manifest.go000066400000000000000000000107341477503325500177510ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "github.com/sigstore/cosign/v2/cmd/cosign/cli/manifest" "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func Manifest() *cobra.Command { cmd := &cobra.Command{ Use: "manifest", Short: "Provides utilities for discovering images in and performing operations on Kubernetes manifests", } cmd.AddCommand( manifestVerify(), ) return cmd } func manifestVerify() *cobra.Command { o := &options.VerifyOptions{} cmd := &cobra.Command{ Use: "verify", Short: "Verify all signatures of images specified in the manifest", Long: `Verify all signature of images in a Kubernetes resource manifest by checking claims against the transparency log.`, Example: ` cosign manifest verify --key || # verify cosign claims and signing certificates on images in the manifest cosign manifest verify # additionally verify specified annotations cosign manifest verify -a key1=val1 -a key2=val2 # verify images with public key cosign manifest verify --key cosign.pub # verify images with public key provided by URL cosign manifest verify --key https://host.for/ # verify images with public key stored in Azure Key Vault cosign manifest verify --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # verify images with public key stored in AWS KMS cosign manifest verify --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # verify images with public key stored in Google Cloud KMS cosign manifest verify --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # verify images with public key stored in Hashicorp Vault cosign manifest verify --key hashivault://[KEY] `, Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { annotations, err := o.AnnotationsMap() if err != nil { return err } v := &manifest.VerifyManifestCommand{ VerifyCommand: verify.VerifyCommand{ RegistryOptions: o.Registry, CertVerifyOptions: o.CertVerify, CheckClaims: o.CheckClaims, KeyRef: o.Key, CertRef: o.CertVerify.Cert, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, CertChain: o.CertVerify.CertChain, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, Output: o.Output, RekorURL: o.Rekor.URL, Attachment: o.Attachment, Annotations: annotations, LocalImage: o.LocalImage, Offline: o.CommonVerifyOptions.Offline, TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath, IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog, MaxWorkers: o.CommonVerifyOptions.MaxWorkers, }, } if o.CommonVerifyOptions.MaxWorkers == 0 { return fmt.Errorf("please set the --max-worker flag to a value that is greater than 0") } return v.Exec(cmd.Context(), args) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/manifest/000077500000000000000000000000001477503325500174155ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/manifest/verify.go000066400000000000000000000074341477503325500212600ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package manifest import ( "bytes" "context" "errors" "flag" "fmt" "io" "os" "path/filepath" "strings" "k8s.io/apimachinery/pkg/util/yaml" "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" ) // VerifyManifestCommand verifies all image signatures on a supplied k8s resource type VerifyManifestCommand struct { verify.VerifyCommand } // Exec runs the verification command func (c *VerifyManifestCommand) Exec(ctx context.Context, args []string) error { if len(args) != 1 { return flag.ErrHelp } manifestPath := args[0] err := isExtensionAllowed(manifestPath) if err != nil { return fmt.Errorf("check if extension is valid: %w", err) } manifest, err := os.ReadFile(manifestPath) if err != nil { return fmt.Errorf("could not read manifest: %w", err) } images, err := getImagesFromYamlManifest(manifest) if err != nil { return fmt.Errorf("unable to extract the container image references in the manifest %w", err) } if len(images) == 0 { return errors.New("no images found in manifest") } fmt.Fprintf(os.Stderr, "Extracted image(s): %s\n", strings.Join(images, ", ")) return c.VerifyCommand.Exec(ctx, images) } // unionImagesKind is the union type that match PodSpec, PodSpecTemplate, and // JobSpecTemplate; but filtering all keys except for `Image`. type unionImagesKind struct { Spec struct { // PodSpec imageContainers `json:",inline"` // PodSpecTemplate Template struct { Spec struct { imageContainers `json:",inline"` } } // JobSpecTemplate JobTemplate struct { Spec struct { Template struct { Spec struct { imageContainers `json:",inline"` } } } } } } // imageContainers is a wrapper for `containers[].image` and `initContainers[].image` type imageContainers struct { Containers []struct { Image string } InitContainers []struct { Image string } } func (uik *unionImagesKind) images() []string { images := []string(nil) var addImage = func(ic *imageContainers) { for _, c := range ic.InitContainers { if len(c.Image) > 0 { images = append(images, c.Image) } } for _, c := range ic.Containers { if len(c.Image) > 0 { images = append(images, c.Image) } } } // Pod addImage(&uik.Spec.imageContainers) // Deployment, ReplicaSet, StatefulSet, DaemonSet, Job addImage(&uik.Spec.Template.Spec.imageContainers) // CronJob addImage(&uik.Spec.JobTemplate.Spec.Template.Spec.imageContainers) return images } func getImagesFromYamlManifest(manifest []byte) ([]string, error) { dec := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(manifest), 4096) var images []string for { ic := unionImagesKind{} if err := dec.Decode(&ic); err != nil { if errors.Is(err, io.EOF) { break } return images, errors.New("unable to decode the manifest") } images = append(images, ic.images()...) } return images, nil } func isExtensionAllowed(ext string) error { allowedExtensions := allowedExtensionsForManifest() for _, v := range allowedExtensions { if strings.EqualFold(filepath.Ext(strings.TrimSpace(ext)), v) { return nil } } return fmt.Errorf("only %v manifests are supported at this time", allowedExtensions) } func allowedExtensionsForManifest() []string { return []string{".yaml", ".yml"} } cosign-2.5.0/cmd/cosign/cli/manifest/verify_test.go000066400000000000000000000152701477503325500223140ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package manifest import ( "reflect" "testing" ) const singleContainerManifest = ` apiVersion: v1 kind: Pod metadata: name: single-pod spec: restartPolicy: Never containers: - name: nginx-container image: nginx:1.21.1 ` const initContainerManifest = ` apiVersion: v1 kind: Pod metadata: name: single-pod spec: restartPolicy: Never initContainers: - name: preflight image: preflight:3.2.1 containers: - name: nginx-container image: nginx:1.21.1 ` const multiContainerManifest = ` apiVersion: v1 kind: Pod metadata: name: multi-pod spec: restartPolicy: Never volumes: - name: shared-data emptyDir: {} containers: - name: nginx-container image: nginx:1.21.1 volumeMounts: - name: shared-data mountPath: /usr/share/nginx/html - name: ubuntu-container image: ubuntu:21.10 volumeMounts: - name: shared-data mountPath: /pod-data command: ["/bin/sh"] args: ["-c", "echo Hello, World > /pod-data/index.html"] ` const multiResourceContainerManifest = ` apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 --- apiVersion: v1 kind: Pod metadata: name: multi-pod spec: restartPolicy: Never volumes: - name: shared-data emptyDir: {} containers: - name: nginx-container image: nginx:1.21.1 volumeMounts: - name: shared-data mountPath: /usr/share/nginx/html - name: ubuntu-container image: ubuntu:21.10 volumeMounts: - name: shared-data mountPath: /pod-data command: ["/bin/sh"] args: ["-c", "echo Hello, World > /pod-data/index.html"] ` const customContainerManifest = ` apiVersion: v42 kind: PodSpec metadata: name: custom-pod spec: restartPolicy: Never containers: - name: nginx-container image: nginx:1.21.1 ` const daemonsetManifest = ` apiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd-elasticsearch namespace: kube-system labels: k8s-app: fluentd-logging spec: selector: matchLabels: name: fluentd-elasticsearch template: metadata: labels: name: fluentd-elasticsearch spec: tolerations: # this toleration is to have the daemonset runnable on master nodes # remove it if your masters can't run pods - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule initContainers: - name: py image: python command: ["python", "-c", "import math;print(math.sin(1))"] containers: - name: fluentd-elasticsearch image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2 resources: limits: memory: 200Mi requests: cpu: 100m memory: 200Mi volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers ` const jobManifest = ` apiVersion: batch/v1 kind: Job metadata: name: pi spec: template: spec: initContainers: - name: py image: python command: ["python", "-c", "import math;print(math.sin(1))"] containers: - name: pi image: perl command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] restartPolicy: Never backoffLimit: 4 ` const cronJobManifest = ` apiVersion: batch/v1 kind: CronJob metadata: name: hello spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: initContainers: - name: py image: python command: ["python", "-c", "booting up"] containers: - name: hello image: busybox imagePullPolicy: IfNotPresent command: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure ` func TestGetImagesFromYamlManifest(t *testing.T) { testCases := []struct { name string fileContents []byte expected []string }{{ name: "single image", fileContents: []byte(singleContainerManifest), expected: []string{"nginx:1.21.1"}, }, { name: "initialize and container images", fileContents: []byte(initContainerManifest), expected: []string{"preflight:3.2.1", "nginx:1.21.1"}, }, { name: "daemonsets", fileContents: []byte(daemonsetManifest), expected: []string{"python", "quay.io/fluentd_elasticsearch/fluentd:v2.5.2"}, }, { name: "jobs", fileContents: []byte(jobManifest), expected: []string{"python", "perl"}, }, { name: "cronjobs", fileContents: []byte(cronJobManifest), expected: []string{"python", "busybox"}, }, { name: "multi image", fileContents: []byte(multiContainerManifest), expected: []string{"nginx:1.21.1", "ubuntu:21.10"}, }, { name: "multiple resources and images within a document", fileContents: []byte(multiResourceContainerManifest), expected: []string{"nginx:1.14.2", "nginx:1.21.1", "ubuntu:21.10"}, }, { name: "no images found", fileContents: []byte(``), expected: nil, }, { name: "custom type single image", fileContents: []byte(customContainerManifest), expected: []string{"nginx:1.21.1"}, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { got, err := getImagesFromYamlManifest(tc.fileContents) if err != nil { t.Fatalf("getImagesFromYamlManifest returned error: %v", err) } if !reflect.DeepEqual(tc.expected, got) { t.Errorf("getImagesFromYamlManifest returned %v, wanted %v", got, tc.expected) } }) } } cosign-2.5.0/cmd/cosign/cli/options/000077500000000000000000000000001477503325500173025ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/options/annotations.go000066400000000000000000000030371477503325500221710ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "fmt" "strings" "github.com/spf13/cobra" sigs "github.com/sigstore/cosign/v2/pkg/signature" ) // AnnotationOptions is the top level wrapper for the annotations. type AnnotationOptions struct { Annotations []string } var _ Interface = (*AnnotationOptions)(nil) func (o *AnnotationOptions) AnnotationsMap() (sigs.AnnotationsMap, error) { ann := sigs.AnnotationsMap{} for _, a := range o.Annotations { kv := strings.Split(a, "=") if len(kv) != 2 { return ann, fmt.Errorf("unable to parse annotation: %s", a) } if ann.Annotations == nil { ann.Annotations = map[string]interface{}{} } ann.Annotations[kv[0]] = kv[1] } return ann, nil } // AddFlags implements Interface func (o *AnnotationOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringSliceVarP(&o.Annotations, "annotations", "a", nil, "extra key=value pairs to sign") _ = cmd.RegisterFlagCompletionFunc("annotations", cobra.NoFileCompletions) } cosign-2.5.0/cmd/cosign/cli/options/annotations_test.go000066400000000000000000000032211477503325500232230ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "testing" "github.com/google/go-cmp/cmp" "github.com/sigstore/cosign/v2/pkg/signature" ) func TestAnnotationOptions_AnnotationsMap(t *testing.T) { tests := []struct { name string annotations []string want signature.AnnotationsMap wantErr bool }{{ name: "nil", }, { name: "valid key", annotations: []string{"key=value"}, want: signature.AnnotationsMap{ Annotations: map[string]interface{}{ "key": "value", }, }, }, { name: "invalid key", annotations: []string{"key value"}, wantErr: true, want: signature.AnnotationsMap{}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &AnnotationOptions{ Annotations: tt.annotations, } got, err := s.AnnotationsMap() if (err != nil) != tt.wantErr { t.Errorf("AnnotationsMap() error = %v, wantErr %v", err, tt.wantErr) return } if diff := cmp.Diff(got, tt.want); diff != "" { t.Errorf("AnnotationsMap() got = %v, want %v\n diff: %s", got, tt.want, diff) } }) } } cosign-2.5.0/cmd/cosign/cli/options/attach.go000066400000000000000000000121131477503325500210730ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "fmt" "strings" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/spf13/cobra" ctypes "github.com/sigstore/cosign/v2/pkg/types" ) // AttachSignatureOptions is the top level wrapper for the attach signature command. type AttachSignatureOptions struct { Signature string Payload string Cert string CertChain string TimeStampedSig string RekorBundle string Registry RegistryOptions } var _ Interface = (*AttachSignatureOptions)(nil) // AddFlags implements Interface func (o *AttachSignatureOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) cmd.Flags().StringVar(&o.Signature, "signature", "", "path to the signature, or {-} for stdin") cmd.Flags().StringVar(&o.Payload, "payload", "", "path to the payload covered by the signature") cmd.Flags().StringVar(&o.Cert, "certificate", "", "path to the X.509 certificate in PEM format to include in the OCI Signature") cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "", "path to a list of CA X.509 certificates in PEM format which will be needed "+ "when building the certificate chain for the signing certificate. "+ "Must start with the parent intermediate CA certificate of the "+ "signing certificate and end with the root certificate. Included in the OCI Signature") cmd.Flags().StringVar(&o.TimeStampedSig, "tsr", "", "path to the Time Stamped Signature Response from RFC3161 compliant TSA") cmd.Flags().StringVar(&o.RekorBundle, "rekor-response", "", "path to the rekor bundle") } // AttachSBOMOptions is the top level wrapper for the attach sbom command. type AttachSBOMOptions struct { SBOM string SBOMType string SBOMInputFormat string Registry RegistryOptions RegistryExperimental RegistryExperimentalOptions } var _ Interface = (*AttachSBOMOptions)(nil) // AddFlags implements Interface func (o *AttachSBOMOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) o.RegistryExperimental.AddFlags(cmd) cmd.Flags().StringVar(&o.SBOM, "sbom", "", "path to the sbom, or {-} for stdin") _ = cmd.MarkFlagFilename("sbom", sbomExts...) sbomTypes := []string{"spdx", "cyclonedx", "syft"} cmd.Flags().StringVar(&o.SBOMType, "type", sbomTypes[0], "type of sbom ("+strings.Join(sbomTypes, "|")+")") _ = cmd.RegisterFlagCompletionFunc("type", cobra.FixedCompletions(sbomTypes, cobra.ShellCompDirectiveNoFileComp)) inputFormats := []string{ctypes.JSONInputFormat, ctypes.XMLInputFormat, ctypes.TextInputFormat} cmd.Flags().StringVar(&o.SBOMInputFormat, "input-format", "", "type of sbom input format ("+strings.Join(inputFormats, "|")+")") _ = cmd.RegisterFlagCompletionFunc("input-format", cobra.FixedCompletions(inputFormats, cobra.ShellCompDirectiveNoFileComp)) } func (o *AttachSBOMOptions) MediaType() (types.MediaType, error) { var looksLikeJSON bool if strings.HasSuffix(o.SBOM, ".json") { looksLikeJSON = true } switch o.SBOMType { case "cyclonedx": if o.SBOMInputFormat != "" && o.SBOMInputFormat != ctypes.XMLInputFormat && o.SBOMInputFormat != ctypes.JSONInputFormat { return "invalid", fmt.Errorf("invalid SBOM input format: %q, expected (json|xml)", o.SBOMInputFormat) } if o.SBOMInputFormat == ctypes.JSONInputFormat || looksLikeJSON { return ctypes.CycloneDXJSONMediaType, nil } return ctypes.CycloneDXXMLMediaType, nil case "spdx": if o.SBOMInputFormat != "" && o.SBOMInputFormat != ctypes.TextInputFormat && o.SBOMInputFormat != ctypes.JSONInputFormat { return "invalid", fmt.Errorf("invalid SBOM input format: %q, expected (json|text)", o.SBOMInputFormat) } if o.SBOMInputFormat == ctypes.JSONInputFormat || looksLikeJSON { return ctypes.SPDXJSONMediaType, nil } return ctypes.SPDXMediaType, nil case "syft": if o.SBOMInputFormat != "" && o.SBOMInputFormat != ctypes.JSONInputFormat { return "invalid", fmt.Errorf("invalid SBOM input format: %q, expected (json)", o.SBOMInputFormat) } return ctypes.SyftMediaType, nil default: return "unknown", fmt.Errorf("unknown SBOM type: %q, expected (spdx|cyclonedx|syft)", o.SBOMType) } } // AttachAttestationOptions is the top level wrapper for the attach attestation command. type AttachAttestationOptions struct { Attestations []string Registry RegistryOptions } // AddFlags implements Interface func (o *AttachAttestationOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) cmd.Flags().StringArrayVarP(&o.Attestations, "attestation", "", nil, "path to the attestation envelope") } cosign-2.5.0/cmd/cosign/cli/options/attest.go000066400000000000000000000112251477503325500211360ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "strings" "github.com/spf13/cobra" ) // AttestOptions is the top level wrapper for the attest command. type AttestOptions struct { Key string Cert string CertChain string NoUpload bool Recursive bool Replace bool SkipConfirmation bool TlogUpload bool TSAClientCACert string TSAClientCert string TSAClientKey string TSAServerName string TSAServerURL string RekorEntryType string RecordCreationTimestamp bool NewBundleFormat bool Rekor RekorOptions Fulcio FulcioOptions OIDC OIDCOptions SecurityKey SecurityKeyOptions Predicate PredicateLocalOptions Registry RegistryOptions } var _ Interface = (*AttestOptions)(nil) // AddFlags implements Interface func (o *AttestOptions) AddFlags(cmd *cobra.Command) { o.SecurityKey.AddFlags(cmd) o.Predicate.AddFlags(cmd) o.Fulcio.AddFlags(cmd) o.OIDC.AddFlags(cmd) o.Rekor.AddFlags(cmd) o.Registry.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the private key file, KMS URI or Kubernetes Secret") _ = cmd.MarkFlagFilename("key", privateKeyExts...) cmd.Flags().StringVar(&o.Cert, "certificate", "", "path to the X.509 certificate in PEM format to include in the OCI Signature") _ = cmd.MarkFlagFilename("certificate", certificateExts...) cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "", "path to a list of CA X.509 certificates in PEM format which will be needed "+ "when building the certificate chain for the signing certificate. "+ "Must start with the parent intermediate CA certificate of the "+ "signing certificate and end with the root certificate. Included in the OCI Signature") _ = cmd.MarkFlagFilename("certificate-chain", certificateExts...) cmd.Flags().BoolVar(&o.NoUpload, "no-upload", false, "do not upload the generated attestation") cmd.Flags().BoolVarP(&o.Recursive, "recursive", "r", false, "if a multi-arch image is specified, additionally sign each discrete image") cmd.Flags().BoolVarP(&o.Replace, "replace", "", false, "") cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false, "skip confirmation prompts for non-destructive operations") cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true, "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.RekorEntryType, "rekor-entry-type", rekorEntryTypes[0], "specifies the type to be used for a rekor entry upload ("+strings.Join(rekorEntryTypes, "|")+")") _ = cmd.RegisterFlagCompletionFunc("rekor-entry-type", cobra.FixedCompletions(rekorEntryTypes, cobra.ShellCompDirectiveNoFileComp)) cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "", "path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server") cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "", "path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server") cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "", "path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server") cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "", "SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server") cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") _ = cmd.RegisterFlagCompletionFunc("timestamp-server-url", cobra.NoFileCompletions) cmd.Flags().BoolVar(&o.RecordCreationTimestamp, "record-creation-timestamp", false, "set the createdAt timestamp in the attestation artifact to the time it was created; by default, cosign sets this to the zero value") cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "attach a Sigstore bundle using OCI referrers API") } cosign-2.5.0/cmd/cosign/cli/options/attest_blob.go000066400000000000000000000123151477503325500221350ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "strings" "github.com/spf13/cobra" ) // AttestOptions is the top level wrapper for the attest command. type AttestBlobOptions struct { Key string Cert string CertChain string SkipConfirmation bool TlogUpload bool TSAClientCACert string TSAClientCert string TSAClientKey string TSAServerName string TSAServerURL string RFC3161TimestampPath string Hash string Predicate PredicateLocalOptions OutputSignature string OutputAttestation string OutputCertificate string BundlePath string NewBundleFormat bool RekorEntryType string Rekor RekorOptions Fulcio FulcioOptions OIDC OIDCOptions SecurityKey SecurityKeyOptions } var _ Interface = (*AttestOptions)(nil) // AddFlags implements Interface func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) { o.Predicate.AddFlags(cmd) o.Rekor.AddFlags(cmd) o.Fulcio.AddFlags(cmd) o.OIDC.AddFlags(cmd) o.SecurityKey.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the private key file, KMS URI or Kubernetes Secret") _ = cmd.MarkFlagFilename("key", privateKeyExts...) cmd.Flags().StringVar(&o.Cert, "certificate", "", "path to the X.509 certificate in PEM format to include in the OCI Signature") _ = cmd.MarkFlagFilename("certificate", certificateExts...) cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "", "path to a list of CA X.509 certificates in PEM format which will be needed "+ "when building the certificate chain for the signing certificate. "+ "Must start with the parent intermediate CA certificate of the "+ "signing certificate and end with the root certificate. Included in the OCI Signature") _ = cmd.MarkFlagFilename("certificate-chain", certificateExts...) cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", "write the signature to FILE") _ = cmd.MarkFlagFilename("output-signature", signatureExts...) cmd.Flags().StringVar(&o.OutputAttestation, "output-attestation", "", "write the attestation to FILE") // _ = cmd.MarkFlagFilename("output-attestation") // no typical extensions cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", "write the certificate to FILE") _ = cmd.MarkFlagFilename("key", certificateExts...) cmd.Flags().StringVar(&o.BundlePath, "bundle", "", "write everything required to verify the blob to a FILE") _ = cmd.MarkFlagFilename("bundle", bundleExts...) // TODO: have this default to true as a breaking change cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "output bundle in new format that contains all verification material") cmd.Flags().StringVar(&o.Hash, "hash", "", "hash of blob in hexadecimal (base16). Used if you want to sign an artifact stored elsewhere and have the hash") _ = cmd.RegisterFlagCompletionFunc("hash", cobra.NoFileCompletions) cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false, "skip confirmation prompts for non-destructive operations") cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true, "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.RekorEntryType, "rekor-entry-type", rekorEntryTypes[0], "specifies the type to be used for a rekor entry upload ("+strings.Join(rekorEntryTypes, "|")+")") _ = cmd.RegisterFlagCompletionFunc("rekor-entry-type", cobra.FixedCompletions(rekorEntryTypes, cobra.ShellCompDirectiveNoFileComp)) cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "", "path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server") cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "", "path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server") cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "", "path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server") cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "", "SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server") cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") _ = cmd.RegisterFlagCompletionFunc("timestamp-server-url", cobra.NoFileCompletions) cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp-bundle", "", "path to an RFC 3161 timestamp bundle FILE") // _ = cmd.MarkFlagFilename("rfc3161-timestamp-bundle") // no typical extensions } cosign-2.5.0/cmd/cosign/cli/options/bundle.go000066400000000000000000000064321477503325500211070ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "strings" "github.com/spf13/cobra" ) type BundleCreateOptions struct { Artifact string AttestationPath string BundlePath string CertificatePath string IgnoreTlog bool KeyRef string Out string RekorURL string RFC3161TimestampPath string SignaturePath string Sk bool Slot string } var _ Interface = (*BundleCreateOptions)(nil) func (o *BundleCreateOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Artifact, "artifact", "", "path to artifact FILE") // _ = cmd.MarkFlagFilename("artifact") // no typical extensions cmd.Flags().StringVar(&o.AttestationPath, "attestation", "", "path to attestation FILE") // _ = cmd.MarkFlagFilename("attestation") // no typical extensions cmd.Flags().StringVar(&o.BundlePath, "bundle", "", "path to old format bundle FILE") _ = cmd.MarkFlagFilename("bundle", bundleExts...) cmd.Flags().StringVar(&o.CertificatePath, "certificate", "", "path to the signing certificate, likely from Fulco.") _ = cmd.MarkFlagFilename("certificate", certificateExts...) cmd.Flags().BoolVar(&o.IgnoreTlog, "ignore-tlog", false, "ignore transparency log verification, to be used when an artifact "+ "signature has not been uploaded to the transparency log.") cmd.Flags().StringVar(&o.KeyRef, "key", "", "path to the public key file, KMS URI or Kubernetes Secret") _ = cmd.MarkFlagFilename("key", publicKeyExts...) cmd.Flags().StringVar(&o.Out, "out", "", "path to output bundle") _ = cmd.MarkFlagFilename("out", bundleExts...) cmd.Flags().StringVar(&o.RekorURL, "rekor-url", "https://rekor.sigstore.dev", "address of rekor STL server") _ = cmd.RegisterFlagCompletionFunc("rekor-url", cobra.NoFileCompletions) cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "path to RFC3161 timestamp FILE") // _ = cmd.MarkFlagFilename("rfc3161-timestamp") // no typical extensions cmd.Flags().StringVar(&o.SignaturePath, "signature", "", "path to base64-encoded signature over attestation in DSSE format") _ = cmd.MarkFlagFilename("signature", signatureExts...) cmd.Flags().BoolVar(&o.Sk, "sk", false, "whether to use a hardware security key") slots := []string{"authentication", "signature", "card-authentication", "key-management"} cmd.Flags().StringVar(&o.Slot, "slot", "signature", "security key slot to use for generated key ("+ strings.Join(slots, "|")+")") _ = cmd.RegisterFlagCompletionFunc("slot", cobra.FixedCompletions(slots, cobra.ShellCompDirectiveNoFileComp)) cmd.MarkFlagsMutuallyExclusive("bundle", "certificate") cmd.MarkFlagsMutuallyExclusive("bundle", "signature") } cosign-2.5.0/cmd/cosign/cli/options/certificate.go000066400000000000000000000151201477503325500221120ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "errors" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/spf13/cobra" ) // CertVerifyOptions is the wrapper for certificate verification. type CertVerifyOptions struct { Cert string CertIdentity string CertIdentityRegexp string CertOidcIssuer string CertOidcIssuerRegexp string CertGithubWorkflowTrigger string CertGithubWorkflowSha string CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string CAIntermediates string CARoots string CertChain string SCT string IgnoreSCT bool } var _ Interface = (*RekorOptions)(nil) // AddFlags implements Interface func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Cert, "certificate", "", "path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed.") _ = cmd.MarkFlagFilename("certificate", certificateExts...) cmd.Flags().StringVar(&o.CertIdentity, "certificate-identity", "", "The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows.") cmd.Flags().StringVar(&o.CertIdentityRegexp, "certificate-identity-regexp", "", "A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows.") cmd.Flags().StringVar(&o.CertOidcIssuer, "certificate-oidc-issuer", "", "The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows.") cmd.Flags().StringVar(&o.CertOidcIssuerRegexp, "certificate-oidc-issuer-regexp", "", "A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows.") // -- Cert extensions begin -- // Source: https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md cmd.Flags().StringVar(&o.CertGithubWorkflowTrigger, "certificate-github-workflow-trigger", "", "contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run") cmd.Flags().StringVar(&o.CertGithubWorkflowSha, "certificate-github-workflow-sha", "", "contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon.") cmd.Flags().StringVar(&o.CertGithubWorkflowName, "certificate-github-workflow-name", "", "contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow.") cmd.Flags().StringVar(&o.CertGithubWorkflowRepository, "certificate-github-workflow-repository", "", "contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon") cmd.Flags().StringVar(&o.CertGithubWorkflowRef, "certificate-github-workflow-ref", "", "contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon.") // -- Cert extensions end -- cmd.Flags().StringVar(&o.CAIntermediates, "ca-intermediates", "", "path to a file of intermediate CA certificates in PEM format which will be needed "+ "when building the certificate chains for the signing certificate. "+ "The flag is optional and must be used together with --ca-roots, conflicts with "+ "--certificate-chain.") _ = cmd.MarkFlagFilename("ca-intermediates", certificateExts...) cmd.Flags().StringVar(&o.CARoots, "ca-roots", "", "path to a bundle file of CA certificates in PEM format which will be needed "+ "when building the certificate chains for the signing certificate. Conflicts with --certificate-chain.") _ = cmd.MarkFlagFilename("ca-roots", certificateExts...) cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "", "path to a list of CA certificates in PEM format which will be needed "+ "when building the certificate chain for the signing certificate. "+ "Must start with the parent intermediate CA certificate of the "+ "signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates.") _ = cmd.MarkFlagFilename("certificate-chain", certificateExts...) cmd.MarkFlagsMutuallyExclusive("ca-roots", "certificate-chain") cmd.MarkFlagsMutuallyExclusive("ca-intermediates", "certificate-chain") cmd.Flags().StringVar(&o.SCT, "sct", "", "path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. "+ "If a certificate contains an SCT, verification will check both the detached and embedded SCTs.") // _ = cmd.MarkFlagFilename("sct") // no typical extensions cmd.Flags().BoolVar(&o.IgnoreSCT, "insecure-ignore-sct", false, "when set, verification will not check that a certificate contains an embedded SCT, a proof of "+ "inclusion in a certificate transparency log") } func (o *CertVerifyOptions) Identities() ([]cosign.Identity, error) { if o.CertIdentity == "" && o.CertIdentityRegexp == "" { return nil, errors.New("--certificate-identity or --certificate-identity-regexp is required for verification in keyless mode") } if o.CertOidcIssuer == "" && o.CertOidcIssuerRegexp == "" { return nil, errors.New("--certificate-oidc-issuer or --certificate-oidc-issuer-regexp is required for verification in keyless mode") } return []cosign.Identity{{IssuerRegExp: o.CertOidcIssuerRegexp, Issuer: o.CertOidcIssuer, SubjectRegExp: o.CertIdentityRegexp, Subject: o.CertIdentity}}, nil } cosign-2.5.0/cmd/cosign/cli/options/clean.go000066400000000000000000000036551477503325500207240ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "errors" "github.com/spf13/cobra" ) type CleanType string const ( CleanTypeSignature CleanType = "signature" CleanTypeAttestation CleanType = "attestation" CleanTypeSbom CleanType = "sbom" CleanTypeAll CleanType = "all" ) func defaultCleanType() CleanType { return CleanTypeAll } // cleanType implements github.com/spf13/pflag.Value. func (c *CleanType) String() string { return string(*c) } // cleanType implements github.com/spf13/pflag.Value. func (c *CleanType) Set(v string) error { switch v { case "signature", "attestation", "sbom", "all": *c = CleanType(v) return nil default: return errors.New(`must be one of "signature", "attestation", "sbom", or "all"`) } } // cleanType implements github.com/spf13/pflag.Value. func (c *CleanType) Type() string { return "CLEAN_TYPE" } type CleanOptions struct { Registry RegistryOptions CleanType CleanType Force bool } var _ Interface = (*CleanOptions)(nil) func (c *CleanOptions) AddFlags(cmd *cobra.Command) { c.Registry.AddFlags(cmd) c.CleanType = defaultCleanType() cmd.Flags().Var(&c.CleanType, "type", "a type of clean: (sbom is deprecated)") // TODO(#2044): Rename to --skip-confirmation for consistency? cmd.Flags().BoolVarP(&c.Force, "force", "f", false, "do not prompt for confirmation") } cosign-2.5.0/cmd/cosign/cli/options/copy.go000066400000000000000000000027741477503325500206150ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // CopyOptions is the top level wrapper for the copy command. type CopyOptions struct { CopyOnly []string SignatureOnly bool Force bool Platform string Registry RegistryOptions } var _ Interface = (*CopyOptions)(nil) // AddFlags implements Interface func (o *CopyOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) cmd.Flags().StringSliceVar(&o.CopyOnly, "only", []string{}, "custom string array to only copy specific items, this flag is comma delimited. ex: --only=sig,att,sbom") cmd.Flags().BoolVar(&o.SignatureOnly, "sig-only", false, "[DEPRECATED] only copy the image signature") cmd.Flags().BoolVarP(&o.Force, "force", "f", false, "overwrite destination image(s), if necessary") cmd.Flags().StringVar(&o.Platform, "platform", "", "only copy container image and its signatures for a specific platform image") } cosign-2.5.0/cmd/cosign/cli/options/deprecate.go000066400000000000000000000021741477503325500215710ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options const SBOMAttachmentDeprecation = "WARNING: SBOM attachments are deprecated " + "and support will be removed in a Cosign release soon after 2024-02-22 " + "(see https://github.com/sigstore/cosign/issues/2755). " + "Instead, please use SBOM attestations." const RootWithoutChecksumDeprecation = "WARNING: Fetching initial root from URL " + "without providing its checksum is deprecated and will be disallowed in " + "a future Cosign release. Please provide the initial root checksum " + "via the --root-checksum argument." cosign-2.5.0/cmd/cosign/cli/options/download.go000066400000000000000000000030521477503325500214400ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import "github.com/spf13/cobra" // DownloadOptions is the struct for control type SBOMDownloadOptions struct { Platform string // Platform to download sboms } type AttestationDownloadOptions struct { PredicateType string // Predicate type of attestation to retrieve Platform string // Platform to download attestations } var _ Interface = (*SBOMDownloadOptions)(nil) var _ Interface = (*AttestationDownloadOptions)(nil) // AddFlags implements Interface func (o *SBOMDownloadOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Platform, "platform", "", "download SBOM for a specific platform image") } // AddFlags implements Interface func (o *AttestationDownloadOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.PredicateType, "predicate-type", "", "download attestation with matching predicateType") cmd.Flags().StringVar(&o.Platform, "platform", "", "download attestation for a specific platform image") } cosign-2.5.0/cmd/cosign/cli/options/env.go000066400000000000000000000022221477503325500204170ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // EnvOptions is the top level wrapper for the env command. type EnvOptions struct { ShowDescriptions bool ShowSensitiveValues bool } var _ Interface = (*EnvOptions)(nil) // AddFlags implements Interface func (o *EnvOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.ShowDescriptions, "show-descriptions", true, "show descriptions for environment variables") cmd.Flags().BoolVar(&o.ShowSensitiveValues, "show-sensitive-values", false, "show values of sensitive environment variables") } cosign-2.5.0/cmd/cosign/cli/options/errors.go000066400000000000000000000022331477503325500211450ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options // KeyParseError is an error returned when an incorrect set of key flags // are parsed by the CLI type KeyParseError struct{} // PubKeyParseError is an error returned when an incorrect set of public key // flags are parsed by the CLI type PubKeyParseError struct{} func (e *KeyParseError) Error() string { return "exactly one of: key reference (--key), or hardware token (--sk) must be provided" } func (e *PubKeyParseError) Error() string { return "exactly one of: key reference (--key), certificate (--cert) or hardware token (--sk) must be provided" } cosign-2.5.0/cmd/cosign/cli/options/experimental.go000066400000000000000000000015031477503325500223250ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "strconv" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) func EnableExperimental() bool { if b, err := strconv.ParseBool(env.Getenv(env.VariableExperimental)); err == nil { return b } return false } cosign-2.5.0/cmd/cosign/cli/options/files.go000066400000000000000000000030241477503325500207320ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "fmt" "strings" "github.com/spf13/cobra" cremote "github.com/sigstore/cosign/v2/pkg/cosign/remote" ) // FilesOptions is the wrapper for the files. type FilesOptions struct { Files []string } var _ Interface = (*FilesOptions)(nil) func (o *FilesOptions) Parse() ([]cremote.File, error) { fs := cremote.FilesFromFlagList(o.Files) // If we have multiple files, each file must have a platform. if len(fs) > 1 { for _, f := range fs { if f.Platform() == nil { return nil, fmt.Errorf("each file must include a unique platform, %s had no platform", f.Path()) } } } return fs, nil } func (o *FilesOptions) String() string { return strings.Join(o.Files, ",") } // AddFlags implements Interface func (o *FilesOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringSliceVarP(&o.Files, "files", "f", nil, ":[platform/arch]") // _ = cmd.MarkFlagFilename("files") // no typical extensions } cosign-2.5.0/cmd/cosign/cli/options/flags.go000066400000000000000000000017271477503325500207340ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "reflect" ) // OneOf ensures that only one of the supplied interfaces is set to a non-zero value. func OneOf(args ...interface{}) bool { return NOf(args...) == 1 } // NOf returns how many of the fields are non-zero func NOf(args ...interface{}) int { n := 0 for _, arg := range args { if !reflect.ValueOf(arg).IsZero() { n++ } } return n } cosign-2.5.0/cmd/cosign/cli/options/flags_test.go000066400000000000000000000033331477503325500217660ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import "testing" func TestOneOf(t *testing.T) { type dumbStruct struct { A int B []string C bool } tests := []struct { name string args []interface{} want bool }{ { name: "3/3", args: []interface{}{"one", 2, true}, want: false, }, { name: "2/3", args: []interface{}{"one", 2, false}, want: false, }, { name: "1/3", args: []interface{}{"", 2, false}, want: true, }, { name: "0/1", args: []interface{}{""}, want: false, }, { name: "1/1", args: []interface{}{"hey"}, want: true, }, { name: "structs", args: []interface{}{"hey", dumbStruct{A: 2}}, want: false, }, { name: "struct", args: []interface{}{"", dumbStruct{A: 2}}, want: true, }, { name: "pointers", args: []interface{}{"hey", &struct{ a int }{a: 2}, false}, want: false, }, { name: "pointer", args: []interface{}{&struct{ a int }{a: 2}, false}, want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := OneOf(tt.args...); got != tt.want { t.Errorf("OneOf() = %v, want %v", got, tt.want) } }) } } cosign-2.5.0/cmd/cosign/cli/options/fulcio.go000066400000000000000000000036151477503325500211170ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) const DefaultFulcioURL = "https://fulcio.sigstore.dev" // FulcioOptions is the wrapper for Fulcio related options. type FulcioOptions struct { URL string AuthFlow string IdentityToken string InsecureSkipFulcioVerify bool } var _ Interface = (*FulcioOptions)(nil) // AddFlags implements Interface func (o *FulcioOptions) AddFlags(cmd *cobra.Command) { // TODO: change this back to api.SigstorePublicServerURL after the v1 migration is complete. cmd.Flags().StringVar(&o.URL, "fulcio-url", DefaultFulcioURL, "address of sigstore PKI server") cmd.Flags().StringVar(&o.IdentityToken, "identity-token", "", "identity token to use for certificate from fulcio. the token or a path to a file containing the token is accepted.") // _ = cmd.MarkFlagFilename("identity-token") // no typical extensions cmd.Flags().StringVar(&o.AuthFlow, "fulcio-auth-flow", "", "fulcio interactive oauth2 flow to use for certificate from fulcio. Defaults to determining the flow based on the runtime environment. (options) normal|device|token|client_credentials") cmd.Flags().BoolVar(&o.InsecureSkipFulcioVerify, "insecure-skip-verify", false, "skip verifying fulcio published to the SCT (this should only be used for testing).") } cosign-2.5.0/cmd/cosign/cli/options/generate.go000066400000000000000000000017511477503325500214270ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // GenerateOptions is the top level wrapper for the generate command. type GenerateOptions struct { AnnotationOptions Registry RegistryOptions } var _ Interface = (*GenerateOptions)(nil) // AddFlags implements Interface func (o *GenerateOptions) AddFlags(cmd *cobra.Command) { o.AnnotationOptions.AddFlags(cmd) o.Registry.AddFlags(cmd) } cosign-2.5.0/cmd/cosign/cli/options/generate_key_pair.go000066400000000000000000000023461477503325500233130ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // GenerateKeyPairOptions is the top level wrapper for the generate-key-pair command. type GenerateKeyPairOptions struct { // KMS Key Management Service KMS string OutputKeyPrefix string } var _ Interface = (*GenerateKeyPairOptions)(nil) // AddFlags implements Interface func (o *GenerateKeyPairOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.KMS, "kms", "", "create key pair in KMS service to use for signing") cmd.Flags().StringVar(&o.OutputKeyPrefix, "output-key-prefix", "cosign", "name used for generated .pub and .key files (defaults to `cosign`)") } cosign-2.5.0/cmd/cosign/cli/options/import_key_pair.go000066400000000000000000000030161477503325500230260ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // ImportKeyPairOptions is the top level wrapper for the import-key-pair command. type ImportKeyPairOptions struct { // Local key file generated by external program such as OpenSSL Key string // Filename used for outputted keys OutputKeyPrefix string SkipConfirmation bool } var _ Interface = (*ImportKeyPairOptions)(nil) // AddFlags implements Interface func (o *ImportKeyPairOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&o.Key, "key", "k", "", "import key pair to use for signing") _ = cmd.MarkFlagFilename("key", privateKeyExts...) cmd.Flags().StringVarP(&o.OutputKeyPrefix, "output-key-prefix", "o", "import-cosign", "name used for outputted key pairs") // _ = cmd.MarkFlagFilename("output-key-prefix") // no typical extensions cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false, "skip confirmation prompts for overwriting existing key") } cosign-2.5.0/cmd/cosign/cli/options/initialize.go000066400000000000000000000030221477503325500217670ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/sigstore/sigstore/pkg/tuf" "github.com/spf13/cobra" ) // InitializeOptions is the top level wrapper for the initialize command. type InitializeOptions struct { Mirror string Root string RootChecksum string } var _ Interface = (*InitializeOptions)(nil) // AddFlags implements Interface func (o *InitializeOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Mirror, "mirror", tuf.DefaultRemoteRoot, "GCS bucket to a SigStore TUF repository, or HTTP(S) base URL, or file:/// for local filestore remote (air-gap)") cmd.Flags().StringVar(&o.Root, "root", "", "path to trusted initial root. defaults to embedded root") _ = cmd.MarkFlagDirname("root") cmd.Flags().StringVar(&o.RootChecksum, "root-checksum", "", "checksum of the initial root, required if root is downloaded via http(s). expects sha256 by default, can be changed to sha512 by providing sha512:") } cosign-2.5.0/cmd/cosign/cli/options/key.go000066400000000000000000000040101477503325500204140ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import "github.com/sigstore/cosign/v2/pkg/cosign" type KeyOpts struct { Sk bool Slot string KeyRef string FulcioURL string RekorURL string IDToken string PassFunc cosign.PassFunc OIDCIssuer string OIDCClientID string OIDCClientSecret string OIDCRedirectURL string OIDCDisableProviders bool // Disable OIDC credential providers in keyless signer OIDCProvider string // Specify which OIDC credential provider to use for keyless signer BundlePath string NewBundleFormat bool SkipConfirmation bool TSAClientCACert string TSAClientCert string TSAClientKey string TSAServerName string // expected SAN field in the TSA server's certificate - https://pkg.go.dev/crypto/tls#Config.ServerName TSAServerURL string RFC3161TimestampPath string TSACertChainPath string // IssueCertificate controls whether to issue a certificate when a key is // provided. IssueCertificateForExistingKey bool // FulcioAuthFlow is the auth flow to use when authenticating against // Fulcio. See https://pkg.go.dev/github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio#pkg-constants // for valid values. FulcioAuthFlow string // Modeled after InsecureSkipVerify in tls.Config, this disables // verifying the SCT. InsecureSkipFulcioVerify bool } cosign-2.5.0/cmd/cosign/cli/options/load.go000066400000000000000000000021431477503325500205500ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // LoadOptions is the top level wrapper for the load command. type LoadOptions struct { Directory string Registry RegistryOptions } var _ Interface = (*LoadOptions)(nil) // AddFlags implements Interface func (o *LoadOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) cmd.Flags().StringVar(&o.Directory, "dir", "", "path to directory where the signed image is stored on disk") _ = cmd.MarkFlagDirname("dir") _ = cmd.MarkFlagRequired("dir") } cosign-2.5.0/cmd/cosign/cli/options/oidc.go000066400000000000000000000052451477503325500205550ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "fmt" "os" "strings" "unicode/utf8" "github.com/spf13/cobra" ) const DefaultOIDCIssuerURL = "https://oauth2.sigstore.dev/auth" // OIDCOptions is the wrapper for OIDC related options. type OIDCOptions struct { Issuer string ClientID string clientSecretFile string RedirectURL string Provider string DisableAmbientProviders bool } func (o *OIDCOptions) ClientSecret() (string, error) { if o.clientSecretFile != "" { clientSecretBytes, err := os.ReadFile(o.clientSecretFile) if err != nil { return "", fmt.Errorf("reading OIDC client secret: %w", err) } if !utf8.Valid(clientSecretBytes) { return "", fmt.Errorf("OIDC client secret in file %s not valid utf8", o.clientSecretFile) } clientSecretString := string(clientSecretBytes) clientSecretString = strings.TrimSpace(clientSecretString) return clientSecretString, nil } return "", nil } var _ Interface = (*OIDCOptions)(nil) // AddFlags implements Interface func (o *OIDCOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Issuer, "oidc-issuer", DefaultOIDCIssuerURL, "OIDC provider to be used to issue ID token") cmd.Flags().StringVar(&o.ClientID, "oidc-client-id", "sigstore", "OIDC client ID for application") cmd.Flags().StringVar(&o.clientSecretFile, "oidc-client-secret-file", "", "Path to file containing OIDC client secret for application") // _ = cmd.MarkFlagFilename("oidc-client-secret-file") // no typical extensions cmd.Flags().StringVar(&o.RedirectURL, "oidc-redirect-url", "", "OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'.") cmd.Flags().StringVar(&o.Provider, "oidc-provider", "", "Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github-actions, filesystem, buildkite-agent]") cmd.Flags().BoolVar(&o.DisableAmbientProviders, "oidc-disable-ambient-providers", false, "Disable ambient OIDC providers. When true, ambient credentials will not be read") } cosign-2.5.0/cmd/cosign/cli/options/options.go000066400000000000000000000023441477503325500213270ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import "github.com/spf13/cobra" type Interface interface { // AddFlags adds this options' flags to the cobra command. AddFlags(cmd *cobra.Command) } var bundleExts = []string{ "bundle", } var certificateExts = []string{ "cert", "crt", "pem", } var logExts = []string{ "log", } var moduleExts = []string{ "dll", "dylib", "so", } var privateKeyExts = []string{ "key", } var publicKeyExts = []string{ "pub", } var sbomExts = []string{ "json", "xml", "spdx", } var signatureExts = []string{ "sig", } var wasmExts = []string{ "wasm", } var rekorEntryTypes = []string{ "dsse", // first one is the default "intoto", } cosign-2.5.0/cmd/cosign/cli/options/piv_tool.go000066400000000000000000000104011477503325500214600ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // PIVToolSetManagementKeyOptions is the wrapper for `piv-tool set-management-key` related options. type PIVToolSetManagementKeyOptions struct { OldKey string NewKey string RandomKey bool } var _ Interface = (*PIVToolSetManagementKeyOptions)(nil) // AddFlags implements Interface func (o *PIVToolSetManagementKeyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.OldKey, "old-key", "", "existing management key, uses default if empty") cmd.Flags().StringVar(&o.NewKey, "new-key", "", "new management key, uses default if empty") cmd.Flags().BoolVar(&o.RandomKey, "random-management-key", false, "if set to true, generates a new random management key and deletes it after") } // PIVToolSetPINOptions is the wrapper for `piv-tool set-pin` related options. type PIVToolSetPINOptions struct { OldPIN string NewPIN string } var _ Interface = (*PIVToolSetPINOptions)(nil) // AddFlags implements Interface func (o *PIVToolSetPINOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.OldPIN, "old-pin", "", "existing PIN, uses default if empty") cmd.Flags().StringVar(&o.NewPIN, "new-pin", "", "new PIN, uses default if empty") } // PIVToolSetPUKOptions is the wrapper for `piv-tool set-puk` related options. type PIVToolSetPUKOptions struct { OldPUK string NewPUK string } var _ Interface = (*PIVToolSetPUKOptions)(nil) // AddFlags implements Interface func (o *PIVToolSetPUKOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.OldPUK, "old-puk", "", "existing PUK, uses default if empty") cmd.Flags().StringVar(&o.NewPUK, "new-puk", "", "new PUK, uses default if empty") } // PIVToolUnblockOptions is the wrapper for `piv-tool unblock` related options. type PIVToolUnblockOptions struct { PUK string NewPIN string } var _ Interface = (*PIVToolUnblockOptions)(nil) // AddFlags implements Interface func (o *PIVToolUnblockOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.PUK, "puk", "", "existing PUK, uses default if empty") cmd.Flags().StringVar(&o.NewPIN, "new-PIN", "", "new PIN, uses default if empty") } // PIVToolAttestationOptions is the wrapper for `piv-tool attestation` related options. type PIVToolAttestationOptions struct { Output string Slot string } var _ Interface = (*PIVToolAttestationOptions)(nil) // AddFlags implements Interface func (o *PIVToolAttestationOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&o.Output, "output", "o", "text", "format to output attestation information in. (text|json)") cmd.Flags().StringVar(&o.Slot, "slot", "", "Slot to use for generated key (authentication|signature|card-authentication|key-management)") } // PIVToolGenerateKeyOptions is the wrapper for `piv-tool generate-key` related options. type PIVToolGenerateKeyOptions struct { ManagementKey string RandomKey bool Slot string PINPolicy string TouchPolicy string } var _ Interface = (*PIVToolGenerateKeyOptions)(nil) // AddFlags implements Interface func (o *PIVToolGenerateKeyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.ManagementKey, "management-key", "", "management key, uses default if empty") cmd.Flags().BoolVar(&o.RandomKey, "random-management-key", false, "if set to true, generates a new random management key and deletes it after") cmd.Flags().StringVar(&o.Slot, "slot", "", "Slot to use for generated key (authentication|signature|card-authentication|key-management)") cmd.Flags().StringVar(&o.PINPolicy, "pin-policy", "", "PIN policy for slot (never|once|always)") cmd.Flags().StringVar(&o.TouchPolicy, "touch-policy", "", "Touch policy for slot (never|always|cached)") } cosign-2.5.0/cmd/cosign/cli/options/pkcs11_tool.go000066400000000000000000000036701477503325500217760ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/spf13/cobra" ) // PKCS11ToolListTokens is the wrapper for `pkcs11-tool list-tokens` related options. type PKCS11ToolListTokensOptions struct { ModulePath string } var _ Interface = (*PKCS11ToolListTokensOptions)(nil) // AddFlags implements Interface func (o *PKCS11ToolListTokensOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.ModulePath, "module-path", env.Getenv(env.VariablePKCS11ModulePath), "absolute path to the PKCS11 module") _ = cmd.MarkFlagFilename("module-path", moduleExts...) } // PKCS11ToolListKeysUrisOptions is the wrapper for `pkcs11-tool list-keys-uris` related options. type PKCS11ToolListKeysUrisOptions struct { ModulePath string SlotID uint Pin string } var _ Interface = (*PKCS11ToolListKeysUrisOptions)(nil) // AddFlags implements Interface func (o *PKCS11ToolListKeysUrisOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.ModulePath, "module-path", env.Getenv(env.VariablePKCS11ModulePath), "absolute path to the PKCS11 module") _ = cmd.MarkFlagFilename("module-path", moduleExts...) cmd.Flags().UintVar(&o.SlotID, "slot-id", 0, "id of the PKCS11 slot, uses 0 if empty") cmd.Flags().StringVar(&o.Pin, "pin", "", "pin of the PKCS11 slot, uses environment variable COSIGN_PKCS11_PIN if empty") } cosign-2.5.0/cmd/cosign/cli/options/predicate.go000066400000000000000000000067141477503325500216010ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "fmt" "net/url" slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" "github.com/in-toto/in-toto-golang/in_toto" "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/pkg/cosign/attestation" ) const ( PredicateCustom = "custom" PredicateSLSA = "slsaprovenance" PredicateSLSA02 = "slsaprovenance02" PredicateSLSA1 = "slsaprovenance1" PredicateSPDX = "spdx" PredicateSPDXJSON = "spdxjson" PredicateCycloneDX = "cyclonedx" PredicateLink = "link" PredicateVuln = "vuln" PredicateOpenVEX = "openvex" ) // PredicateTypeMap is the mapping between the predicate `type` option to predicate URI. var PredicateTypeMap = map[string]string{ PredicateCustom: attestation.CosignCustomProvenanceV01, PredicateSLSA: slsa02.PredicateSLSAProvenance, PredicateSLSA02: slsa02.PredicateSLSAProvenance, PredicateSLSA1: slsa1.PredicateSLSAProvenance, PredicateSPDX: in_toto.PredicateSPDX, PredicateSPDXJSON: in_toto.PredicateSPDX, PredicateCycloneDX: in_toto.PredicateCycloneDX, PredicateLink: in_toto.PredicateLinkV1, PredicateVuln: attestation.CosignVulnProvenanceV01, PredicateOpenVEX: attestation.OpenVexNamespace, } // PredicateOptions is the wrapper for predicate related options. type PredicateOptions struct { Type string } var _ Interface = (*PredicateOptions)(nil) // AddFlags implements Interface func (o *PredicateOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Type, "type", "custom", "specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI") } // ParsePredicateType parses the predicate `type` flag passed into a predicate URI, or validates `type` is a valid URI. func ParsePredicateType(t string) (string, error) { uri, ok := PredicateTypeMap[t] if !ok { if _, err := url.ParseRequestURI(t); err != nil { return "", fmt.Errorf("invalid predicate type: %s", t) } uri = t } return uri, nil } // PredicateLocalOptions is the wrapper for predicate related options. type PredicateLocalOptions struct { PredicateOptions Path string } var _ Interface = (*PredicateLocalOptions)(nil) // AddFlags implements Interface func (o *PredicateLocalOptions) AddFlags(cmd *cobra.Command) { o.PredicateOptions.AddFlags(cmd) cmd.Flags().StringVar(&o.Path, "predicate", "", "path to the predicate file.") _ = cmd.MarkFlagFilename("predicate", sbomExts...) _ = cmd.MarkFlagRequired("predicate") } // PredicateRemoteOptions is the wrapper for remote predicate related options. type PredicateRemoteOptions struct { PredicateOptions } var _ Interface = (*PredicateRemoteOptions)(nil) // AddFlags implements Interface func (o *PredicateRemoteOptions) AddFlags(cmd *cobra.Command) { o.PredicateOptions.AddFlags(cmd) } cosign-2.5.0/cmd/cosign/cli/options/public_key.go000066400000000000000000000024611477503325500217620ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // PublicKeyOptions is the top level wrapper for the public-key command. type PublicKeyOptions struct { Key string SecurityKey SecurityKeyOptions OutFile string } var _ Interface = (*PublicKeyOptions)(nil) // AddFlags implements Interface func (o *PublicKeyOptions) AddFlags(cmd *cobra.Command) { o.SecurityKey.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the private key file, KMS URI or Kubernetes Secret") _ = cmd.MarkFlagFilename("key", privateKeyExts...) cmd.Flags().StringVar(&o.OutFile, "outfile", "", "path to a payload file to use rather than generating one") _ = cmd.MarkFlagFilename("outfile", publicKeyExts...) } cosign-2.5.0/cmd/cosign/cli/options/reference.go000066400000000000000000000021541477503325500215710ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // ReferenceOptions is a wrapper for image reference options. type ReferenceOptions struct { TagPrefix string } var _ Interface = (*ReferenceOptions)(nil) // AddFlags implements Interface func (o *ReferenceOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.TagPrefix, "attachment-tag-prefix", "", "optional custom prefix to use for attached image tags. Attachment images are tagged as: `[AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName]`") } cosign-2.5.0/cmd/cosign/cli/options/registry.go000066400000000000000000000210731477503325500215040ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "context" "crypto/tls" "crypto/x509" "errors" "fmt" "io" "net/http" "os" ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login" "github.com/chrismellard/docker-credential-acr-env/pkg/credhelper" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/authn/github" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/google" "github.com/google/go-containerregistry/pkg/v1/remote" alibabaacr "github.com/mozillazg/docker-credential-acr-helper/pkg/credhelper" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/spf13/cobra" ) // Keychain is an alias of authn.Keychain to expose this configuration option to consumers of this lib type Keychain = authn.Keychain // RegistryOptions is the wrapper for the registry options. type RegistryOptions struct { AllowInsecure bool AllowHTTPRegistry bool KubernetesKeychain bool RefOpts ReferenceOptions Keychain Keychain AuthConfig authn.AuthConfig RegistryCACert string RegistryClientCert string RegistryClientKey string RegistryServerName string // RegistryClientOpts allows overriding the result of GetRegistryClientOpts. RegistryClientOpts []remote.Option } var _ Interface = (*RegistryOptions)(nil) // AddFlags implements Interface func (o *RegistryOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.AllowInsecure, "allow-insecure-registry", false, "whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing") cmd.Flags().BoolVar(&o.AllowHTTPRegistry, "allow-http-registry", false, "whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing") cmd.Flags().BoolVar(&o.KubernetesKeychain, "k8s-keychain", false, "whether to use the kubernetes keychain instead of the default keychain (supports workload identity).") cmd.Flags().StringVar(&o.AuthConfig.Username, "registry-username", "", "registry basic auth username") cmd.Flags().StringVar(&o.AuthConfig.Password, "registry-password", "", "registry basic auth password") cmd.Flags().StringVar(&o.AuthConfig.RegistryToken, "registry-token", "", "registry bearer auth token") cmd.Flags().StringVar(&o.RegistryCACert, "registry-cacert", "", "path to the X.509 CA certificate file in PEM format to be used for the connection to the registry") _ = cmd.MarkFlagFilename("registry-cacert", certificateExts...) cmd.Flags().StringVar(&o.RegistryClientCert, "registry-client-cert", "", "path to the X.509 certificate file in PEM format to be used for the connection to the registry") _ = cmd.MarkFlagFilename("registry-client-cert", certificateExts...) cmd.Flags().StringVar(&o.RegistryClientKey, "registry-client-key", "", "path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry") _ = cmd.MarkFlagFilename("registry-client-key", privateKeyExts...) cmd.Flags().StringVar(&o.RegistryServerName, "registry-server-name", "", "SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry") o.RefOpts.AddFlags(cmd) } func (o *RegistryOptions) ClientOpts(ctx context.Context) ([]ociremote.Option, error) { opts := []ociremote.Option{ociremote.WithRemoteOptions(o.GetRegistryClientOpts(ctx)...)} if o.RefOpts.TagPrefix != "" { opts = append(opts, ociremote.WithPrefix(o.RefOpts.TagPrefix)) } targetRepoOverride, err := ociremote.GetEnvTargetRepository() if err != nil { return nil, err } if (targetRepoOverride != name.Repository{}) { opts = append(opts, ociremote.WithTargetRepository(targetRepoOverride)) } return opts, nil } func (o *RegistryOptions) NameOptions() []name.Option { var nameOpts []name.Option if o.AllowHTTPRegistry { nameOpts = append(nameOpts, name.Insecure) } return nameOpts } func (o *RegistryOptions) GetRegistryClientOpts(ctx context.Context) []remote.Option { if o.RegistryClientOpts != nil { ropts := o.RegistryClientOpts ropts = append(ropts, remote.WithContext(ctx)) return ropts } opts := []remote.Option{ remote.WithContext(ctx), remote.WithUserAgent(UserAgent()), } switch { case o.Keychain != nil: opts = append(opts, remote.WithAuthFromKeychain(o.Keychain)) case o.KubernetesKeychain: kc := authn.NewMultiKeychain( authn.DefaultKeychain, google.Keychain, authn.NewKeychainFromHelper(ecr.NewECRHelper(ecr.WithLogger(io.Discard))), authn.NewKeychainFromHelper(credhelper.NewACRCredentialsHelper()), authn.NewKeychainFromHelper(alibabaacr.NewACRHelper().WithLoggerOut(io.Discard)), github.Keychain, ) opts = append(opts, remote.WithAuthFromKeychain(kc)) case o.AuthConfig.Username != "" && o.AuthConfig.Password != "": opts = append(opts, remote.WithAuth(&authn.Basic{Username: o.AuthConfig.Username, Password: o.AuthConfig.Password})) case o.AuthConfig.RegistryToken != "": opts = append(opts, remote.WithAuth(&authn.Bearer{Token: o.AuthConfig.RegistryToken})) default: opts = append(opts, remote.WithAuthFromKeychain(authn.DefaultKeychain)) } tlsConfig, err := o.getTLSConfig() if err == nil { tr := http.DefaultTransport.(*http.Transport).Clone() tr.TLSClientConfig = tlsConfig opts = append(opts, remote.WithTransport(tr)) } // Reuse a remote.Pusher and a remote.Puller for all operations that use these opts. // This allows us to avoid re-authenticating for everying remote.Function we call, // which speeds things up a whole lot. pusher, err := remote.NewPusher(opts...) if err == nil { opts = append(opts, remote.Reuse(pusher)) } puller, err := remote.NewPuller(opts...) if err == nil { opts = append(opts, remote.Reuse(puller)) } return opts } type RegistryReferrersMode string const ( RegistryReferrersModeLegacy RegistryReferrersMode = "legacy" RegistryReferrersModeOCI11 RegistryReferrersMode = "oci-1-1" ) func (e *RegistryReferrersMode) String() string { return string(*e) } func (e *RegistryReferrersMode) Set(v string) error { switch v { case "legacy": *e = RegistryReferrersMode(v) return nil case "oci-1-1": if !EnableExperimental() { return fmt.Errorf(`in order to use mode "%s", you must set COSIGN_EXPERIMENTAL=1`, v) } *e = RegistryReferrersMode(v) return nil default: return errors.New(`must be one of "legacy", "oci-1-1"`) } } func (e *RegistryReferrersMode) Type() string { return "registryReferrersMode" } // RegistryExperimentalOptions is the wrapper for the registry experimental options. type RegistryExperimentalOptions struct { RegistryReferrersMode RegistryReferrersMode } var _ Interface = (*RegistryExperimentalOptions)(nil) // AddFlags implements Interface func (o *RegistryExperimentalOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().Var(&o.RegistryReferrersMode, "registry-referrers-mode", "mode for fetching references from the registry. allowed: legacy, oci-1-1") } func (o *RegistryOptions) getTLSConfig() (*tls.Config, error) { var tlsConfig tls.Config if o.RegistryCACert != "" { f, err := os.Open(o.RegistryCACert) if err != nil { return nil, err } defer f.Close() caCertBytes, err := io.ReadAll(f) if err != nil { return nil, fmt.Errorf("unable to read CA certs from %s: %w", o.RegistryCACert, err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caCertBytes) { return nil, fmt.Errorf("no valid CA certs found in %s", o.RegistryCACert) } tlsConfig.RootCAs = pool } if o.RegistryClientCert != "" && o.RegistryClientKey != "" { cert, err := tls.LoadX509KeyPair(o.RegistryClientCert, o.RegistryClientKey) if err != nil { return nil, fmt.Errorf("unable to read client certs from cert %s, key %s: %w", o.RegistryClientCert, o.RegistryClientKey, err) } tlsConfig.Certificates = []tls.Certificate{cert} } if o.RegistryServerName != "" { tlsConfig.ServerName = o.RegistryServerName } tlsConfig.InsecureSkipVerify = o.AllowInsecure // #nosec G402 return &tlsConfig, nil } cosign-2.5.0/cmd/cosign/cli/options/registry_test.go000066400000000000000000000141651477503325500225470ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "math/big" "os" "strings" "testing" "time" ) func generatePrivateKey(t *testing.T) *rsa.PrivateKey { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatal(err) } return privateKey } func writePrivateKey(t *testing.T, privateKey *rsa.PrivateKey, fileLocation string) { // Encode the private key to PEM format privateKeyPEM := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey), }) // Write the private key to the specified file err := os.WriteFile(fileLocation, privateKeyPEM, 0600) if err != nil { t.Fatal(err) } } func generateCertificate(t *testing.T, dir string, isCa bool) (certficateLocation, privateKeyLocation string) { certficateLocation = createTempFile(t, dir) privateKeyLocation = createTempFile(t, dir) // Generate a private key for the CA privateKey := generatePrivateKey(t) // Create a self-signed certificate for the CA caTemplate := &x509.Certificate{ Subject: pkix.Name{ CommonName: "Test CA", }, NotBefore: time.Now().Add(time.Hour * -24), NotAfter: time.Now().Add(time.Hour * 24), KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, IsCA: isCa, SerialNumber: big.NewInt(1337), } caCertDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &privateKey.PublicKey, privateKey) if err != nil { t.Fatal(err) } // Encode the CA certificate to PEM format caCertPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: caCertDER, }) // Write the CA certificate to the specified file err = os.WriteFile(certficateLocation, caCertPEM, 0644) if err != nil { t.Fatal(err) } writePrivateKey(t, privateKey, privateKeyLocation) return certficateLocation, privateKeyLocation } func TestGetTLSConfig(t *testing.T) { tempDir := t.TempDir() // Create a temporary directory for testing t.Cleanup(func() { os.RemoveAll(tempDir) }) validCaCertificate, validCaKey := generateCertificate(t, tempDir, true) validClientCertificate, validClientKey := generateCertificate(t, tempDir, false) tests := []struct { name string registryCACert string registryClientCert string registryClientKey string registryServerName string allowInsecure bool expectError string }{ { name: "Valid CA Cert, Client Cert and Key, Server Name, Allow Insecure", registryCACert: validCaCertificate, registryClientCert: validClientCertificate, registryClientKey: validClientKey, registryServerName: "example.com", allowInsecure: true, }, { name: "Wrong key for client cert", registryCACert: validCaCertificate, registryClientCert: validClientCertificate, registryClientKey: validCaKey, // using ca key for client cert must fail registryServerName: "example.com", allowInsecure: true, expectError: fmt.Sprintf("unable to read client certs from cert %s, key %s: tls: private key does not match public key", validClientCertificate, validCaKey), }, { name: "Wrong ca key", registryCACert: validClientKey, // using client key for ca cert must fail registryClientCert: validClientCertificate, registryClientKey: validClientKey, registryServerName: "example.com", allowInsecure: true, expectError: fmt.Sprintf("no valid CA certs found in %s", validClientKey), }, { name: "Invalid CA path", registryCACert: "/not/existing/path/fooobar", // this path is not expected to exist registryClientCert: validClientCertificate, registryClientKey: validClientKey, registryServerName: "example.com", allowInsecure: true, expectError: "open /not/existing/path/fooobar: ", // the error message is OS dependent }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { o := &RegistryOptions{ RegistryCACert: tt.registryCACert, RegistryClientCert: tt.registryClientCert, RegistryClientKey: tt.registryClientKey, RegistryServerName: tt.registryServerName, AllowInsecure: tt.allowInsecure, } tlsConfig, err := o.getTLSConfig() if tt.expectError != "" { if err == nil || !strings.HasPrefix(err.Error(), tt.expectError) { t.Errorf("getTLSConfig()\nerror: \"%v\",\nexpectError: \"%v\"", err, tt.expectError) return } } else { if err != nil { t.Errorf("getTLSConfig() error = %v, expectError %v", err, tt.expectError) return } } if err == nil { if tt.registryCACert != "" { if tlsConfig.RootCAs == nil { t.Errorf("Expected RootCAs to be set") } } if tt.registryClientCert != "" && tt.registryClientKey != "" { if len(tlsConfig.Certificates) == 0 { t.Errorf("Expected Certificates to be set") } } if tt.registryServerName != "" { if tlsConfig.ServerName != tt.registryServerName { t.Errorf("Expected ServerName to be %s, got %s", tt.registryServerName, tlsConfig.ServerName) } } if tt.allowInsecure { if !tlsConfig.InsecureSkipVerify { t.Errorf("Expected InsecureSkipVerify to be true") } } } }) } } // Helper function to create temporary files for testing func createTempFile(t *testing.T, dir string) string { tmpfile, err := os.CreateTemp(dir, "registry-test-") if err != nil { t.Fatal(err) } defer tmpfile.Close() return tmpfile.Name() } cosign-2.5.0/cmd/cosign/cli/options/rekor.go000066400000000000000000000020121477503325500207460ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) const DefaultRekorURL = "https://rekor.sigstore.dev" // RekorOptions is the wrapper for Rekor related options. type RekorOptions struct { URL string } var _ Interface = (*RekorOptions)(nil) // AddFlags implements Interface func (o *RekorOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.URL, "rekor-url", DefaultRekorURL, "address of rekor STL server") } cosign-2.5.0/cmd/cosign/cli/options/root.go000066400000000000000000000051211477503325500206130ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "fmt" "os" "strings" "time" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" ) const EnvPrefix = "COSIGN" // RootOptions define flags and options for the root cosign cli. type RootOptions struct { OutputFile string Verbose bool Timeout time.Duration } // DefaultTimeout specifies the default timeout for commands. const DefaultTimeout = 3 * time.Minute var _ Interface = (*RootOptions)(nil) // AddFlags implements Interface func (o *RootOptions) AddFlags(cmd *cobra.Command) { cmd.PersistentFlags().StringVar(&o.OutputFile, "output-file", "", "log output to a file") _ = cmd.MarkFlagFilename("output-file", logExts...) cmd.PersistentFlags().BoolVarP(&o.Verbose, "verbose", "d", false, "log debug output") cmd.PersistentFlags().DurationVarP(&o.Timeout, "timeout", "t", DefaultTimeout, "timeout for commands") } func BindViper(cmd *cobra.Command, args []string) { callPersistentPreRun(cmd, args) v := viper.New() v.SetEnvPrefix(EnvPrefix) v.AutomaticEnv() bindFlags(cmd, v) } // callPersistentPreRun calls parent commands. PersistentPreRun // does not call parents PersistentPreRun functions func callPersistentPreRun(cmd *cobra.Command, args []string) { if parent := cmd.Parent(); parent != nil { if parent.PersistentPreRun != nil { parent.PersistentPreRun(parent, args) } if parent.PersistentPreRunE != nil { err := parent.PersistentPreRunE(parent, args) if err != nil { cmd.PrintErrln("Error:", err.Error()) os.Exit(1) } } callPersistentPreRun(parent, args) } } func bindFlags(cmd *cobra.Command, v *viper.Viper) { cmd.Flags().VisitAll(func(f *pflag.Flag) { if strings.Contains(f.Name, "-") { _ = v.BindEnv(f.Name, flagToEnvVar(f.Name)) } if !f.Changed && v.IsSet((f.Name)) { val := v.Get(f.Name) _ = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) } }) } func flagToEnvVar(f string) string { f = strings.ToUpper(f) return fmt.Sprintf("%s_%s", EnvPrefix, strings.ReplaceAll(f, "-", "_")) } cosign-2.5.0/cmd/cosign/cli/options/root_test.go000066400000000000000000000024471477503325500216620ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "testing" "github.com/google/go-cmp/cmp" ) func TestFlagToEnv(t *testing.T) { testCases := []struct { flag string expected string }{ { flag: "rekor-url", expected: "COSIGN_REKOR_URL", }, { flag: "certificate", expected: "COSIGN_CERTIFICATE", }, { flag: "k8s-keychain", expected: "COSIGN_K8S_KEYCHAIN", }, { flag: "output-file", expected: "COSIGN_OUTPUT_FILE", }, { flag: "sbom", expected: "COSIGN_SBOM", }, } for _, tc := range testCases { t.Run(tc.flag, func(t *testing.T) { result := flagToEnvVar(tc.flag) if diff := cmp.Diff(result, tc.expected); diff != "" { t.Fatal(diff) } }) } } cosign-2.5.0/cmd/cosign/cli/options/save.go000066400000000000000000000021441477503325500205700ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // SaveOptions is the top level wrapper for the load command. type SaveOptions struct { Directory string Registry RegistryOptions } var _ Interface = (*SaveOptions)(nil) // AddFlags implements Interface func (o *SaveOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) cmd.Flags().StringVar(&o.Directory, "dir", "", "path to dir where the signed image should be stored on disk") _ = cmd.MarkFlagDirname("dir") _ = cmd.MarkFlagRequired("dir") } cosign-2.5.0/cmd/cosign/cli/options/security_key.go000066400000000000000000000022471477503325500223550ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // SecurityKeyOptions is the wrapper for security key related options. type SecurityKeyOptions struct { Use bool Slot string } var _ Interface = (*SecurityKeyOptions)(nil) // AddFlags implements Interface func (o *SecurityKeyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.Use, "sk", false, "whether to use a hardware security key") cmd.Flags().StringVar(&o.Slot, "slot", "", "security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)") } cosign-2.5.0/cmd/cosign/cli/options/sign.go000066400000000000000000000136551477503325500206030ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // SignOptions is the top level wrapper for the sign command. type SignOptions struct { Key string Cert string CertChain string Upload bool Output string // deprecated: TODO remove when the output flag is fully deprecated OutputSignature string // TODO: this should be the root output file arg. OutputPayload string OutputCertificate string PayloadPath string Recursive bool Attachment string SkipConfirmation bool TlogUpload bool TSAClientCACert string TSAClientCert string TSAClientKey string TSAServerName string TSAServerURL string IssueCertificate bool SignContainerIdentity string RecordCreationTimestamp bool Rekor RekorOptions Fulcio FulcioOptions OIDC OIDCOptions SecurityKey SecurityKeyOptions AnnotationOptions Registry RegistryOptions RegistryExperimental RegistryExperimentalOptions } var _ Interface = (*SignOptions)(nil) // AddFlags implements Interface func (o *SignOptions) AddFlags(cmd *cobra.Command) { o.Rekor.AddFlags(cmd) o.Fulcio.AddFlags(cmd) o.OIDC.AddFlags(cmd) o.SecurityKey.AddFlags(cmd) o.AnnotationOptions.AddFlags(cmd) o.Registry.AddFlags(cmd) o.RegistryExperimental.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the private key file, KMS URI or Kubernetes Secret") _ = cmd.MarkFlagFilename("key", privateKeyExts...) cmd.Flags().StringVar(&o.Cert, "certificate", "", "path to the X.509 certificate in PEM format to include in the OCI Signature") _ = cmd.MarkFlagFilename("certificate", certificateExts...) cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "", "path to a list of CA X.509 certificates in PEM format which will be needed "+ "when building the certificate chain for the signing certificate. "+ "Must start with the parent intermediate CA certificate of the "+ "signing certificate and end with the root certificate. Included in the OCI Signature") _ = cmd.MarkFlagFilename("certificate-chain", certificateExts...) cmd.Flags().BoolVar(&o.Upload, "upload", true, "whether to upload the signature") cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", "write the signature to FILE") _ = cmd.MarkFlagFilename("output-signature", signatureExts...) cmd.Flags().StringVar(&o.OutputPayload, "output-payload", "", "write the signed payload to FILE") // _ = cmd.MarkFlagFilename("output-payload") // no typical extensions cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", "write the certificate to FILE") _ = cmd.MarkFlagFilename("output-certificate", certificateExts...) cmd.Flags().StringVar(&o.PayloadPath, "payload", "", "path to a payload file to use rather than generating one") // _ = cmd.MarkFlagFilename("payload") // no typical extensions cmd.Flags().BoolVarP(&o.Recursive, "recursive", "r", false, "if a multi-arch image is specified, additionally sign each discrete image") cmd.Flags().StringVar(&o.Attachment, "attachment", "", "DEPRECATED, related image attachment to sign (sbom), default none") _ = cmd.MarkFlagFilename("attachment", sbomExts...) cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false, "skip confirmation prompts for non-destructive operations") cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true, "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "", "path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server") _ = cmd.MarkFlagFilename("timestamp-client-cacert", certificateExts...) cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "", "path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server") _ = cmd.MarkFlagFilename("timestamp-client-cert", certificateExts...) cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "", "path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server") _ = cmd.MarkFlagFilename("timestamp-client-key", privateKeyExts...) cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "", "SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server") cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") _ = cmd.MarkFlagFilename("certificate", certificateExts...) cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false, "issue a code signing certificate from Fulcio, even if a key is provided") cmd.Flags().StringVar(&o.SignContainerIdentity, "sign-container-identity", "", "manually set the .critical.docker-reference field for the signed identity, which is useful when image proxies are being used where the pull reference should match the signature") cmd.Flags().BoolVar(&o.RecordCreationTimestamp, "record-creation-timestamp", false, "set the createdAt timestamp in the signature artifact to the time it was created; by default, cosign sets this to the zero value") } cosign-2.5.0/cmd/cosign/cli/options/signature_digest.go000066400000000000000000000050071477503325500231730ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "crypto" "fmt" "sort" "strings" _ "crypto/sha256" // for sha224 + sha256 _ "crypto/sha512" // for sha384 + sha512 "github.com/spf13/cobra" ) var supportedSignatureAlgorithms = map[string]crypto.Hash{ "sha224": crypto.SHA224, "sha256": crypto.SHA256, "sha384": crypto.SHA384, "sha512": crypto.SHA512, } func supportedSignatureAlgorithmNames() []string { names := make([]string, 0, len(supportedSignatureAlgorithms)) for name := range supportedSignatureAlgorithms { names = append(names, name) } sort.Strings(names) return names } // SignatureDigestOptions holds options for specifying which digest algorithm should // be used when processing a signature. type SignatureDigestOptions struct { AlgorithmName string } var _ Interface = (*SignatureDigestOptions)(nil) // AddFlags implements Interface func (o *SignatureDigestOptions) AddFlags(cmd *cobra.Command) { validSignatureDigestAlgorithms := strings.Join(supportedSignatureAlgorithmNames(), "|") cmd.Flags().StringVar(&o.AlgorithmName, "signature-digest-algorithm", "sha256", fmt.Sprintf("digest algorithm to use when processing a signature (%s)", validSignatureDigestAlgorithms)) } // HashAlgorithm converts the algorithm's name - provided as a string - into a crypto.Hash algorithm. // Returns an error if the algorithm name doesn't match a supported algorithm, and defaults to SHA256 // in the event that the given algorithm is invalid. func (o *SignatureDigestOptions) HashAlgorithm() (crypto.Hash, error) { normalizedAlgo := strings.ToLower(strings.TrimSpace(o.AlgorithmName)) if normalizedAlgo == "" { return crypto.SHA256, nil } algo, exists := supportedSignatureAlgorithms[normalizedAlgo] if !exists { return crypto.SHA256, fmt.Errorf("unknown digest algorithm: %s", o.AlgorithmName) } if !algo.Available() { return crypto.SHA256, fmt.Errorf("hash %q is not available on this platform", o.AlgorithmName) } return algo, nil } cosign-2.5.0/cmd/cosign/cli/options/signblob.go000066400000000000000000000114361477503325500214350ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // SignBlobOptions is the top level wrapper for the sign-blob command. // The new output-certificate flag is only in use when COSIGN_EXPERIMENTAL is enabled type SignBlobOptions struct { Key string Base64Output bool Output string // deprecated: TODO remove when the output flag is fully deprecated OutputSignature string // TODO: this should be the root output file arg. OutputCertificate string SecurityKey SecurityKeyOptions Fulcio FulcioOptions Rekor RekorOptions OIDC OIDCOptions Registry RegistryOptions BundlePath string NewBundleFormat bool SkipConfirmation bool TlogUpload bool TSAClientCACert string TSAClientCert string TSAClientKey string TSAServerName string TSAServerURL string RFC3161TimestampPath string IssueCertificate bool } var _ Interface = (*SignBlobOptions)(nil) // AddFlags implements Interface func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) { o.SecurityKey.AddFlags(cmd) o.Fulcio.AddFlags(cmd) o.Rekor.AddFlags(cmd) o.OIDC.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the private key file, KMS URI or Kubernetes Secret") _ = cmd.MarkFlagFilename("key", privateKeyExts...) cmd.Flags().BoolVar(&o.Base64Output, "b64", true, "whether to base64 encode the output") cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", "write the signature to FILE") _ = cmd.MarkFlagFilename("output-signature", signatureExts...) // TODO: remove when output flag is fully deprecated cmd.Flags().StringVar(&o.Output, "output", "", "write the signature to FILE") _ = cmd.MarkFlagFilename("output", signatureExts...) cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", "write the certificate to FILE") _ = cmd.MarkFlagFilename("output-certificate", certificateExts...) cmd.Flags().StringVar(&o.BundlePath, "bundle", "", "write everything required to verify the blob to a FILE") _ = cmd.MarkFlagFilename("bundle", bundleExts...) // TODO: have this default to true as a breaking change cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "output bundle in new format that contains all verification material") cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false, "skip confirmation prompts for non-destructive operations") cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true, "whether or not to upload to the tlog") cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "", "path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server") _ = cmd.MarkFlagFilename("timestamp-client-cacert", certificateExts...) cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "", "path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server") _ = cmd.MarkFlagFilename("timestamp-client-cert", certificateExts...) cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "", "path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server") _ = cmd.MarkFlagFilename("timestamp-client-key", privateKeyExts...) cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "", "SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server") _ = cmd.RegisterFlagCompletionFunc("timestamp-server-name", cobra.NoFileCompletions) cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") _ = cmd.RegisterFlagCompletionFunc("timestamp-server-url", cobra.NoFileCompletions) cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "write the RFC3161 timestamp to a file") // _ = cmd.MarkFlagFilename("rfc3161-timestamp") // no typical extensions cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false, "issue a code signing certificate from Fulcio, even if a key is provided") } cosign-2.5.0/cmd/cosign/cli/options/tree.go000066400000000000000000000015131477503325500205700ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import "github.com/spf13/cobra" type TreeOptions struct { Registry RegistryOptions CleanType string } var _ Interface = (*TreeOptions)(nil) func (c *TreeOptions) AddFlags(cmd *cobra.Command) { c.Registry.AddFlags(cmd) } cosign-2.5.0/cmd/cosign/cli/options/triangulate.go000066400000000000000000000021741477503325500221540ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // TriangulateOptions is the top level wrapper for the triangulate command. type TriangulateOptions struct { Type string Registry RegistryOptions } var _ Interface = (*TriangulateOptions)(nil) // AddFlags implements Interface func (o *TriangulateOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) cmd.Flags().StringVar(&o.Type, "type", "signature", "related attachment to triangulate (attestation|sbom|signature|digest), default signature (sbom is deprecated)") } cosign-2.5.0/cmd/cosign/cli/options/trustedroot.go000066400000000000000000000051631477503325500222340ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) type TrustedRootCreateOptions struct { CertChain []string CtfeKeyPath []string CtfeStartTime []string Out string RekorKeyPath []string RekorStartTime []string TSACertChainPath []string } var _ Interface = (*TrustedRootCreateOptions)(nil) func (o *TrustedRootCreateOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringArrayVar(&o.CertChain, "certificate-chain", nil, "path to a list of CA certificates in PEM format which will be needed "+ "when building the certificate chain for the signing certificate. "+ "Must start with the parent intermediate CA certificate of the "+ "signing certificate and end with the root certificate.") _ = cmd.MarkFlagFilename("certificate-chain", certificateExts...) cmd.Flags().StringArrayVar(&o.CtfeKeyPath, "ctfe-key", nil, "path to a PEM-encoded public key used by certificate authority for "+ "certificate transparency log.") _ = cmd.MarkFlagFilename("ctfe-key", publicKeyExts...) cmd.Flags().StringArrayVar(&o.CtfeStartTime, "ctfe-start-time", nil, "RFC 3339 string describing validity start time for key use by "+ "certificate transparency log.") cmd.Flags().StringVar(&o.Out, "out", "", "path to output trusted root") // _ = cmd.MarkFlagFilename("output") // no typical extensions cmd.Flags().StringArrayVar(&o.RekorKeyPath, "rekor-key", nil, "path to a PEM-encoded public key used by transparency log like Rekor.") _ = cmd.MarkFlagFilename("rekor-key", publicKeyExts...) cmd.Flags().StringArrayVar(&o.RekorStartTime, "rekor-start-time", nil, "RFC 3339 string describing validity start time for key use by "+ "transparency log like Rekor.") cmd.Flags().StringArrayVar(&o.TSACertChainPath, "timestamp-certificate-chain", nil, "path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. "+ "Optionally may contain intermediate CA certificates") _ = cmd.MarkFlagFilename("timestamp-certificate-chain", certificateExts...) } cosign-2.5.0/cmd/cosign/cli/options/upload.go000066400000000000000000000032741477503325500211230ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" ) // UploadBlobOptions is the top level wrapper for the `upload blob` command. type UploadBlobOptions struct { ContentType string Files FilesOptions Registry RegistryOptions Annotations map[string]string } var _ Interface = (*UploadBlobOptions)(nil) // AddFlags implements Interface func (o *UploadBlobOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) o.Files.AddFlags(cmd) cmd.Flags().StringVar(&o.ContentType, "ct", "", "content type to set") cmd.Flags().StringToStringVarP(&o.Annotations, "annotation", "a", nil, "annotations to set") } // UploadWASMOptions is the top level wrapper for the `upload wasm` command. type UploadWASMOptions struct { File string Registry RegistryOptions } var _ Interface = (*UploadWASMOptions)(nil) // AddFlags implements Interface func (o *UploadWASMOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) cmd.Flags().StringVarP(&o.File, "file", "f", "", "path to the wasm file to upload") _ = cmd.MarkFlagFilename("file", wasmExts...) _ = cmd.MarkFlagRequired("file") } cosign-2.5.0/cmd/cosign/cli/options/useragent.go000066400000000000000000000021161477503325500216260ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "fmt" "runtime" "sigs.k8s.io/release-utils/version" ) var ( // uaString is meant to resemble the User-Agent sent by browsers with requests. // See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent uaString = fmt.Sprintf("cosign/%s (%s; %s)", version.GetVersionInfo().GitVersion, runtime.GOOS, runtime.GOARCH) ) // UserAgent returns the User-Agent string which `cosign` should send with HTTP requests.ß func UserAgent() string { return uaString } cosign-2.5.0/cmd/cosign/cli/options/verify.go000066400000000000000000000213311477503325500211350ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package options import ( "github.com/spf13/cobra" "github.com/sigstore/cosign/v2/internal/pkg/cosign" ) type CommonVerifyOptions struct { Offline bool // Force offline verification TSACertChainPath string IgnoreTlog bool MaxWorkers int // This is added to CommonVerifyOptions to provide a path to support // it for other verify options. ExperimentalOCI11 bool PrivateInfrastructure bool UseSignedTimestamps bool NewBundleFormat bool TrustedRootPath string } func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.Offline, "offline", false, "only allow offline verification") cmd.Flags().StringVar(&o.TSACertChainPath, "timestamp-certificate-chain", "", "path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. "+ "Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp") cmd.Flags().BoolVar(&o.UseSignedTimestamps, "use-signed-timestamps", false, "verify rfc3161 timestamps") cmd.Flags().BoolVar(&o.IgnoreTlog, "insecure-ignore-tlog", false, "ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts "+ "cannot be publicly verified when not included in a log") cmd.Flags().BoolVar(&o.PrivateInfrastructure, "private-infrastructure", false, "skip transparency log verification when verifying artifacts in a privately deployed infrastructure") cmd.Flags().BoolVar(&o.ExperimentalOCI11, "experimental-oci11", false, "set to true to enable experimental OCI 1.1 behaviour") cmd.Flags().IntVar(&o.MaxWorkers, "max-workers", cosign.DefaultMaxWorkers, "the amount of maximum workers for parallel executions") cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "", "Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.") // TODO: have this default to true as a breaking change cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "expect the signature/attestation to be packaged in a Sigstore bundle") } // VerifyOptions is the top level wrapper for the `verify` command. type VerifyOptions struct { Key string CheckClaims bool Attachment string Output string SignatureRef string PayloadRef string LocalImage bool CommonVerifyOptions CommonVerifyOptions SecurityKey SecurityKeyOptions CertVerify CertVerifyOptions Rekor RekorOptions Registry RegistryOptions SignatureDigest SignatureDigestOptions AnnotationOptions } var _ Interface = (*VerifyOptions)(nil) // AddFlags implements Interface func (o *VerifyOptions) AddFlags(cmd *cobra.Command) { o.SecurityKey.AddFlags(cmd) o.Rekor.AddFlags(cmd) o.CertVerify.AddFlags(cmd) o.Registry.AddFlags(cmd) o.SignatureDigest.AddFlags(cmd) o.AnnotationOptions.AddFlags(cmd) o.CommonVerifyOptions.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the public key file, KMS URI or Kubernetes Secret") _ = cmd.MarkFlagFilename("key", publicKeyExts...) cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true, "whether to check the claims found") cmd.Flags().StringVar(&o.Attachment, "attachment", "", "DEPRECATED, related image attachment to verify (sbom), default none") _ = cmd.MarkFlagFilename("attachment", sbomExts...) cmd.Flags().StringVarP(&o.Output, "output", "o", "json", "output format for the signing image information (json|text)") cmd.Flags().StringVar(&o.SignatureRef, "signature", "", "signature content or path or remote URL") _ = cmd.MarkFlagFilename("signature", signatureExts...) cmd.Flags().StringVar(&o.PayloadRef, "payload", "", "payload path or remote URL") // _ = cmd.MarkFlagFilename("payload") // no typical extensions cmd.Flags().BoolVar(&o.LocalImage, "local-image", false, "whether the specified image is a path to an image saved locally via 'cosign save'") } // VerifyAttestationOptions is the top level wrapper for the `verify attestation` command. type VerifyAttestationOptions struct { Key string CheckClaims bool Output string CommonVerifyOptions CommonVerifyOptions SecurityKey SecurityKeyOptions Rekor RekorOptions CertVerify CertVerifyOptions Registry RegistryOptions Predicate PredicateRemoteOptions Policies []string LocalImage bool } var _ Interface = (*VerifyAttestationOptions)(nil) // AddFlags implements Interface func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) { o.SecurityKey.AddFlags(cmd) o.Rekor.AddFlags(cmd) o.CertVerify.AddFlags(cmd) o.Registry.AddFlags(cmd) o.Predicate.AddFlags(cmd) o.CommonVerifyOptions.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the public key file, KMS URI or Kubernetes Secret") cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true, "whether to check the claims found") cmd.Flags().StringSliceVar(&o.Policies, "policy", nil, "specify CUE or Rego files with policies to be used for validation") cmd.Flags().StringVarP(&o.Output, "output", "o", "json", "output format for the signing image information (json|text)") cmd.Flags().BoolVar(&o.LocalImage, "local-image", false, "whether the specified image is a path to an image saved locally via 'cosign save'") } // VerifyBlobOptions is the top level wrapper for the `verify blob` command. type VerifyBlobOptions struct { Key string Signature string BundlePath string SecurityKey SecurityKeyOptions CertVerify CertVerifyOptions Rekor RekorOptions CommonVerifyOptions CommonVerifyOptions RFC3161TimestampPath string } var _ Interface = (*VerifyBlobOptions)(nil) // AddFlags implements Interface func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) { o.SecurityKey.AddFlags(cmd) o.Rekor.AddFlags(cmd) o.CertVerify.AddFlags(cmd) o.CommonVerifyOptions.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the public key file, KMS URI or Kubernetes Secret") cmd.Flags().StringVar(&o.Signature, "signature", "", "signature content or path or remote URL") cmd.Flags().StringVar(&o.BundlePath, "bundle", "", "path to bundle FILE") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "path to RFC3161 timestamp FILE") } // VerifyDockerfileOptions is the top level wrapper for the `dockerfile verify` command. type VerifyDockerfileOptions struct { VerifyOptions BaseImageOnly bool } var _ Interface = (*VerifyDockerfileOptions)(nil) // AddFlags implements Interface func (o *VerifyDockerfileOptions) AddFlags(cmd *cobra.Command) { o.VerifyOptions.AddFlags(cmd) cmd.Flags().BoolVar(&o.BaseImageOnly, "base-image-only", false, "only verify the base image (the last FROM image in the Dockerfile)") } // VerifyBlobAttestationOptions is the top level wrapper for the `verify-blob-attestation` command. type VerifyBlobAttestationOptions struct { Key string SignaturePath string BundlePath string PredicateOptions CheckClaims bool SecurityKey SecurityKeyOptions CertVerify CertVerifyOptions Rekor RekorOptions CommonVerifyOptions CommonVerifyOptions RFC3161TimestampPath string } var _ Interface = (*VerifyBlobOptions)(nil) // AddFlags implements Interface func (o *VerifyBlobAttestationOptions) AddFlags(cmd *cobra.Command) { o.PredicateOptions.AddFlags(cmd) o.SecurityKey.AddFlags(cmd) o.Rekor.AddFlags(cmd) o.CertVerify.AddFlags(cmd) o.CommonVerifyOptions.AddFlags(cmd) cmd.Flags().StringVar(&o.Key, "key", "", "path to the public key file, KMS URI or Kubernetes Secret") cmd.Flags().StringVar(&o.SignaturePath, "signature", "", "path to base64-encoded signature over attestation in DSSE format") cmd.Flags().StringVar(&o.BundlePath, "bundle", "", "path to bundle FILE") cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true, "if true, verifies the provided blob's sha256 digest exists as an in-toto subject within the attestation. If false, only the DSSE envelope is verified.") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "path to RFC3161 timestamp FILE") } cosign-2.5.0/cmd/cosign/cli/piv_tool.go000066400000000000000000000104131477503325500177700ustar00rootroot00000000000000//go:build pivkey && cgo // +build pivkey,cgo // Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "encoding/json" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/pivcli" "github.com/spf13/cobra" ) var pivToolForce bool func PIVTool() *cobra.Command { cmd := &cobra.Command{ Use: "piv-tool", Short: "Provides utilities for managing a hardware token", } cmd.AddCommand( pivToolSetManagementKey(), pivToolSetPIN(), pivToolSetPUK(), pivToolUnblock(), pivToolAttestation(), pivToolGenerateKey(), pivToolResetKey(), ) // TODO: drop -f in favor of --no-input only // TODO: use the force flag. cmd.PersistentFlags().BoolVarP(&pivToolForce, "no-input", "f", false, "skip warnings and confirmations") return cmd } func pivToolSetManagementKey() *cobra.Command { o := &options.PIVToolSetManagementKeyOptions{} cmd := &cobra.Command{ Use: "set-management-key", Short: "sets the management key of a hardware token", Args: cobra.ExactArgs(0), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return pivcli.SetManagementKeyCmd(cmd.Context(), o.OldKey, o.NewKey, o.RandomKey) }, } o.AddFlags(cmd) return cmd } func pivToolSetPIN() *cobra.Command { o := &options.PIVToolSetPINOptions{} cmd := &cobra.Command{ Use: "set-pin", Short: "sets the PIN on a hardware token", Args: cobra.ExactArgs(0), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return pivcli.SetPinCmd(cmd.Context(), o.OldPIN, o.NewPIN) }, } o.AddFlags(cmd) return cmd } func pivToolSetPUK() *cobra.Command { o := &options.PIVToolSetPUKOptions{} cmd := &cobra.Command{ Use: "set-puk", Short: "sets the PUK on a hardware token", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return pivcli.SetPukCmd(cmd.Context(), o.OldPUK, o.NewPUK) }, } o.AddFlags(cmd) return cmd } func pivToolUnblock() *cobra.Command { o := &options.PIVToolUnblockOptions{} cmd := &cobra.Command{ Use: "unblock", Short: "unblocks the hardware token, sets a new PIN", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return pivcli.UnblockCmd(cmd.Context(), o.PUK, o.NewPIN) }, } o.AddFlags(cmd) return cmd } func pivToolAttestation() *cobra.Command { o := &options.PIVToolAttestationOptions{} cmd := &cobra.Command{ Use: "attestation", Short: "attestation contains commands to manage a hardware token", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { a, err := pivcli.AttestationCmd(cmd.Context(), o.Slot) switch o.Output { case "text": a.Output(cmd.OutOrStdout(), cmd.OutOrStderr()) case "json": b, err := json.Marshal(a) if err != nil { return err } cmd.Println(string(b)) } return err }, } o.AddFlags(cmd) return cmd } func pivToolGenerateKey() *cobra.Command { o := &options.PIVToolGenerateKeyOptions{} cmd := &cobra.Command{ Use: "generate-key", Short: "generate-key generates a new signing key on the hardware token", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return pivcli.GenerateKeyCmd(cmd.Context(), o.ManagementKey, o.RandomKey, o.Slot, o.PINPolicy, o.TouchPolicy) }, } o.AddFlags(cmd) return cmd } func pivToolResetKey() *cobra.Command { cmd := &cobra.Command{ Use: "reset", Short: "reset resets the hardware token completely", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return pivcli.ResetKeyCmd(cmd.Context()) }, } return cmd } cosign-2.5.0/cmd/cosign/cli/piv_tool_disabled.go000066400000000000000000000015141477503325500216210ustar00rootroot00000000000000//go:build !pivkey || !cgo // +build !pivkey !cgo // Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/spf13/cobra" ) func PIVTool() *cobra.Command { return &cobra.Command{ Use: "piv-tool", Short: "This cosign was not built with piv-tool support!", } } cosign-2.5.0/cmd/cosign/cli/pivcli/000077500000000000000000000000001477503325500170755ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/pivcli/commands.go000066400000000000000000000175501477503325500212350ustar00rootroot00000000000000//go:build pivkey && cgo // +build pivkey,cgo // Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pivcli import ( "context" "crypto/rand" "crypto/x509" "encoding/pem" "errors" "flag" "fmt" "io" "os" "strings" "github.com/go-piv/piv-go/v2/piv" "github.com/manifoldco/promptui" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" ) func SetManagementKeyCmd(_ context.Context, oldKey, newKey string, randomKey bool) error { yk, err := pivkey.GetKey() if err != nil { return err } defer yk.Close() oldBytes, err := keyBytes(oldKey) if err != nil { return err } var newBytes *[]byte if randomKey { if !Confirm("Resetting management key to random value. You must factory reset the device to change this value") { return nil } newBytes, err = randomManagementKey() if err != nil { return err } } else { newBytes, err = keyBytes(newKey) if err != nil { return err } } if !Confirm("Setting new management key. This will overwrite the previous key.") { return nil } return yk.SetManagementKey(*oldBytes, *newBytes) } func SetPukCmd(_ context.Context, oldPuk, newPuk string) error { yk, err := pivkey.GetKey() if err != nil { return err } defer yk.Close() if oldPuk == "" { oldPuk = piv.DefaultPUK } if newPuk == "" { newPuk = piv.DefaultPUK } if !Confirm("Setting new PUK. This will overwrite the previous PUK.") { return nil } return yk.SetPUK(oldPuk, newPuk) } func UnblockCmd(_ context.Context, oldPuk, newPin string) error { yk, err := pivkey.GetKey() if err != nil { return err } defer yk.Close() if oldPuk == "" { oldPuk = piv.DefaultPUK } if newPin == "" { newPin = piv.DefaultPIN } if !Confirm("Unblocking the device. This will set a new pin.") { return nil } return yk.Unblock(oldPuk, newPin) } func SetPinCmd(_ context.Context, oldPin, newPin string) error { yk, err := pivkey.GetKey() if err != nil { return err } defer yk.Close() if oldPin == "" { oldPin = piv.DefaultPIN } if newPin == "" { newPin = piv.DefaultPIN } if !Confirm("Setting new pin. This will overwrite the previous pin.") { return nil } return yk.SetPIN(oldPin, newPin) } type Attestations struct { // Skip these for JSON, use the byte form instead DeviceCert *x509.Certificate `json:"-"` KeyCert *x509.Certificate `json:"-"` DeviceCertPem string KeyCertPem string KeyAttestation *piv.Attestation } func (a *Attestations) Output(stdout, stderr io.Writer) { fmt.Fprintln(stderr, "Printing device attestation certificate") b := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: a.DeviceCert.Raw, }) fmt.Fprintln(stdout, string(b)) fmt.Fprintln(stderr, "Printing key attestation certificate") b = pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: a.KeyCert.Raw, }) fmt.Fprintln(stdout, string(b)) fmt.Fprintln(stderr, "Verifying certificates...") fmt.Fprintln(stderr, "Verified ok") fmt.Println() fmt.Fprintln(stderr, "Device info:") fmt.Fprintln(stdout, " Issuer:", a.DeviceCert.Issuer) fmt.Fprintln(stdout, " Form factor:", formFactorString(a.KeyAttestation.Formfactor)) fmt.Fprintln(stdout, " PIN Policy:", pinPolicyStr(a.KeyAttestation.PINPolicy)) fmt.Fprintf(stdout, " Serial number: %d\n", a.KeyAttestation.Serial) fmt.Fprintf(stdout, " Version: %d.%d.%d\n", a.KeyAttestation.Version.Major, a.KeyAttestation.Version.Minor, a.KeyAttestation.Version.Patch) } func AttestationCmd(_ context.Context, slotArg string) (*Attestations, error) { yk, err := pivkey.GetKeyWithSlot(slotArg) if err != nil { return nil, err } defer yk.Close() deviceCert, err := yk.GetAttestationCertificate() if err != nil { return nil, err } keyCert, err := yk.Attest() if err != nil { return nil, err } a, err := piv.Verify(deviceCert, keyCert) if err != nil { return nil, err } ret := &Attestations{ DeviceCert: deviceCert, DeviceCertPem: toPem(deviceCert), KeyCert: keyCert, KeyCertPem: toPem(keyCert), KeyAttestation: a, } return ret, nil } func toPem(c *x509.Certificate) string { b := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: c.Raw, }) return string(b) } func GenerateKeyCmd(ctx context.Context, managementKey string, randomKey bool, slotArg string, pinPolicyArg string, touchPolicyArg string) error { slot := pivkey.SlotForName(slotArg) if slot == nil { return flag.ErrHelp } pinPolicy := pivkey.PINPolicyForName(strings.ToLower(pinPolicyArg), *slot) if pinPolicy < 0 { return flag.ErrHelp } touchPolicy := pivkey.TouchPolicyForName(strings.ToLower(touchPolicyArg), *slot) if touchPolicy < 0 { return flag.ErrHelp } yk, err := pivkey.GetKey() if err != nil { return err } defer yk.Close() keyBytes, err := keyBytes(managementKey) if err != nil { return err } if randomKey { if !Confirm("Resetting management key to random value. You must factory reset the device to change this value") { return nil } newKeyBytes, err := randomManagementKey() if err != nil { return err } if err := yk.SetManagementKey(*keyBytes, *newKeyBytes); err != nil { return err } keyBytes = newKeyBytes } key := piv.Key{ Algorithm: piv.AlgorithmEC256, PINPolicy: pinPolicy, TouchPolicy: touchPolicy, } if !Confirm("Generating new signing key. This will destroy any previous keys.") { return nil } pubKey, err := yk.GenerateKey(*keyBytes, *slot, key) if err != nil { return err } fmt.Fprintln(os.Stderr, "Generated public key") b, err := x509.MarshalPKIXPublicKey(pubKey) if err != nil { return err } pemBytes := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: b, }) fmt.Println(string(pemBytes)) yk.Close() att, err := AttestationCmd(ctx, slotArg) if err != nil { return err } att.Output(os.Stdout, os.Stderr) return nil } func ResetKeyCmd(ctx context.Context) error { yk, err := pivkey.GetKey() if err != nil { return err } defer yk.Close() if !Confirm("Resetting key to factory defaults. This will destroy all values on device.") { return nil } return yk.Reset() } func keyBytes(s string) (*[]byte, error) { if s == "" { return &piv.DefaultManagementKey, nil } if len(s) > 24 { return nil, errors.New("key too long, must be <24 characters") } ret := []byte{} copy(ret[:], s) return &ret, nil } var Confirm = func(p string) bool { prompt := promptui.Prompt{ Label: p, IsConfirm: true, } result, err := prompt.Run() if err != nil { fmt.Println(err) return false } return strings.ToLower(result) == "y" } func randomManagementKey() (*[]byte, error) { var newKeyBytes []byte n, err := io.ReadFull(rand.Reader, newKeyBytes[:]) if err != nil { return nil, err } if n != len(newKeyBytes) { return nil, errors.New("short read from random") } return &newKeyBytes, nil } func formFactorString(ff piv.Formfactor) string { switch ff { case piv.FormfactorUSBAKeychain: return "USB A Keychain" case piv.FormfactorUSBANano: return "USB A Nano" case piv.FormfactorUSBCKeychain: return "USB C Keychain" case piv.FormfactorUSBCNano: return "USB C Nano" case piv.FormfactorUSBCLightningKeychain: return "USB C Lighting Keychain" default: return fmt.Sprintf("unknown: %d", ff) } } func pinPolicyStr(pp piv.PINPolicy) string { switch pp { case piv.PINPolicyAlways: return "Always" case piv.PINPolicyNever: return "Never" case piv.PINPolicyOnce: return "Once" default: return "unknown" } } cosign-2.5.0/cmd/cosign/cli/pkcs11_tool.go000066400000000000000000000041031477503325500202730ustar00rootroot00000000000000//go:build pkcs11key // +build pkcs11key // Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/pkcs11cli" "github.com/spf13/cobra" ) var pkcs11ToolForce bool func PKCS11Tool() *cobra.Command { cmd := &cobra.Command{ Use: "pkcs11-tool", Short: "Provides utilities for retrieving information from a PKCS11 token.", } cmd.AddCommand( pkcs11ToolListTokens(), PKCS11ToolListKeysUrisOptions(), ) // TODO: drop -f in favor of --no-input only // TODO: use the force flag. cmd.PersistentFlags().BoolVarP(&pkcs11ToolForce, "no-input", "f", false, "skip warnings and confirmations") return cmd } func pkcs11ToolListTokens() *cobra.Command { o := &options.PKCS11ToolListTokensOptions{} cmd := &cobra.Command{ Use: "list-tokens", Short: "list-tokens lists all PKCS11 tokens linked to a PKCS11 module", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return pkcs11cli.ListTokensCmd(cmd.Context(), o.ModulePath) }, } o.AddFlags(cmd) return cmd } func PKCS11ToolListKeysUrisOptions() *cobra.Command { o := &options.PKCS11ToolListKeysUrisOptions{} cmd := &cobra.Command{ Use: "list-keys-uris", Short: "list-keys-uris lists URIs of all keys in a PKCS11 token", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return pkcs11cli.ListKeysUrisCmd(cmd.Context(), o.ModulePath, o.SlotID, o.Pin) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/pkcs11_tool_disabled.go000066400000000000000000000015161477503325500221270ustar00rootroot00000000000000//go:build !pkcs11key // +build !pkcs11key // Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "github.com/spf13/cobra" ) func PKCS11Tool() *cobra.Command { return &cobra.Command{ Use: "pkcs11-tool", Short: "This cosign was not built with pkcs11-tool support!", } } cosign-2.5.0/cmd/cosign/cli/pkcs11cli/000077500000000000000000000000001477503325500174015ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/pkcs11cli/commands.go000066400000000000000000000157731477503325500215460ustar00rootroot00000000000000//go:build pkcs11key // +build pkcs11key // Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pkcs11cli import ( "context" "encoding/hex" "errors" "flag" "fmt" "os" "path/filepath" "syscall" "github.com/miekg/pkcs11" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "golang.org/x/term" ) type Token struct { Slot uint TokenInfo pkcs11.TokenInfo } type KeyInfo struct { KeyLabel []byte KeyID []byte KeyURI string } func GetTokens(_ context.Context, modulePath string) ([]Token, error) { if modulePath == "" || !filepath.IsAbs(modulePath) { return nil, flag.ErrHelp } var tokens []Token // Initialize PKCS11 module. p := pkcs11.New(modulePath) if p == nil { return nil, errors.New("failed to load PKCS11 module") } err := p.Initialize() if err != nil { return nil, fmt.Errorf("initialize PKCS11 module: %w", err) } defer p.Destroy() defer p.Finalize() // Get list of all slots with a token, and get info of each. slots, err := p.GetSlotList(true) if err != nil { return nil, fmt.Errorf("get slot list: %w", err) } for _, slot := range slots { tokenInfo, err := p.GetTokenInfo(slot) if err != nil { continue } tokens = append(tokens, Token{Slot: slot, TokenInfo: tokenInfo}) } return tokens, nil } func GetKeysInfo(_ context.Context, modulePath string, slotID uint, pin string) ([]KeyInfo, error) { if modulePath == "" || !filepath.IsAbs(modulePath) { return nil, flag.ErrHelp } var keysInfo []KeyInfo // Initialize PKCS11 module. ctx := pkcs11.New(modulePath) if ctx == nil { return nil, errors.New("failed to load PKCS11 module") } err := ctx.Initialize() if err != nil { return nil, fmt.Errorf("initialize PKCS11 module: %w", err) } defer ctx.Destroy() defer ctx.Finalize() // Get token Info. var tokenInfo pkcs11.TokenInfo tokenInfo, err = ctx.GetTokenInfo(uint(slotID)) if err != nil { return nil, fmt.Errorf("get token info: %w", err) } // If pin was not given, check COSIGN_PKCS11_PIN environment variable. if pin == "" { pin = env.Getenv(env.VariablePKCS11Pin) // If COSIGN_PKCS11_PIN was not set, check if CKF_LOGIN_REQUIRED is set in Token Info. // If it is, ask the user for the PIN, otherwise, do not. if pin == "" { if tokenInfo.Flags&pkcs11.CKF_LOGIN_REQUIRED == pkcs11.CKF_LOGIN_REQUIRED { fmt.Fprintf(os.Stderr, "Enter PIN for PKCS11 token '%s': ", tokenInfo.Label) // Unnecessary convert of syscall.Stdin on *nix, but Windows is a uintptr // nolint:unconvert b, err := term.ReadPassword(int(syscall.Stdin)) if err != nil { return nil, fmt.Errorf("get pin: %w", err) } pin = string(b) } } } // Open a new session to the token. session, err := ctx.OpenSession(slotID, pkcs11.CKF_SERIAL_SESSION) if err != nil { return nil, fmt.Errorf("open session: %w", err) } defer ctx.CloseSession(session) // Login user. err = ctx.Login(session, pkcs11.CKU_USER, pin) if err != nil { return nil, fmt.Errorf("login: %w", err) } defer ctx.Logout(session) // Look for private keys. maxHandlePerFind := 20 var handles []pkcs11.ObjectHandle findAttributes := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), } if err = ctx.FindObjectsInit(session, findAttributes); err != nil { return nil, fmt.Errorf("init find objects: %w", err) } newhandles, _, err := ctx.FindObjects(session, maxHandlePerFind) if err != nil { return nil, fmt.Errorf("find objects: %w", err) } for len(newhandles) > 0 { handles = append(handles, newhandles...) newhandles, _, err = ctx.FindObjects(session, maxHandlePerFind) if err != nil { return nil, fmt.Errorf("find objects: %w", err) } } err = ctx.FindObjectsFinal(session) if err != nil { return nil, fmt.Errorf("finalize find objects: %w", err) } // For each private key, get key label and key id then construct uri. for _, handle := range handles { var keyInfo KeyInfo attributes := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_ID, nil), pkcs11.NewAttribute(pkcs11.CKA_LABEL, nil), } if attributes, err = ctx.GetAttributeValue(session, handle, attributes); err != nil { return nil, fmt.Errorf("get attributes: %w", err) } keyID := attributes[0].Value keyLabel := attributes[1].Value slotIDInt := int(slotID) // If the object has neither a key id nor a key label, we skip it. if (keyID == nil || len(keyID) == 0) && (keyLabel == nil || len(keyLabel) == 0) { continue } // Construct the PKCS11 URI. pkcs11Uri := pkcs11key.NewPkcs11UriConfigFromInput(modulePath, &slotIDInt, tokenInfo.Label, keyLabel, keyID, pin) pkcs11UriStr, err := pkcs11Uri.Construct() if err != nil { return nil, fmt.Errorf("construct pkcs11 uri: %w", err) } if keyLabel != nil && len(keyLabel) != 0 { keyInfo.KeyLabel = keyLabel } if keyID != nil && len(keyID) != 0 { keyInfo.KeyID = keyID } keyInfo.KeyURI = pkcs11UriStr keysInfo = append(keysInfo, keyInfo) } return keysInfo, nil } func ListTokensCmd(ctx context.Context, modulePath string) error { if modulePath == "" { return fmt.Errorf("please specify --module-path or set COSIGN_PKCS11_MODULE_PATH") } tokens, err := GetTokens(ctx, modulePath) if err != nil { return err } fmt.Fprintf(os.Stdout, "\nListing tokens of PKCS11 module '%s'\n", modulePath) for _, token := range tokens { fmt.Fprintf(os.Stdout, "Token in slot %d\n", token.Slot) fmt.Fprintf(os.Stdout, "\tLabel: %s\n", token.TokenInfo.Label) fmt.Fprintf(os.Stdout, "\tManufacturer: %s\n", token.TokenInfo.ManufacturerID) fmt.Fprintf(os.Stdout, "\tModel: %s\n", token.TokenInfo.Model) fmt.Fprintf(os.Stdout, "\tS/N: %s\n\n", token.TokenInfo.SerialNumber) } return nil } func ListKeysUrisCmd(ctx context.Context, modulePath string, slotID uint, pin string) error { if modulePath == "" { return fmt.Errorf("please specify --module-path or set COSIGN_PKCS11_MODULE_PATH") } keysInfo, err := GetKeysInfo(ctx, modulePath, slotID, pin) if err != nil { return err } fmt.Fprintf(os.Stdout, "\nListing URIs of keys in slot '%d' of PKCS11 module '%s'\n", slotID, modulePath) for i, keyInfo := range keysInfo { fmt.Fprintf(os.Stdout, "Object %d\n", i) if keyInfo.KeyLabel != nil && len(keyInfo.KeyLabel) != 0 { fmt.Fprintf(os.Stdout, "\tLabel: %s\n", string(keyInfo.KeyLabel)) } if keyInfo.KeyID != nil && len(keyInfo.KeyID) != 0 { fmt.Fprintf(os.Stdout, "\tID: %s\n", hex.EncodeToString(keyInfo.KeyID)) } fmt.Fprintf(os.Stdout, "\tURI: %s\n", keyInfo.KeyURI) } return nil } cosign-2.5.0/cmd/cosign/cli/public_key.go000066400000000000000000000055671477503325500203010ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "os" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/publickey" "github.com/spf13/cobra" ) func PublicKey() *cobra.Command { o := &options.PublicKeyOptions{} cmd := &cobra.Command{ Use: "public-key", Short: "Gets a public key from the key-pair.", Long: "Gets a public key from the key-pair and\nwrites to a specified file. By default, it will write to standard out.", Example: ` # extract public key from private key to a specified out file. cosign public-key --key --outfile # extract public key from URL. cosign public-key --key https://host.for/ --outfile # extract public key from Azure Key Vault cosign public-key --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # extract public key from AWS KMS cosign public-key --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # extract public key from Google Cloud KMS cosign public-key --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # extract public key from Hashicorp Vault KMS cosign public-key --key hashivault://[KEY] # extract public key from GitLab with project name cosign public-key --key gitlab://[OWNER]/[PROJECT_NAME] # extract public key from GitLab with project id cosign public-key --key gitlab://[PROJECT_ID] `, PreRunE: func(_ *cobra.Command, _ []string) error { if !options.OneOf(o.Key, o.SecurityKey.Use) { return &options.KeyParseError{} } return nil }, PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, _ []string) error { writer := publickey.NamedWriter{Name: "", Writer: nil} var f *os.File // Open output file for public key if specified. if o.OutFile != "" { writer.Name = o.OutFile var err error f, err = os.OpenFile(o.OutFile, os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return err } writer.Writer = f defer f.Close() } else { writer.Writer = os.Stdout } pk := publickey.Pkopts{ KeyRef: o.Key, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, } return publickey.GetPublicKey(cmd.Context(), pk, writer, generate.GetPass) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/publickey/000077500000000000000000000000001477503325500175765ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/publickey/public_key.go000066400000000000000000000037731477503325500222650ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package publickey import ( "context" "fmt" "io" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) type NamedWriter struct { Name string io.Writer } type Pkopts struct { KeyRef string Sk bool Slot string } func GetPublicKey(ctx context.Context, opts Pkopts, writer NamedWriter, pf cosign.PassFunc) error { var k signature.PublicKeyProvider switch { case opts.KeyRef != "": s, err := sigs.SignerFromKeyRef(ctx, opts.KeyRef, pf) if err != nil { return err } pkcs11Key, ok := s.(*pkcs11key.Key) if ok { defer pkcs11Key.Close() } k = s case opts.Sk: sk, err := pivkey.GetKeyWithSlot(opts.Slot) if err != nil { return fmt.Errorf("opening piv token: %w", err) } defer sk.Close() pk, err := sk.Verifier() if err != nil { return fmt.Errorf("initializing piv token verifier: %w", err) } k = pk } pemBytes, err := sigs.PublicKeyPem(k, signatureoptions.WithContext(ctx)) if err != nil { return err } if _, err := writer.Write(pemBytes); err != nil { return err } if writer.Name != "" { ui.Infof(ctx, "Public key written to %s", writer.Name) } return nil } cosign-2.5.0/cmd/cosign/cli/publickey/public_key_test.go000066400000000000000000000042261477503325500233160ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package publickey import ( "bytes" "context" "crypto/rand" "os" "path/filepath" "testing" "github.com/sigstore/cosign/v2/pkg/cosign" ) func pass(s string) cosign.PassFunc { return func(_ bool) ([]byte, error) { return []byte(s), nil } } // Test success on getting public key with valid keypair. func TestPublicKeyLocation(t *testing.T) { ctx := context.Background() // Generate a valid keypair. keys, err := cosign.GenerateKeyPair(pass("hello")) if err != nil { t.Fatal(err) } var out bytes.Buffer w := NamedWriter{"cosign.pub", &out} td := t.TempDir() f := filepath.Join(td, "private.key") if err := os.WriteFile(f, keys.PrivateBytes, 0644); err != nil { t.Fatal(err) } opts := Pkopts{ KeyRef: f, } err = GetPublicKey(ctx, opts, w, pass("hello")) if err != nil { t.Fatalf("got error %s", err) } // Verify that key's public key matches the output buffer. if !bytes.Equal(out.Bytes(), keys.PublicBytes) { t.Fatalf("expect %s got %s", keys.PrivateBytes, out.Bytes()) } } // Tests failure with bad private key. func TestPublicKeyBadPrivateKey(t *testing.T) { ctx := context.Background() // Use random bytes for private key pair. buf := []byte{} if _, err := rand.Read(buf); err != nil { t.Fatal(err) } td := t.TempDir() f := filepath.Join(td, "private.key") if err := os.WriteFile(f, buf, 0644); err != nil { t.Fatal(err) } var out bytes.Buffer w := NamedWriter{"cosign.pub", &out} opts := Pkopts{ KeyRef: f, } if err := GetPublicKey(ctx, opts, w, pass("hello")); err == nil { t.Error("expected error getting public key!") } } cosign-2.5.0/cmd/cosign/cli/rekor/000077500000000000000000000000001477503325500167315ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/rekor/rekor.go000066400000000000000000000017431477503325500204070ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rekor import ( rekor "github.com/sigstore/rekor/pkg/client" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func NewClient(rekorURL string) (*client.Rekor, error) { rekorClient, err := rekor.GetRekorClient(rekorURL, rekor.WithUserAgent(options.UserAgent())) if err != nil { return nil, err } return rekorClient, nil } cosign-2.5.0/cmd/cosign/cli/rekor/rekor_test.go000066400000000000000000000025611477503325500214450ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rekor import ( "net/http" "net/http/httptest" "testing" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func TestNewClient(t *testing.T) { t.Parallel() expectedUserAgent := options.UserAgent() requestReceived := false testServer := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { requestReceived = true file := []byte{} got := r.UserAgent() if got != expectedUserAgent { t.Errorf("wanted User-Agent %q, got %q", expectedUserAgent, got) } w.WriteHeader(http.StatusOK) _, _ = w.Write(file) })) defer testServer.Close() client, err := NewClient(testServer.URL) if err != nil { t.Error(err) } _, _ = client.Tlog.GetLogInfo(nil) if !requestReceived { t.Fatal("no requests were received") } } cosign-2.5.0/cmd/cosign/cli/save.go000066400000000000000000000050661477503325500171030ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "context" "errors" "fmt" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/layout" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/spf13/cobra" ) func Save() *cobra.Command { o := &options.SaveOptions{} cmd := &cobra.Command{ Use: "save", Short: "Save the container image and associated signatures to disk at the specified directory.", Long: "Save the container image and associated signatures to disk at the specified directory.", Example: ` cosign save --dir `, Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return SaveCmd(cmd.Context(), *o, args[0]) }, } o.AddFlags(cmd) return cmd } func SaveCmd(ctx context.Context, opts options.SaveOptions, imageRef string) error { regOpts := opts.Registry regClientOpts, err := regOpts.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } ref, err := name.ParseReference(imageRef, opts.Registry.NameOptions()...) if err != nil { return fmt.Errorf("parsing image name %s: %w", imageRef, err) } se, err := ociremote.SignedEntity(ref, regClientOpts...) if err != nil { return fmt.Errorf("signed entity: %w", err) } if _, ok := se.(oci.SignedImage); ok { si, err := ociremote.SignedImage(ref, regClientOpts...) if err != nil { return fmt.Errorf("getting signed image: %w", err) } return layout.WriteSignedImage(opts.Directory, si) } if _, ok := se.(oci.SignedImageIndex); ok { sii, err := ociremote.SignedImageIndex(ref, regClientOpts...) if err != nil { return fmt.Errorf("getting signed image index: %w", err) } return layout.WriteSignedImageIndex(opts.Directory, sii) } return errors.New("unknown signed entity") } cosign-2.5.0/cmd/cosign/cli/sign.go000066400000000000000000000127311477503325500171020ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "os" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/spf13/cobra" ) func Sign() *cobra.Command { o := &options.SignOptions{} cmd := &cobra.Command{ Use: "sign", Short: "Sign the supplied container image.", Long: `Sign the supplied container image. Make sure to sign the image by its digest (@sha256:...) rather than by tag (:latest) so that you actually sign what you think you're signing! This prevents race conditions or (worse) malicious tampering. `, Example: ` cosign sign --key | [--payload ] [-a key=value] [--upload=true|false] [-f] [-r] # sign a container image with the Sigstore OIDC flow cosign sign # sign a container image with a local key pair file cosign sign --key cosign.key # sign a multi-arch container image AND all referenced, discrete images cosign sign --key cosign.key --recursive # sign a container image and add annotations cosign sign --key cosign.key -a key1=value1 -a key2=value2 # sign a container image with a key stored in an environment variable cosign sign --key env://[ENV_VAR] # sign a container image with a key pair stored in Azure Key Vault cosign sign --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # sign a container image with a key pair stored in AWS KMS cosign sign --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # sign a container image with a key pair stored in Google Cloud KMS cosign sign --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY]/versions/[VERSION] # sign a container image with a key pair stored in Hashicorp Vault cosign sign --key hashivault://[KEY] # sign a container image with a key pair stored in a Kubernetes secret cosign sign --key k8s://[NAMESPACE]/[KEY] # sign a container image with a key, attaching a certificate and certificate chain cosign sign --key cosign.key --cert cosign.crt --cert-chain chain.crt # sign a container in a registry which does not fully support OCI media types COSIGN_DOCKER_MEDIA_TYPES=1 cosign sign --key cosign.key legacy-registry.example.com/my/image@ # sign a container image and upload to the transparency log cosign sign --key cosign.key # sign a container image and skip uploading to the transparency log cosign sign --key cosign.key --tlog-upload=false # sign a container image by manually setting the container image identity cosign sign --sign-container-identity # sign a container image and honor the creation timestamp of the signature cosign sign --key cosign.key --record-creation-timestamp `, Args: cobra.MinimumNArgs(1), PersistentPreRun: options.BindViper, RunE: func(_ *cobra.Command, args []string) error { switch o.Attachment { case "sbom": fmt.Fprintln(os.Stderr, options.SBOMAttachmentDeprecation) case "": break default: return fmt.Errorf("specified image attachment %s not specified. Can be 'sbom'", o.Attachment) } oidcClientSecret, err := o.OIDC.ClientSecret() if err != nil { return err } ko := options.KeyOpts{ KeyRef: o.Key, PassFunc: generate.GetPass, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, FulcioURL: o.Fulcio.URL, IDToken: o.Fulcio.IdentityToken, FulcioAuthFlow: o.Fulcio.AuthFlow, InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, RekorURL: o.Rekor.URL, OIDCIssuer: o.OIDC.Issuer, OIDCClientID: o.OIDC.ClientID, OIDCClientSecret: oidcClientSecret, OIDCRedirectURL: o.OIDC.RedirectURL, OIDCDisableProviders: o.OIDC.DisableAmbientProviders, OIDCProvider: o.OIDC.Provider, SkipConfirmation: o.SkipConfirmation, TSAClientCACert: o.TSAClientCACert, TSAClientCert: o.TSAClientCert, TSAClientKey: o.TSAClientKey, TSAServerName: o.TSAServerName, TSAServerURL: o.TSAServerURL, IssueCertificateForExistingKey: o.IssueCertificate, } if err := sign.SignCmd(ro, ko, *o, args); err != nil { if o.Attachment == "" { return fmt.Errorf("signing %v: %w", args, err) } return fmt.Errorf("signing attachment %s for image %v: %w", o.Attachment, args, err) } return nil }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/sign/000077500000000000000000000000001477503325500165475ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/sign/privacy/000077500000000000000000000000001477503325500202245ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/sign/privacy/privacy.go000066400000000000000000000032301477503325500222260ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package privacy import "sync" const ( // spacing is intentional to have this indented Statement = ` The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/. Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record. This may include the email address associated with the account with which you authenticate your contractual Agreement. This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/. ` StatementConfirmation = "By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above." ) var ( StatementOnce sync.Once ) cosign-2.5.0/cmd/cosign/cli/sign/sign.go000066400000000000000000000452731477503325500200510ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sign import ( "bytes" "context" "crypto" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "errors" "fmt" "os" "path/filepath" "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio/fulcioverifier" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign/privacy" icos "github.com/sigstore/cosign/v2/internal/pkg/cosign" ifulcio "github.com/sigstore/cosign/v2/internal/pkg/cosign/fulcio" ipayload "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload" irekor "github.com/sigstore/cosign/v2/internal/pkg/cosign/rekor" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" cremote "github.com/sigstore/cosign/v2/pkg/cosign/remote" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/walk" sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" sigPayload "github.com/sigstore/sigstore/pkg/signature/payload" // Loads OIDC providers _ "github.com/sigstore/cosign/v2/pkg/providers/all" ) func ShouldUploadToTlog(ctx context.Context, ko options.KeyOpts, ref name.Reference, tlogUpload bool) (bool, error) { upload := shouldUploadToTlog(ctx, ko, ref, tlogUpload) var statementErr error if upload { privacy.StatementOnce.Do(func() { ui.Infof(ctx, privacy.Statement) ui.Infof(ctx, privacy.StatementConfirmation) if !ko.SkipConfirmation { if err := ui.ConfirmContinue(ctx); err != nil { statementErr = err } } }) } return upload, statementErr } func shouldUploadToTlog(ctx context.Context, ko options.KeyOpts, ref name.Reference, tlogUpload bool) bool { // return false if not uploading to the tlog has been requested if !tlogUpload { return false } if ko.SkipConfirmation { return true } // We don't need to validate the ref, just return true if ref == nil { return true } // Check if the image is public (no auth in Get) if _, err := remote.Get(ref, remote.WithContext(ctx)); err != nil { ui.Warnf(ctx, "%q appears to be a private repository, please confirm uploading to the transparency log at %q", ref.Context().String(), ko.RekorURL) if ui.ConfirmContinue(ctx) != nil { ui.Infof(ctx, "not uploading to transparency log") return false } } return true } func GetAttachedImageRef(ref name.Reference, attachment string, opts ...ociremote.Option) (name.Reference, error) { if attachment == "" { return ref, nil } if attachment == "sbom" { return ociremote.SBOMTag(ref, opts...) } return nil, fmt.Errorf("unknown attachment type %s", attachment) } // ParseOCIReference parses a string reference to an OCI image into a reference, warning if the reference did not include a digest. func ParseOCIReference(ctx context.Context, refStr string, opts ...name.Option) (name.Reference, error) { ref, err := name.ParseReference(refStr, opts...) if err != nil { return nil, fmt.Errorf("parsing reference: %w", err) } if _, ok := ref.(name.Digest); !ok { msg := fmt.Sprintf(ui.TagReferenceMessage, refStr) ui.Warnf(ctx, msg) } return ref, nil } // nolint func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignOptions, imgs []string) error { if options.NOf(ko.KeyRef, ko.Sk) > 1 { return &options.KeyParseError{} } ctx, cancel := context.WithTimeout(context.Background(), ro.Timeout) defer cancel() sv, err := SignerFromKeyOpts(ctx, signOpts.Cert, signOpts.CertChain, ko) if err != nil { return fmt.Errorf("getting signer: %w", err) } defer sv.Close() dd := cremote.NewDupeDetector(sv) var staticPayload []byte if signOpts.PayloadPath != "" { ui.Infof(ctx, "Using payload from: %s", signOpts.PayloadPath) staticPayload, err = os.ReadFile(filepath.Clean(signOpts.PayloadPath)) if err != nil { return fmt.Errorf("payload from file: %w", err) } } // Set up an ErrDone consideration to return along "success" paths var ErrDone error if !signOpts.Recursive { ErrDone = mutate.ErrSkipChildren } regOpts := signOpts.Registry opts, err := regOpts.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } am, err := signOpts.AnnotationsMap() if err != nil { return fmt.Errorf("getting annotations: %w", err) } annotations := am.Annotations for _, inputImg := range imgs { ref, err := ParseOCIReference(ctx, inputImg, regOpts.NameOptions()...) if err != nil { return err } ref, err = GetAttachedImageRef(ref, signOpts.Attachment, opts...) if err != nil { return fmt.Errorf("unable to resolve attachment %s for image %s", signOpts.Attachment, inputImg) } if digest, ok := ref.(name.Digest); ok && !signOpts.Recursive { se, err := ociremote.SignedEntity(ref, opts...) if _, isEntityNotFoundErr := err.(*ociremote.EntityNotFoundError); isEntityNotFoundErr { se = ociremote.SignedUnknown(digest) } else if err != nil { return fmt.Errorf("accessing image: %w", err) } err = signDigest(ctx, digest, staticPayload, ko, signOpts, annotations, dd, sv, se) if err != nil { return fmt.Errorf("signing digest: %w", err) } continue } se, err := ociremote.SignedEntity(ref, opts...) if err != nil { return fmt.Errorf("accessing entity: %w", err) } if err := walk.SignedEntity(ctx, se, func(ctx context.Context, se oci.SignedEntity) error { // Get the digest for this entity in our walk. d, err := se.(interface{ Digest() (v1.Hash, error) }).Digest() if err != nil { return fmt.Errorf("computing digest: %w", err) } digest := ref.Context().Digest(d.String()) err = signDigest(ctx, digest, staticPayload, ko, signOpts, annotations, dd, sv, se) if err != nil { return fmt.Errorf("signing digest: %w", err) } return ErrDone }); err != nil { return fmt.Errorf("recursively signing: %w", err) } } return nil } func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko options.KeyOpts, signOpts options.SignOptions, annotations map[string]interface{}, dd mutate.DupeDetector, sv *SignerVerifier, se oci.SignedEntity) error { var err error // The payload can be passed to skip generation. if len(payload) == 0 { payload, err = (&sigPayload.Cosign{ Image: digest, ClaimedIdentity: signOpts.SignContainerIdentity, Annotations: annotations, }).MarshalJSON() if err != nil { return fmt.Errorf("payload: %w", err) } } var s icos.Signer s = ipayload.NewSigner(sv) if sv.Cert != nil { s = ifulcio.NewSigner(s, sv.Cert, sv.Chain) } if ko.TSAServerURL != "" { if ko.TSAClientCACert == "" && ko.TSAClientCert == "" { // no mTLS params or custom CA s = tsa.NewSigner(s, client.NewTSAClient(ko.TSAServerURL)) } else { s = tsa.NewSigner(s, client.NewTSAClientMTLS(ko.TSAServerURL, ko.TSAClientCACert, ko.TSAClientCert, ko.TSAClientKey, ko.TSAServerName, )) } } shouldUpload, err := ShouldUploadToTlog(ctx, ko, digest, signOpts.TlogUpload) if err != nil { return fmt.Errorf("should upload to tlog: %w", err) } if shouldUpload { rClient, err := rekor.NewClient(ko.RekorURL) if err != nil { return err } s = irekor.NewSigner(s, rClient) } ociSig, _, err := s.Sign(ctx, bytes.NewReader(payload)) if err != nil { return err } b64sig, err := ociSig.Base64Signature() if err != nil { return err } outputSignature := signOpts.OutputSignature if outputSignature != "" { // Add digest to suffix to differentiate each image during recursive signing if signOpts.Recursive { outputSignature = fmt.Sprintf("%s-%s", outputSignature, strings.Replace(digest.DigestStr(), ":", "-", 1)) } if err := os.WriteFile(outputSignature, []byte(b64sig), 0600); err != nil { return fmt.Errorf("create signature file: %w", err) } } outputPayload := signOpts.OutputPayload if outputPayload != "" { // Add digest to suffix to differentiate each image during recursive signing if signOpts.Recursive { outputPayload = fmt.Sprintf("%s-%s", outputPayload, strings.Replace(digest.DigestStr(), ":", "-", 1)) } if err := os.WriteFile(outputPayload, payload, 0600); err != nil { return fmt.Errorf("create payload file: %w", err) } } if signOpts.OutputCertificate != "" { rekorBytes, err := sv.Bytes(ctx) if err != nil { return fmt.Errorf("create certificate file: %w", err) } if err := os.WriteFile(signOpts.OutputCertificate, rekorBytes, 0600); err != nil { return fmt.Errorf("create certificate file: %w", err) } // TODO: maybe accept a --b64 flag as well? ui.Infof(ctx, "Certificate wrote in the file %s", signOpts.OutputCertificate) } if ko.BundlePath != "" { signedPayload, err := fetchLocalSignedPayload(ociSig) if err != nil { return fmt.Errorf("failed to fetch signed payload: %w", err) } contents, err := json.Marshal(signedPayload) if err != nil { return fmt.Errorf("failed to marshal signed payload: %w", err) } if err := os.WriteFile(ko.BundlePath, contents, 0600); err != nil { return fmt.Errorf("create bundle file: %w", err) } ui.Infof(ctx, "Wrote bundle to file %s", ko.BundlePath) } if !signOpts.Upload { return nil } // Attach the signature to the entity. newSE, err := mutate.AttachSignatureToEntity(se, ociSig, mutate.WithDupeDetector(dd), mutate.WithRecordCreationTimestamp(signOpts.RecordCreationTimestamp)) if err != nil { return err } // Publish the signatures associated with this entity walkOpts, err := signOpts.Registry.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } // Check if we are overriding the signatures repository location repo, _ := ociremote.GetEnvTargetRepository() if repo.RepositoryStr() == "" { ui.Infof(ctx, "Pushing signature to: %s", digest.Repository) } else { ui.Infof(ctx, "Pushing signature to: %s", repo.RepositoryStr()) } // Publish the signatures associated with this entity (using OCI 1.1+ behavior) if signOpts.RegistryExperimental.RegistryReferrersMode == options.RegistryReferrersModeOCI11 { return ociremote.WriteSignaturesExperimentalOCI(digest, newSE, walkOpts...) } // Publish the signatures associated with this entity return ociremote.WriteSignatures(digest.Repository, newSE, walkOpts...) } func signerFromSecurityKey(ctx context.Context, keySlot string) (*SignerVerifier, error) { sk, err := pivkey.GetKeyWithSlot(keySlot) if err != nil { return nil, err } sv, err := sk.SignerVerifier() if err != nil { sk.Close() return nil, err } // Handle the -cert flag. // With PIV, we assume the certificate is in the same slot on the PIV // token as the private key. If it's not there, show a warning to the // user. certFromPIV, err := sk.Certificate() var pemBytes []byte if err != nil { ui.Warnf(ctx, "no x509 certificate retrieved from the PIV token") } else { pemBytes, err = cryptoutils.MarshalCertificateToPEM(certFromPIV) if err != nil { sk.Close() return nil, err } } return &SignerVerifier{ Cert: pemBytes, SignerVerifier: sv, close: sk.Close, }, nil } func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef string, passFunc cosign.PassFunc) (*SignerVerifier, error) { k, err := sigs.SignerVerifierFromKeyRef(ctx, keyRef, passFunc) if err != nil { return nil, fmt.Errorf("reading key: %w", err) } certSigner := &SignerVerifier{ SignerVerifier: k, } var leafCert *x509.Certificate // Attempt to extract certificate from PKCS11 token // With PKCS11, we assume the certificate is in the same slot on the PKCS11 // token as the private key. If it's not there, show a warning to the // user. if pkcs11Key, ok := k.(*pkcs11key.Key); ok { certFromPKCS11, _ := pkcs11Key.Certificate() certSigner.close = pkcs11Key.Close if certFromPKCS11 == nil { ui.Warnf(ctx, "no x509 certificate retrieved from the PKCS11 token") } else { pemBytes, err := cryptoutils.MarshalCertificateToPEM(certFromPKCS11) if err != nil { pkcs11Key.Close() return nil, err } // Check that the provided public key and certificate key match pubKey, err := k.PublicKey() if err != nil { pkcs11Key.Close() return nil, err } if cryptoutils.EqualKeys(pubKey, certFromPKCS11.PublicKey) != nil { pkcs11Key.Close() return nil, errors.New("pkcs11 key and certificate do not match") } leafCert = certFromPKCS11 certSigner.Cert = pemBytes } } // Handle --cert flag if certPath != "" { // Allow both DER and PEM encoding certBytes, err := os.ReadFile(certPath) if err != nil { return nil, fmt.Errorf("read certificate: %w", err) } // Handle PEM if bytes.HasPrefix(certBytes, []byte("-----")) { decoded, _ := pem.Decode(certBytes) if decoded.Type != "CERTIFICATE" { return nil, fmt.Errorf("supplied PEM file is not a certificate: %s", certPath) } certBytes = decoded.Bytes } parsedCert, err := x509.ParseCertificate(certBytes) if err != nil { return nil, fmt.Errorf("parse x509 certificate: %w", err) } pk, err := k.PublicKey() if err != nil { return nil, fmt.Errorf("get public key: %w", err) } if cryptoutils.EqualKeys(pk, parsedCert.PublicKey) != nil { return nil, errors.New("public key in certificate does not match the provided public key") } pemBytes, err := cryptoutils.MarshalCertificateToPEM(parsedCert) if err != nil { return nil, fmt.Errorf("marshaling certificate to PEM: %w", err) } if certSigner.Cert != nil { ui.Warnf(ctx, "overriding x509 certificate retrieved from the PKCS11 token") } leafCert = parsedCert certSigner.Cert = pemBytes } if certChainPath == "" { return certSigner, nil } else if certSigner.Cert == nil { return nil, errors.New("no leaf certificate found or provided while specifying chain") } // Handle --cert-chain flag // Accept only PEM encoded certificate chain certChainBytes, err := os.ReadFile(certChainPath) if err != nil { return nil, fmt.Errorf("reading certificate chain from path: %w", err) } certChain, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(certChainBytes)) if err != nil { return nil, fmt.Errorf("loading certificate chain: %w", err) } if len(certChain) == 0 { return nil, errors.New("no certificates in certificate chain") } // Verify certificate chain is valid rootPool := x509.NewCertPool() rootPool.AddCert(certChain[len(certChain)-1]) subPool := x509.NewCertPool() for _, c := range certChain[:len(certChain)-1] { subPool.AddCert(c) } if _, err := cosign.TrustedCert(leafCert, rootPool, subPool); err != nil { return nil, fmt.Errorf("unable to validate certificate chain: %w", err) } // Verify SCT if present in the leaf certificate. contains, err := cosign.ContainsSCT(leafCert.Raw) if err != nil { return nil, err } if contains { pubKeys, err := cosign.GetCTLogPubs(ctx) if err != nil { return nil, fmt.Errorf("getting CTLog public keys: %w", err) } var chain []*x509.Certificate chain = append(chain, leafCert) chain = append(chain, certChain...) if err := cosign.VerifyEmbeddedSCT(context.Background(), chain, pubKeys); err != nil { return nil, err } } certSigner.Chain = certChainBytes return certSigner, nil } func signerFromNewKey() (*SignerVerifier, error) { privKey, err := cosign.GeneratePrivateKey() if err != nil { return nil, fmt.Errorf("generating cert: %w", err) } sv, err := signature.LoadECDSASignerVerifier(privKey, crypto.SHA256) if err != nil { return nil, err } return &SignerVerifier{ SignerVerifier: sv, }, nil } func keylessSigner(ctx context.Context, ko options.KeyOpts, sv *SignerVerifier) (*SignerVerifier, error) { var ( k *fulcio.Signer err error ) if ko.InsecureSkipFulcioVerify { if k, err = fulcio.NewSigner(ctx, ko, sv); err != nil { return nil, fmt.Errorf("getting key from Fulcio: %w", err) } } else { if k, err = fulcioverifier.NewSigner(ctx, ko, sv); err != nil { return nil, fmt.Errorf("getting key from Fulcio: %w", err) } } return &SignerVerifier{ Cert: k.Cert, Chain: k.Chain, SignerVerifier: k, }, nil } func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, error) { var sv *SignerVerifier var err error genKey := false switch { case ko.Sk: sv, err = signerFromSecurityKey(ctx, ko.Slot) case ko.KeyRef != "": sv, err = signerFromKeyRef(ctx, certPath, certChainPath, ko.KeyRef, ko.PassFunc) default: genKey = true ui.Infof(ctx, "Generating ephemeral keys...") sv, err = signerFromNewKey() } if err != nil { return nil, err } if ko.IssueCertificateForExistingKey || genKey { return keylessSigner(ctx, ko, sv) } return sv, nil } type SignerVerifier struct { Cert []byte Chain []byte signature.SignerVerifier close func() } func (c *SignerVerifier) Close() { if c.close != nil { c.close() } } func (c *SignerVerifier) Bytes(ctx context.Context) ([]byte, error) { if c.Cert != nil { return c.Cert, nil } pemBytes, err := sigs.PublicKeyPem(c, signatureoptions.WithContext(ctx)) if err != nil { return nil, err } return pemBytes, nil } func fetchLocalSignedPayload(sig oci.Signature) (*cosign.LocalSignedPayload, error) { signedPayload := &cosign.LocalSignedPayload{} var err error signedPayload.Base64Signature, err = sig.Base64Signature() if err != nil { return nil, err } sigCert, err := sig.Cert() if err != nil { return nil, err } if sigCert != nil { signedPayload.Cert = base64.StdEncoding.EncodeToString(sigCert.Raw) if err != nil { return nil, err } } else { signedPayload.Cert = "" } signedPayload.Bundle, err = sig.Bundle() if err != nil { return nil, err } return signedPayload, nil } cosign-2.5.0/cmd/cosign/cli/sign/sign_blob.go000066400000000000000000000171651477503325500210460ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sign import ( "context" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "fmt" "os" "path/filepath" "google.golang.org/protobuf/encoding/protojson" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" internal "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/cryptoutils" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) // nolint func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string, b64 bool, outputSignature string, outputCertificate string, tlogUpload bool) ([]byte, error) { var payload internal.HashReader var err error ctx, cancel := context.WithTimeout(context.Background(), ro.Timeout) defer cancel() if payloadPath == "-" { payload = internal.NewHashReader(os.Stdin, sha256.New()) } else { ui.Infof(ctx, "Using payload from: %s", payloadPath) f, err := os.Open(filepath.Clean(payloadPath)) defer f.Close() if err != nil { return nil, err } payload = internal.NewHashReader(f, sha256.New()) } if err != nil { return nil, err } sv, err := SignerFromKeyOpts(ctx, "", "", ko) if err != nil { return nil, err } defer sv.Close() sig, err := sv.SignMessage(&payload, signatureoptions.WithContext(ctx)) if err != nil { return nil, fmt.Errorf("signing blob: %w", err) } digest := payload.Sum(nil) signedPayload := cosign.LocalSignedPayload{} var rekorEntry *models.LogEntryAnon var rfc3161Timestamp *cbundle.RFC3161Timestamp var timestampBytes []byte if ko.TSAServerURL != "" { if ko.RFC3161TimestampPath == "" && !ko.NewBundleFormat { return nil, fmt.Errorf("must use protobuf bundle or set timestamp output path") } var err error if ko.TSAClientCACert == "" && ko.TSAClientCert == "" { // no mTLS params or custom CA timestampBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClient(ko.TSAServerURL)) if err != nil { return nil, err } } else { timestampBytes, err = tsa.GetTimestampedSignature(sig, client.NewTSAClientMTLS(ko.TSAServerURL, ko.TSAClientCACert, ko.TSAClientCert, ko.TSAClientKey, ko.TSAServerName, )) if err != nil { return nil, err } } rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(timestampBytes) // TODO: Consider uploading RFC3161 TS to Rekor if rfc3161Timestamp == nil { return nil, fmt.Errorf("rfc3161 timestamp is nil") } if ko.RFC3161TimestampPath != "" { ts, err := json.Marshal(rfc3161Timestamp) if err != nil { return nil, err } if err := os.WriteFile(ko.RFC3161TimestampPath, ts, 0600); err != nil { return nil, fmt.Errorf("create RFC3161 timestamp file: %w", err) } ui.Infof(ctx, "RFC3161 timestamp written to file %s\n", ko.RFC3161TimestampPath) } } shouldUpload, err := ShouldUploadToTlog(ctx, ko, nil, tlogUpload) if err != nil { return nil, fmt.Errorf("upload to tlog: %w", err) } if shouldUpload { rekorBytes, err := sv.Bytes(ctx) if err != nil { return nil, err } rekorClient, err := rekor.NewClient(ko.RekorURL) if err != nil { return nil, err } rekorEntry, err = cosign.TLogUpload(ctx, rekorClient, sig, &payload, rekorBytes) if err != nil { return nil, err } ui.Infof(ctx, "tlog entry created with index: %d", *rekorEntry.LogIndex) signedPayload.Bundle = cbundle.EntryToBundle(rekorEntry) } // if bundle is specified, just do that and ignore the rest if ko.BundlePath != "" { var contents []byte if ko.NewBundleFormat { // Determine if signature is certificate or not var hint string var rawCert []byte signer, err := sv.Bytes(ctx) if err != nil { return nil, fmt.Errorf("error getting signer: %w", err) } cert, err := cryptoutils.UnmarshalCertificatesFromPEM(signer) if err != nil || len(cert) == 0 { pubKey, err := sv.PublicKey() if err != nil { return nil, err } pkixPubKey, err := x509.MarshalPKIXPublicKey(pubKey) if err != nil { return nil, err } hashedBytes := sha256.Sum256(pkixPubKey) hint = base64.StdEncoding.EncodeToString(hashedBytes[:]) } else { rawCert = cert[0].Raw } bundle, err := cbundle.MakeProtobufBundle(hint, rawCert, rekorEntry, timestampBytes) if err != nil { return nil, err } bundle.Content = &protobundle.Bundle_MessageSignature{ MessageSignature: &protocommon.MessageSignature{ MessageDigest: &protocommon.HashOutput{ Algorithm: protocommon.HashAlgorithm_SHA2_256, Digest: digest, }, Signature: sig, }, } contents, err = protojson.Marshal(bundle) if err != nil { return nil, err } } else { signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig) certBytes, err := extractCertificate(ctx, sv) if err != nil { return nil, err } signedPayload.Cert = base64.StdEncoding.EncodeToString(certBytes) contents, err = json.Marshal(signedPayload) if err != nil { return nil, err } } if err := os.WriteFile(ko.BundlePath, contents, 0600); err != nil { return nil, fmt.Errorf("create bundle file: %w", err) } ui.Infof(ctx, "Wrote bundle to file %s", ko.BundlePath) } if outputSignature != "" { var bts = sig if b64 { bts = []byte(base64.StdEncoding.EncodeToString(sig)) } if err := os.WriteFile(outputSignature, bts, 0600); err != nil { return nil, fmt.Errorf("create signature file: %w", err) } ui.Infof(ctx, "Wrote signature to file %s", outputSignature) } else { if b64 { sig = []byte(base64.StdEncoding.EncodeToString(sig)) fmt.Println(string(sig)) } else if _, err := os.Stdout.Write(sig); err != nil { // No newline if using the raw signature return nil, err } } if outputCertificate != "" { certBytes, err := extractCertificate(ctx, sv) if err != nil { return nil, err } if certBytes != nil { bts := certBytes if b64 { bts = []byte(base64.StdEncoding.EncodeToString(certBytes)) } if err := os.WriteFile(outputCertificate, bts, 0600); err != nil { return nil, fmt.Errorf("create certificate file: %w", err) } ui.Infof(ctx, "Wrote certificate to file %s", outputCertificate) } } return sig, nil } // Extract an encoded certificate from the SignerVerifier. Returns (nil, nil) if verifier is not a certificate. func extractCertificate(ctx context.Context, sv *SignerVerifier) ([]byte, error) { signer, err := sv.Bytes(ctx) if err != nil { return nil, fmt.Errorf("error getting signer: %w", err) } cert, err := cryptoutils.UnmarshalCertificatesFromPEM(signer) // signer is a certificate if err == nil && len(cert) == 1 { return signer, nil } return nil, nil } cosign-2.5.0/cmd/cosign/cli/sign/sign_blob_test.go000066400000000000000000000034671477503325500221050ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sign import ( "os" "path/filepath" "testing" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" ) func TestSignBlobCmd(t *testing.T) { td := t.TempDir() bundlePath := filepath.Join(td, "bundle.sigstore.json") keys, _ := cosign.GenerateKeyPair(nil) keyRef := writeFile(t, td, string(keys.PrivateBytes), "key.pem") blob := []byte("foo") blobPath := writeFile(t, td, string(blob), "foo.txt") rootOpts := &options.RootOptions{} keyOpts := options.KeyOpts{KeyRef: keyRef, BundlePath: bundlePath} // Test happy path _, err := SignBlobCmd(rootOpts, keyOpts, blobPath, true, "", "", false) if err != nil { t.Fatalf("unexpected error %v", err) } // Test file outputs keyOpts.NewBundleFormat = true sigPath := filepath.Join(td, "output.sig") certPath := filepath.Join(td, "output.pem") _, err = SignBlobCmd(rootOpts, keyOpts, blobPath, false, sigPath, certPath, false) if err != nil { t.Fatalf("unexpected error %v", err) } } func writeFile(t *testing.T, td string, blob string, name string) string { // Write blob to disk blobPath := filepath.Join(td, name) if err := os.WriteFile(blobPath, []byte(blob), 0644); err != nil { t.Fatal(err) } return blobPath } cosign-2.5.0/cmd/cosign/cli/sign/sign_test.go000066400000000000000000000167721477503325500211120ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sign import ( "context" "crypto/ecdsa" "crypto/x509" "encoding/pem" "errors" "os" "reflect" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/secure-systems-lab/go-securesystemslib/encrypted" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/test" "github.com/sigstore/sigstore/pkg/cryptoutils" ) func pass(s string) cosign.PassFunc { return func(_ bool) ([]byte, error) { return []byte(s), nil } } func generateCertificateFiles(t *testing.T, tmpDir string, pf cosign.PassFunc) (privFile, certFile, chainFile string, privKey *ecdsa.PrivateKey, cert *x509.Certificate, chain []*x509.Certificate) { t.Helper() rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) x509Encoded, err := x509.MarshalPKCS8PrivateKey(privKey) if err != nil { t.Fatalf("failed to encode private key: %v", err) } password := []byte{} if pf != nil { password, err = pf(true) if err != nil { t.Fatalf("failed to read password: %v", err) } } encBytes, err := encrypted.Encrypt(x509Encoded, password) if err != nil { t.Fatalf("failed to encrypt key: %v", err) } // store in PEM format privBytes := pem.EncodeToMemory(&pem.Block{ Bytes: encBytes, Type: cosign.CosignPrivateKeyPemType, }) tmpPrivFile, err := os.CreateTemp(tmpDir, "cosign_test_*.key") if err != nil { t.Fatalf("failed to create temp key file: %v", err) } defer tmpPrivFile.Close() if _, err := tmpPrivFile.Write(privBytes); err != nil { t.Fatalf("failed to write key file: %v", err) } tmpCertFile, err := os.CreateTemp(tmpDir, "cosign.crt") if err != nil { t.Fatalf("failed to create temp certificate file: %v", err) } defer tmpCertFile.Close() if _, err := tmpCertFile.Write(pemLeaf); err != nil { t.Fatalf("failed to write certificate file: %v", err) } tmpChainFile, err := os.CreateTemp(tmpDir, "cosign_chain.crt") if err != nil { t.Fatalf("failed to create temp chain file: %v", err) } defer tmpChainFile.Close() pemChain := pemSub pemChain = append(pemChain, pemRoot...) if _, err := tmpChainFile.Write(pemChain); err != nil { t.Fatalf("failed to write chain file: %v", err) } return tmpPrivFile.Name(), tmpCertFile.Name(), tmpChainFile.Name(), privKey, leafCert, []*x509.Certificate{subCert, rootCert} } // TestSignCmdLocalKeyAndSk verifies the SignCmd returns an error // if both a local key path and a sk are specified func TestSignCmdLocalKeyAndSk(t *testing.T) { ro := &options.RootOptions{Timeout: options.DefaultTimeout} for _, ko := range []options.KeyOpts{ // local and sk keys { KeyRef: "testLocalPath", PassFunc: generate.GetPass, Sk: true, }, } { so := options.SignOptions{} err := SignCmd(ro, ko, so, nil) if (errors.Is(err, &options.KeyParseError{}) == false) { t.Fatal("expected KeyParseError") } } } func Test_signerFromKeyRefSuccess(t *testing.T) { tmpDir := t.TempDir() ctx := context.Background() keyFile, certFile, chainFile, privKey, cert, chain := generateCertificateFiles(t, tmpDir, pass("foo")) signer, err := signerFromKeyRef(ctx, certFile, chainFile, keyFile, pass("foo")) if err != nil { t.Fatalf("unexpected error generating signer: %v", err) } // Expect public key matches pubKey, err := signer.PublicKey() if err != nil { t.Fatalf("unexpected error fetching pubkey: %v", err) } if !privKey.Public().(*ecdsa.PublicKey).Equal(pubKey) { t.Fatalf("public keys must be equal") } // Expect certificate matches expectedPemBytes, err := cryptoutils.MarshalCertificateToPEM(cert) if err != nil { t.Fatalf("unexpected error marshalling certificate: %v", err) } if !reflect.DeepEqual(signer.Cert, expectedPemBytes) { t.Fatalf("certificates must match") } // Expect certificate chain matches expectedPemBytesChain, err := cryptoutils.MarshalCertificatesToPEM(chain) if err != nil { t.Fatalf("unexpected error marshalling certificate chain: %v", err) } if !reflect.DeepEqual(signer.Chain, expectedPemBytesChain) { t.Fatalf("certificate chains must match") } } func Test_signerFromKeyRefFailure(t *testing.T) { tmpDir := t.TempDir() ctx := context.Background() keyFile, certFile, _, _, _, _ := generateCertificateFiles(t, tmpDir, pass("foo")) // Second set of files tmpDir2 := t.TempDir() _, certFile2, chainFile2, _, _, _ := generateCertificateFiles(t, tmpDir2, pass("bar")) // Public keys don't match _, err := signerFromKeyRef(ctx, certFile2, chainFile2, keyFile, pass("foo")) if err == nil || err.Error() != "public key in certificate does not match the provided public key" { t.Fatalf("expected mismatched keys error, got %v", err) } // Certificate chain cannot be verified _, err = signerFromKeyRef(ctx, certFile, chainFile2, keyFile, pass("foo")) if err == nil || !strings.Contains(err.Error(), "unable to validate certificate chain") { t.Fatalf("expected chain verification error, got %v", err) } // Certificate chain specified without certificate _, err = signerFromKeyRef(ctx, "", chainFile2, keyFile, pass("foo")) if err == nil || !strings.Contains(err.Error(), "no leaf certificate found or provided while specifying chain") { t.Fatalf("expected no leaf error, got %v", err) } } func Test_signerFromKeyRefFailureEmptyChainFile(t *testing.T) { tmpDir := t.TempDir() ctx := context.Background() keyFile, certFile, _, _, _, _ := generateCertificateFiles(t, tmpDir, pass("foo")) tmpChainFile, err := os.CreateTemp(tmpDir, "cosign_chain_empty.crt") if err != nil { t.Fatalf("failed to create temp chain file: %v", err) } defer tmpChainFile.Close() if _, err := tmpChainFile.Write([]byte{}); err != nil { t.Fatalf("failed to write chain file: %v", err) } _, err = signerFromKeyRef(ctx, certFile, tmpChainFile.Name(), keyFile, pass("foo")) if err == nil || err.Error() != "no certificates in certificate chain" { t.Fatalf("expected empty chain error, got %v", err) } } func Test_ParseOCIReference(t *testing.T) { var tests = []struct { ref string expectedWarning string }{ {"image:bytag", "WARNING: Image reference image:bytag uses a tag, not a digest"}, {"image:bytag@sha256:abcdef", ""}, {"image:@sha256:abcdef", ""}, } for _, tt := range tests { stderr := ui.RunWithTestCtx(func(ctx context.Context, _ ui.WriteFunc) { ParseOCIReference(ctx, tt.ref) }) if len(tt.expectedWarning) > 0 { assert.Contains(t, stderr, tt.expectedWarning, stderr, "bad warning message") } else { assert.Empty(t, stderr, "expected no warning") } } } cosign-2.5.0/cmd/cosign/cli/signblob.go000066400000000000000000000103361477503325500177400ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "os" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/spf13/cobra" "github.com/spf13/viper" ) func SignBlob() *cobra.Command { o := &options.SignBlobOptions{} viper.RegisterAlias("output", "output-signature") cmd := &cobra.Command{ Use: "sign-blob", Short: "Sign the supplied blob, outputting the base64-encoded signature to stdout.", Example: ` cosign sign-blob --key | # sign a blob with Google sign-in (experimental) cosign sign-blob --output-signature --output-certificate # sign a blob with a local key pair file cosign sign-blob --key cosign.key # sign a blob with a key stored in an environment variable cosign sign-blob --key env://[ENV_VAR] # sign a blob with a key pair stored in Azure Key Vault cosign sign-blob --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # sign a blob with a key pair stored in AWS KMS cosign sign-blob --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # sign a blob with a key pair stored in Google Cloud KMS cosign sign-blob --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # sign a blob with a key pair stored in Hashicorp Vault cosign sign-blob --key hashivault://[KEY] `, Args: cobra.MinimumNArgs(1), PersistentPreRun: options.BindViper, PreRunE: func(_ *cobra.Command, _ []string) error { if options.NOf(o.Key, o.SecurityKey.Use) > 1 { return &options.KeyParseError{} } return nil }, RunE: func(_ *cobra.Command, args []string) error { oidcClientSecret, err := o.OIDC.ClientSecret() if err != nil { return err } ko := options.KeyOpts{ KeyRef: o.Key, PassFunc: generate.GetPass, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, FulcioURL: o.Fulcio.URL, IDToken: o.Fulcio.IdentityToken, FulcioAuthFlow: o.Fulcio.AuthFlow, InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, RekorURL: o.Rekor.URL, OIDCIssuer: o.OIDC.Issuer, OIDCClientID: o.OIDC.ClientID, OIDCClientSecret: oidcClientSecret, OIDCRedirectURL: o.OIDC.RedirectURL, OIDCDisableProviders: o.OIDC.DisableAmbientProviders, BundlePath: o.BundlePath, NewBundleFormat: o.NewBundleFormat, SkipConfirmation: o.SkipConfirmation, TSAClientCACert: o.TSAClientCACert, TSAClientCert: o.TSAClientCert, TSAClientKey: o.TSAClientKey, TSAServerName: o.TSAServerName, TSAServerURL: o.TSAServerURL, RFC3161TimestampPath: o.RFC3161TimestampPath, IssueCertificateForExistingKey: o.IssueCertificate, } for _, blob := range args { // TODO: remove when the output flag has been deprecated if o.Output != "" { fmt.Fprintln(os.Stderr, "WARNING: the '--output' flag is deprecated and will be removed in the future. Use '--output-signature'") o.OutputSignature = o.Output } if _, err := sign.SignBlobCmd(ro, ko, blob, o.Base64Output, o.OutputSignature, o.OutputCertificate, o.TlogUpload); err != nil { return fmt.Errorf("signing %s: %w", blob, err) } } return nil }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/templates/000077500000000000000000000000001477503325500176055ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/templates/help_flags_printer.go000066400000000000000000000046731477503325500240150ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package templates import ( "bytes" "fmt" "io" "strings" "github.com/mitchellh/go-wordwrap" flag "github.com/spf13/pflag" ) const offset = 10 // HelpFlagPrinter is a printer that // processes the help flag and print // it to i/o writer type HelpFlagPrinter struct { wrapLimit uint out io.Writer } // NewHelpFlagPrinter will initialize a HelpFlagPrinter given the // i/o writer func NewHelpFlagPrinter(out io.Writer, wrapLimit uint) *HelpFlagPrinter { return &HelpFlagPrinter{ wrapLimit: wrapLimit, out: out, } } // PrintHelpFlag will beautify the help flags and print it out to p.out func (p *HelpFlagPrinter) PrintHelpFlag(flag *flag.Flag) { formatBuf := new(bytes.Buffer) writeFlag(formatBuf, flag) wrappedStr := formatBuf.String() flagAndUsage := strings.Split(formatBuf.String(), "\n") flagStr := flagAndUsage[0] // if the flag usage is longer than one line, wrap it again if len(flagAndUsage) > 1 { nextLines := strings.Join(flagAndUsage[1:], " ") wrappedUsages := wordwrap.WrapString(nextLines, p.wrapLimit-offset) wrappedStr = flagStr + "\n" + wrappedUsages } appendTabStr := strings.ReplaceAll(wrappedStr, "\n", "\n\t") fmt.Fprintf(p.out, "%s\n\n", appendTabStr) } // writeFlag will output the help flag based // on the format provided by getFlagFormat to i/o writer func writeFlag(out io.Writer, f *flag.Flag) { deprecated := "" if f.Deprecated != "" { deprecated = fmt.Sprintf(" (DEPRECATED: %s)", f.Deprecated) } fmt.Fprintf(out, getFlagFormat(f), f.Shorthand, f.Name, f.DefValue, f.Usage, deprecated) } // getFlagFormat will output the flag format func getFlagFormat(f *flag.Flag) string { var format string format = "--%s=%s:\n%s%s" if f.Value.Type() == "string" { format = "--%s='%s':\n%s%s" } if len(f.Shorthand) > 0 { format = " -%s, " + format } else { format = " %s" + format } return format } cosign-2.5.0/cmd/cosign/cli/templates/templater.go000066400000000000000000000053151477503325500221350ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package templates import ( "bytes" "fmt" "strings" "text/template" "unicode" "github.com/sigstore/cosign/v2/cmd/cosign/cli/templates/term" "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) type templater struct { UsageTemplate string RootCmd *cobra.Command } func SetCustomUsageFunc(cmd *cobra.Command) { if cmd == nil { panic("nil root command") } t := &templater{ RootCmd: cmd, UsageTemplate: MainUsageTemplate(), } cmd.SetUsageFunc(t.UsageFunc()) } func (templater *templater) UsageFunc() func(*cobra.Command) error { return func(c *cobra.Command) error { t := template.New("usage") t.Funcs(templater.templateFuncs()) template.Must(t.Parse(templater.UsageTemplate)) out := term.NewResponsiveWriter(c.OutOrStderr()) return t.Execute(out, c) } } func (templater *templater) templateFuncs() template.FuncMap { return template.FuncMap{ "trim": strings.TrimSpace, "trimRightSpace": trimRightSpace, "trimTrailingWhitespaces": trimRightSpace, "appendIfNotPresent": appendIfNotPresent, "rpad": rpad, "gt": cobra.Gt, "eq": cobra.Eq, "flagsUsages": flagsUsages, } } func trimRightSpace(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) } // appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s. func appendIfNotPresent(s, stringToAppend string) string { if strings.Contains(s, stringToAppend) { return s } return s + " " + stringToAppend } // rpad adds padding to the right of a string. func rpad(s string, padding int) string { template := fmt.Sprintf("%%-%ds", padding) return fmt.Sprintf(template, s) } // flagsUsages will print out the kubectl help flags func flagsUsages(f *flag.FlagSet) (string, error) { flagBuf := new(bytes.Buffer) wrapLimit, err := term.GetWordWrapperLimit() if err != nil { return "", err } printer := NewHelpFlagPrinter(flagBuf, wrapLimit) f.VisitAll(func(flag *flag.Flag) { if flag.Hidden { return } printer.PrintHelpFlag(flag) }) return flagBuf.String(), nil } cosign-2.5.0/cmd/cosign/cli/templates/templates.go000066400000000000000000000042261477503325500221360ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package templates // References: https://github.com/spf13/cobra/blob/4dd4b25de38418174a6e859e8a32eaccca32dccc/command.go#L539-L567 const defaultUsageTemplate = `Usage:{{if .Runnable}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} Aliases: {{.NameAndAliases}}{{end}}{{if .HasExample}} Examples: {{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}} Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}} {{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}} Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Flags: {{flagsUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} Global Flags: {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} ` func MainUsageTemplate() string { return defaultUsageTemplate } cosign-2.5.0/cmd/cosign/cli/templates/term/000077500000000000000000000000001477503325500205545ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/templates/term/term_writer.go000066400000000000000000000055731477503325500234600ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package term import ( "errors" "io" "os" wordwrap "github.com/mitchellh/go-wordwrap" "github.com/moby/term" ) type wordWrapWriter struct { limit uint writer io.Writer } type TerminalSize struct { Width uint16 Height uint16 } // NewResponsiveWriter creates a Writer that detects the column width of the // terminal we are in, and adjusts every line width to fit and use recommended // terminal sizes for better readability. Does proper word wrapping automatically. // // if terminal width >= 120 columns use 120 columns // if terminal width >= 100 columns use 100 columns // if terminal width >= 80 columns use 80 columns // // In case we're not in a terminal or if it's smaller than 80 columns width, // doesn't do any wrapping. func NewResponsiveWriter(w io.Writer) io.Writer { file, ok := w.(*os.File) if !ok { return w } fd := file.Fd() if !term.IsTerminal(fd) { return w } terminalSize := GetSize(fd) if terminalSize == nil { return w } limit := getTerminalLimitWidth(terminalSize) return NewWordWrapWriter(w, limit) } // NewWordWrapWriter is a Writer that supports a limit of characters on every line // and does auto word wrapping that respects that limit. func NewWordWrapWriter(w io.Writer, limit uint) io.Writer { return &wordWrapWriter{ limit: limit, writer: w, } } func getTerminalLimitWidth(terminalSize *TerminalSize) uint { var limit uint switch { case terminalSize.Width >= 120: limit = 120 case terminalSize.Width >= 100: limit = 100 case terminalSize.Width >= 80: limit = 80 } return limit } // GetSize returns the current size of the terminal associated with fd. func GetSize(fd uintptr) *TerminalSize { winsize, err := term.GetWinsize(fd) if err != nil { return nil } return &TerminalSize{Width: winsize.Width, Height: winsize.Height} } func GetWordWrapperLimit() (uint, error) { stdout := os.Stdout fd := stdout.Fd() if !term.IsTerminal(fd) { return 0, nil } terminalSize := GetSize(fd) if terminalSize == nil { return 0, errors.New("terminal size is nil") } return getTerminalLimitWidth(terminalSize), nil } func (w wordWrapWriter) Write(p []byte) (nn int, err error) { if w.limit == 0 { return w.writer.Write(p) } original := string(p) wrapped := wordwrap.WrapString(original, w.limit) return w.writer.Write([]byte(wrapped)) } cosign-2.5.0/cmd/cosign/cli/tree.go000066400000000000000000000072141477503325500171010ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "context" "fmt" "os" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/spf13/cobra" ) func Tree() *cobra.Command { c := &options.TreeOptions{} cmd := &cobra.Command{ Use: "tree", Short: "Display supply chain security related artifacts for an image such as signatures, SBOMs and attestations", Example: " cosign tree ", Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return TreeCmd(cmd.Context(), c.Registry, args[0]) }, } c.AddFlags(cmd) return cmd } func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string) error { scsaMap := map[name.Tag][]v1.Layer{} ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } remoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return err } fmt.Fprintf(os.Stdout, "📦 Supply Chain Security Related artifacts for an image: %s\n", ref.String()) simg, err := ociremote.SignedEntity(ref, remoteOpts...) if err != nil { return err } attRef, err := ociremote.AttestationTag(ref, remoteOpts...) if err != nil { return err } atts, err := simg.Attestations() if err == nil { layers, err := atts.Layers() if err != nil { return err } if len(layers) > 0 { scsaMap[attRef] = layers } } sigRef, err := ociremote.SignatureTag(ref, remoteOpts...) if err != nil { return err } sigs, err := simg.Signatures() if err == nil { layers, err := sigs.Layers() if err != nil { return err } if len(layers) > 0 { scsaMap[sigRef] = layers } } sbomRef, err := ociremote.SBOMTag(ref, remoteOpts...) if err != nil { return err } sbombs, err := simg.Attachment(ociremote.SBOMTagSuffix) if err == nil { layers, err := sbombs.Layers() if err != nil { return err } if len(layers) > 0 { scsaMap[sbomRef] = layers } } if len(scsaMap) == 0 { fmt.Fprintf(os.Stdout, "No Supply Chain Security Related Artifacts artifacts found for image %s\n, start creating one with simply running"+ "$ cosign sign ", ref.String()) return nil } for t, k := range scsaMap { switch t { case sigRef: fmt.Fprintf(os.Stdout, "└── 🔐 Signatures for an image tag: %s\n", t.String()) case sbomRef: fmt.Fprintf(os.Stdout, "└── 📦 SBOMs for an image tag: %s\n", t.String()) case attRef: fmt.Fprintf(os.Stdout, "└── 💾 Attestations for an image tag: %s\n", t.String()) } if err := printLayers(k); err != nil { return err } } return nil } func printLayers(layers []v1.Layer) error { for i, l := range layers { last := i == len(layers)-1 var sym string if last { sym = " └──" } else { sym = " ├──" } digest, err := l.Digest() if err != nil { return err } fmt.Printf("%s 🍒 %s\n", sym, digest) } return nil } cosign-2.5.0/cmd/cosign/cli/triangulate.go000066400000000000000000000025211477503325500204550ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "flag" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/triangulate" "github.com/spf13/cobra" ) func Triangulate() *cobra.Command { o := &options.TriangulateOptions{} cmd := &cobra.Command{ Use: "triangulate", Short: "Outputs the located cosign image reference. This is the location where cosign stores the specified artifact type.", Example: " cosign triangulate ", PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return flag.ErrHelp } return triangulate.MungeCmd(cmd.Context(), o.Registry, args[0], o.Type) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/triangulate/000077500000000000000000000000001477503325500201265ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/triangulate/triangulate.go000066400000000000000000000037071477503325500230030ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package triangulate import ( "context" "fmt" "os" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) func MungeCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string, attachmentType string) error { ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } var dstRef name.Tag var dstRefName string switch attachmentType { case cosign.Signature: dstRef, err = ociremote.SignatureTag(ref, ociremoteOpts...) dstRefName = dstRef.Name() case cosign.SBOM: fmt.Fprintln(os.Stderr, options.SBOMAttachmentDeprecation) dstRef, err = ociremote.SBOMTag(ref, ociremoteOpts...) dstRefName = dstRef.Name() case cosign.Attestation: dstRef, err = ociremote.AttestationTag(ref, ociremoteOpts...) dstRefName = dstRef.Name() case cosign.Digest: dstRef, err = ociremote.DigestTag(ref, ociremoteOpts...) dstRefName = fmt.Sprint(dstRef.Repository.Name(), "@", dstRef.TagStr()) default: err = fmt.Errorf("unknown attachment type %s", attachmentType) } if err != nil { return err } fmt.Println(dstRefName) return nil } cosign-2.5.0/cmd/cosign/cli/trustedroot.go000066400000000000000000000034561477503325500205440ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "context" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/trustedroot" "github.com/spf13/cobra" ) func TrustedRoot() *cobra.Command { cmd := &cobra.Command{ Use: "trusted-root", Short: "Interact with a Sigstore protobuf trusted root", Long: "Tools for interacting with a Sigstore protobuf trusted root", } cmd.AddCommand(trustedRootCreate()) return cmd } func trustedRootCreate() *cobra.Command { o := &options.TrustedRootCreateOptions{} cmd := &cobra.Command{ Use: "create", Short: "Create a Sigstore protobuf trusted root", Long: "Create a Sigstore protobuf trusted root by supplying verification material", RunE: func(cmd *cobra.Command, _ []string) error { trCreateCmd := &trustedroot.CreateCmd{ CertChain: o.CertChain, CtfeKeyPath: o.CtfeKeyPath, CtfeStartTime: o.CtfeStartTime, Out: o.Out, RekorKeyPath: o.RekorKeyPath, RekorStartTime: o.RekorStartTime, TSACertChainPath: o.TSACertChainPath, } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) defer cancel() return trCreateCmd.Exec(ctx) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/trustedroot/000077500000000000000000000000001477503325500202055ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/trustedroot/trustedroot.go000066400000000000000000000117771477503325500231470ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trustedroot import ( "context" "crypto" "crypto/x509" "encoding/hex" "encoding/pem" "fmt" "os" "time" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore/pkg/cryptoutils" ) type CreateCmd struct { CertChain []string CtfeKeyPath []string CtfeStartTime []string Out string RekorKeyPath []string RekorStartTime []string TSACertChainPath []string } func (c *CreateCmd) Exec(_ context.Context) error { var fulcioCertAuthorities []root.CertificateAuthority ctLogs := make(map[string]*root.TransparencyLog) var timestampAuthorities []root.TimestampingAuthority rekorTransparencyLogs := make(map[string]*root.TransparencyLog) for i := 0; i < len(c.CertChain); i++ { fulcioAuthority, err := parseCAPEMFile(c.CertChain[i]) if err != nil { return err } fulcioCertAuthorities = append(fulcioCertAuthorities, fulcioAuthority) } for i := 0; i < len(c.CtfeKeyPath); i++ { ctLogPubKey, id, idBytes, err := getPubKey(c.CtfeKeyPath[i]) if err != nil { return err } startTime := time.Unix(0, 0) if i < len(c.CtfeStartTime) { startTime, err = time.Parse(time.RFC3339, c.CtfeStartTime[i]) if err != nil { return err } } ctLogs[id] = &root.TransparencyLog{ HashFunc: crypto.SHA256, ID: idBytes, ValidityPeriodStart: startTime, PublicKey: *ctLogPubKey, SignatureHashFunc: crypto.SHA256, } } for i := 0; i < len(c.RekorKeyPath); i++ { tlogPubKey, id, idBytes, err := getPubKey(c.RekorKeyPath[i]) if err != nil { return err } startTime := time.Unix(0, 0) if i < len(c.RekorStartTime) { startTime, err = time.Parse(time.RFC3339, c.RekorStartTime[i]) if err != nil { return err } } rekorTransparencyLogs[id] = &root.TransparencyLog{ HashFunc: crypto.SHA256, ID: idBytes, ValidityPeriodStart: startTime, PublicKey: *tlogPubKey, SignatureHashFunc: crypto.SHA256, } } for i := 0; i < len(c.TSACertChainPath); i++ { timestampAuthority, err := parseTAPEMFile(c.TSACertChainPath[i]) if err != nil { return err } timestampAuthorities = append(timestampAuthorities, timestampAuthority) } newTrustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, fulcioCertAuthorities, ctLogs, timestampAuthorities, rekorTransparencyLogs, ) if err != nil { return err } var trBytes []byte trBytes, err = newTrustedRoot.MarshalJSON() if err != nil { return err } if c.Out != "" { err = os.WriteFile(c.Out, trBytes, 0600) if err != nil { return err } } else { fmt.Println(string(trBytes)) } return nil } func parseCAPEMFile(path string) (root.CertificateAuthority, error) { certs, err := parseCerts(path) if err != nil { return nil, err } var ca root.FulcioCertificateAuthority ca.Root = certs[len(certs)-1] ca.ValidityPeriodStart = certs[len(certs)-1].NotBefore if len(certs) > 1 { ca.Intermediates = certs[:len(certs)-1] } return &ca, nil } func parseTAPEMFile(path string) (root.TimestampingAuthority, error) { certs, err := parseCerts(path) if err != nil { return nil, err } var ta root.SigstoreTimestampingAuthority ta.Root = certs[len(certs)-1] ta.ValidityPeriodStart = certs[len(certs)-1].NotBefore if len(certs) > 1 { ta.Intermediates = certs[:len(certs)-1] } return &ta, nil } func parseCerts(path string) ([]*x509.Certificate, error) { var certs []*x509.Certificate contents, err := os.ReadFile(path) if err != nil { return nil, err } for block, contents := pem.Decode(contents); block != nil; block, contents = pem.Decode(contents) { cert, err := x509.ParseCertificate(block.Bytes) if err != nil { return nil, err } certs = append(certs, cert) if len(contents) == 0 { break } } if len(certs) == 0 { return nil, fmt.Errorf("no certificates in file %s", path) } return certs, nil } func getPubKey(path string) (*crypto.PublicKey, string, []byte, error) { pemBytes, err := os.ReadFile(path) if err != nil { return nil, "", []byte{}, err } pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pemBytes) if err != nil { return nil, "", []byte{}, err } keyID, err := cosign.GetTransparencyLogID(pubKey) if err != nil { return nil, "", []byte{}, err } idBytes, err := hex.DecodeString(keyID) if err != nil { return nil, "", []byte{}, err } return &pubKey, keyID, idBytes, nil } cosign-2.5.0/cmd/cosign/cli/trustedroot/trustedroot_test.go000066400000000000000000000066111477503325500241750ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trustedroot import ( "context" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "math/big" "os" "path/filepath" "testing" "github.com/sigstore/sigstore-go/pkg/root" ) func TestCreateCmd(t *testing.T) { ctx := context.Background() // Make some certificate chains td := t.TempDir() fulcioChainPath := filepath.Join(td, "fulcio.pem") makeChain(t, fulcioChainPath, 2) tsaChainPath := filepath.Join(td, "timestamp.pem") makeChain(t, tsaChainPath, 3) outPath := filepath.Join(td, "trustedroot.json") trustedrootCreate := CreateCmd{ CertChain: []string{fulcioChainPath}, Out: outPath, TSACertChainPath: []string{tsaChainPath}, } err := trustedrootCreate.Exec(ctx) checkErr(t, err) tr, err := root.NewTrustedRootFromPath(outPath) checkErr(t, err) fulcioCAs := tr.FulcioCertificateAuthorities() if len(fulcioCAs) != 1 { t.Fatal("unexpected number of fulcio certificate authorities") } if len(fulcioCAs[0].(*root.FulcioCertificateAuthority).Intermediates) != 1 { t.Fatal("unexpected number of fulcio intermediate certificates") } timestampAuthorities := tr.TimestampingAuthorities() if len(timestampAuthorities) != 1 { t.Fatal("unexpected number of timestamp authorities") } if len(timestampAuthorities[0].(*root.SigstoreTimestampingAuthority).Intermediates) != 2 { t.Fatal("unexpected number of timestamp intermediate certificates") } } func makeChain(t *testing.T, path string, size int) { fd, err := os.Create(path) checkErr(t, err) defer fd.Close() chainCert := &x509.Certificate{ SerialNumber: big.NewInt(1), BasicConstraintsValid: true, IsCA: true, } chainKey, err := rsa.GenerateKey(rand.Reader, 512) //nolint:gosec checkErr(t, err) rootDer, err := x509.CreateCertificate(rand.Reader, chainCert, chainCert, &chainKey.PublicKey, chainKey) checkErr(t, err) for i := 1; i < size; i++ { intermediateCert := &x509.Certificate{ SerialNumber: big.NewInt(1 + int64(i)), BasicConstraintsValid: true, IsCA: true, } intermediateKey, err := rsa.GenerateKey(rand.Reader, 512) //nolint:gosec checkErr(t, err) intermediateDer, err := x509.CreateCertificate(rand.Reader, intermediateCert, chainCert, &intermediateKey.PublicKey, chainKey) checkErr(t, err) block := &pem.Block{ Type: "CERTIFICATE", Bytes: intermediateDer, } err = pem.Encode(fd, block) checkErr(t, err) chainCert = intermediateCert chainKey = intermediateKey } // Write out root last block := &pem.Block{ Type: "CERTIFICATE", Bytes: rootDer, } err = pem.Encode(fd, block) checkErr(t, err) // Ensure we handle unexpected content at the end of the PEM file _, err = fd.Write([]byte("asdf\n")) checkErr(t, err) } func checkErr(t *testing.T, err error) { if err != nil { t.Fatal(err) } } cosign-2.5.0/cmd/cosign/cli/upload.go000066400000000000000000000062471477503325500174330ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "flag" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/upload" "github.com/spf13/cobra" ) func Upload() *cobra.Command { cmd := &cobra.Command{ Use: "upload", Short: "Provides utilities for uploading artifacts to a registry", } cmd.AddCommand( uploadBlob(), uploadWASM(), ) return cmd } func uploadBlob() *cobra.Command { o := &options.UploadBlobOptions{} cmd := &cobra.Command{ Use: "blob", Short: "Upload one or more blobs to the supplied container image address.", Example: ` cosign upload blob -f # upload a blob named foo to the location specified by cosign upload blob -f foo # upload a blob named foo to the location specified by , setting the os field to "MYOS". cosign upload blob -f foo:MYOS # upload a blob named foo to the location specified by , setting the os field to "MYOS" and the platform field to "MYPLATFORM". cosign upload blob -f foo:MYOS/MYPLATFORM # upload two blobs named foo-darwin and foo-linux to the location specified by , setting the os fields cosign upload blob -f foo-darwin:darwin -f foo-linux:linux # upload a blob named foo to the location specified by , setting annotations mykey=myvalue. cosign upload blob -a mykey=myvalue -f foo # upload two blobs named foo-darwin and foo-linux to the location specified by , setting annotations cosign upload blob -a mykey=myvalue -a myotherkey="my other value" -f foo-darwin:darwin -f foo-linux:linux `, Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, PreRunE: func(_ *cobra.Command, _ []string) error { if len(o.Files.Files) < 1 { return flag.ErrHelp } return nil }, RunE: func(cmd *cobra.Command, args []string) error { files, err := o.Files.Parse() if err != nil { return err } return upload.BlobCmd(cmd.Context(), o.Registry, files, o.Annotations, o.ContentType, args[0]) }, } o.AddFlags(cmd) return cmd } func uploadWASM() *cobra.Command { o := &options.UploadWASMOptions{} cmd := &cobra.Command{ Use: "wasm", Short: "Upload a wasm module to the supplied container image reference", Example: " cosign upload wasm -f foo.wasm ", Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { return upload.WasmCmd(cmd.Context(), o.Registry, o.File, args[0]) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/upload/000077500000000000000000000000001477503325500170735ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/upload/blob.go000066400000000000000000000034561477503325500203500ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package upload import ( "context" "errors" "fmt" "os" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" cremote "github.com/sigstore/cosign/v2/pkg/cosign/remote" ) func BlobCmd(ctx context.Context, regOpts options.RegistryOptions, files []cremote.File, annotations map[string]string, contentType, imageRef string) error { ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } // We normally discover the content media type by inspecting the byte stream. // Just pass it directly if it's set on the command line. mt := cremote.DefaultMediaTypeGetter if contentType != "" { mt = func(_ []byte) types.MediaType { return types.MediaType(contentType) } } dgstAddr, err := cremote.UploadFiles(ref, files, annotations, mt, regOpts.GetRegistryClientOpts(ctx)...) if err != nil { return err } if len(files) == 0 { return errors.New("no files uploaded?") } if len(files) > 1 { fmt.Fprintf(os.Stderr, "Uploading multi-platform index to %s\n", dgstAddr) } else { fmt.Fprintln(os.Stderr, "Uploaded image to:") fmt.Println(dgstAddr) } return nil } cosign-2.5.0/cmd/cosign/cli/upload/wasm.go000066400000000000000000000027521477503325500203770ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package upload import ( "context" "fmt" "os" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" ) func WasmCmd(ctx context.Context, regOpts options.RegistryOptions, wasmPath, imageRef string) error { b, err := os.ReadFile(wasmPath) if err != nil { return err } ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err } fmt.Fprintf(os.Stderr, "Uploading wasm file from [%s] to [%s].\n", wasmPath, ref.Name()) img, err := static.NewFile(b, static.WithLayerMediaType(types.WasmLayerMediaType), static.WithConfigMediaType(types.WasmConfigMediaType)) if err != nil { return err } return remote.Write(ref, img, regOpts.GetRegistryClientOpts(ctx)...) } cosign-2.5.0/cmd/cosign/cli/verify.go000066400000000000000000000445201477503325500174470ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "context" "fmt" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/sigstore/cosign/v2/internal/ui" "github.com/spf13/cobra" ) const ignoreTLogMessage = "Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the %s." func Verify() *cobra.Command { o := &options.VerifyOptions{} cmd := &cobra.Command{ Use: "verify", Short: "Verify a signature on the supplied container image", Long: `Verify signature and annotations on an image by checking the claims against the transparency log.`, Example: ` cosign verify --key || [ ...] # verify cosign claims and signing certificates on the image with the transparency log cosign verify # verify multiple images cosign verify ... # additionally verify specified annotations cosign verify -a key1=val1 -a key2=val2 # verify image with an on-disk public key cosign verify --key cosign.pub # verify image with an on-disk public key, manually specifying the # signature digest algorithm cosign verify --key cosign.pub --signature-digest-algorithm sha512 # verify image with an on-disk signed image from 'cosign save' cosign verify --key cosign.pub --local-image # verify image with local certificate and certificate chain cosign verify --cert cosign.crt --cert-chain chain.crt # verify image with local certificate and certificate bundles of CA roots # and (optionally) CA intermediates cosign verify --cert cosign.crt --ca-roots ca-roots.pem --ca-intermediates ca-intermediates.pem # verify image using keyless verification with the given certificate # chain and identity parameters, without Fulcio roots (for BYO PKI): cosign verify --cert-chain chain.crt --certificate-oidc-issuer https://issuer.example.com --certificate-identity foo@example.com # verify image with public key provided by URL cosign verify --key https://host.for/[FILE] # verify image with a key stored in an environment variable cosign verify --key env://[ENV_VAR] # verify image with public key stored in Google Cloud KMS cosign verify --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # verify image with public key stored in Hashicorp Vault cosign verify --key hashivault://[KEY] # verify image with public key stored in a Kubernetes secret cosign verify --key k8s://[NAMESPACE]/[KEY] # verify image with public key stored in GitLab with project name cosign verify --key gitlab://[OWNER]/[PROJECT_NAME] # verify image with public key stored in GitLab with project id cosign verify --key gitlab://[PROJECT_ID] `, Args: cobra.MinimumNArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { if o.CommonVerifyOptions.PrivateInfrastructure { o.CommonVerifyOptions.IgnoreTlog = true } annotations, err := o.AnnotationsMap() if err != nil { return err } hashAlgorithm, err := o.SignatureDigest.HashAlgorithm() if err != nil { return err } v := &verify.VerifyCommand{ RegistryOptions: o.Registry, CertVerifyOptions: o.CertVerify, CheckClaims: o.CheckClaims, KeyRef: o.Key, CertRef: o.CertVerify.Cert, CertChain: o.CertVerify.CertChain, CAIntermediates: o.CertVerify.CAIntermediates, CARoots: o.CertVerify.CARoots, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, Output: o.Output, RekorURL: o.Rekor.URL, Attachment: o.Attachment, Annotations: annotations, HashAlgorithm: hashAlgorithm, SignatureRef: o.SignatureRef, PayloadRef: o.PayloadRef, LocalImage: o.LocalImage, Offline: o.CommonVerifyOptions.Offline, TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath, IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog, MaxWorkers: o.CommonVerifyOptions.MaxWorkers, ExperimentalOCI11: o.CommonVerifyOptions.ExperimentalOCI11, UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps, } if o.CommonVerifyOptions.MaxWorkers == 0 { return fmt.Errorf("please set the --max-worker flag to a value that is greater than 0") } if o.Registry.AllowInsecure { v.NameOptions = append(v.NameOptions, name.Insecure) } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) defer cancel() if o.CommonVerifyOptions.IgnoreTlog && !o.CommonVerifyOptions.PrivateInfrastructure { ui.Warnf(ctx, fmt.Sprintf(ignoreTLogMessage, "signature")) } return v.Exec(ctx, args) }, } o.AddFlags(cmd) return cmd } func VerifyAttestation() *cobra.Command { o := &options.VerifyAttestationOptions{} cmd := &cobra.Command{ Use: "verify-attestation", Short: "Verify an attestation on the supplied container image", Long: `Verify an attestation on an image by checking the claims against the transparency log.`, Example: ` cosign verify-attestation --key || [ ...] # verify cosign attestations on the image against the transparency log cosign verify-attestation # verify multiple images cosign verify-attestation ... # additionally verify specified annotations cosign verify-attestation -a key1=val1 -a key2=val2 # verify image with public key cosign verify-attestation --key cosign.pub # verify image attestations with an on-disk signed image from 'cosign save' cosign verify-attestation --key cosign.pub --local-image # verify image with public key provided by URL cosign verify-attestation --key https://host.for/ # verify image with public key stored in Google Cloud KMS cosign verify-attestation --key gcpkms://projects//locations/global/keyRings//cryptoKeys/ # verify image with public key stored in Hashicorp Vault cosign verify-attestation --key hashivault:/// # verify image with public key stored in GitLab with project name cosign verify-attestation --key gitlab://[OWNER]/[PROJECT_NAME] # verify image with public key stored in GitLab with project id cosign verify-attestation --key gitlab://[PROJECT_ID] # verify image with public key and validate attestation based on Rego policy cosign verify-attestation --key cosign.pub --type --policy # verify image with public key and validate attestation based on CUE policy cosign verify-attestation --key cosign.pub --type --policy `, Args: cobra.MinimumNArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { if o.CommonVerifyOptions.PrivateInfrastructure { o.CommonVerifyOptions.IgnoreTlog = true } v := &verify.VerifyAttestationCommand{ RegistryOptions: o.Registry, CommonVerifyOptions: o.CommonVerifyOptions, CheckClaims: o.CheckClaims, CertVerifyOptions: o.CertVerify, CertRef: o.CertVerify.Cert, CertChain: o.CertVerify.CertChain, CAIntermediates: o.CertVerify.CAIntermediates, CARoots: o.CertVerify.CARoots, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSha: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, KeyRef: o.Key, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, Output: o.Output, RekorURL: o.Rekor.URL, PredicateType: o.Predicate.Type, Policies: o.Policies, LocalImage: o.LocalImage, NameOptions: o.Registry.NameOptions(), Offline: o.CommonVerifyOptions.Offline, TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath, IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog, MaxWorkers: o.CommonVerifyOptions.MaxWorkers, UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps, } if o.CommonVerifyOptions.MaxWorkers == 0 { return fmt.Errorf("please set the --max-worker flag to a value that is greater than 0") } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) defer cancel() if o.CommonVerifyOptions.IgnoreTlog && !o.CommonVerifyOptions.PrivateInfrastructure { ui.Warnf(ctx, fmt.Sprintf(ignoreTLogMessage, "attestation")) } return v.Exec(ctx, args) }, } o.AddFlags(cmd) return cmd } func VerifyBlob() *cobra.Command { o := &options.VerifyBlobOptions{} cmd := &cobra.Command{ Use: "verify-blob", Short: "Verify a signature on the supplied blob", Long: `Verify a signature on the supplied blob input using the specified key reference. You may specify either a key, a certificate or a kms reference to verify against. If you use a key or a certificate, you must specify the path to them on disk. The signature may be specified as a path to a file or a base64 encoded string. The blob may be specified as a path to a file or - for stdin.`, Example: ` cosign verify-blob (--key ||)|(--certificate ) --signature # Verify a simple blob and message cosign verify-blob --key cosign.pub (--signature | msg) # Verify a signature with certificate and CA certificate chain cosign verify-blob --certificate cert.pem --certificate-chain certchain.pem --signature $sig # Verify a signature with CA roots and optional intermediate certificates cosign verify-blob --certificate cert.pem --ca-roots caroots.pem [--ca-intermediates caintermediates.pem] --signature $sig # Verify a signature from an environment variable cosign verify-blob --key cosign.pub --signature $sig msg # verify a signature with public key provided by URL cosign verify-blob --key https://host.for/ --signature $sig msg # verify a signature with signature and key provided by URL cosign verify-blob --key https://host.for/ --signature https://example.com/ # Verify a signature against Azure Key Vault cosign verify-blob --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] --signature $sig # Verify a signature against AWS KMS cosign verify-blob --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] --signature $sig # Verify a signature against Google Cloud KMS cosign verify-blob --key gcpkms://projects/[PROJECT ID]/locations/[LOCATION]/keyRings/[KEYRING]/cryptoKeys/[KEY] --signature $sig # Verify a signature against Hashicorp Vault cosign verify-blob --key hashivault://[KEY] --signature $sig # Verify a signature against GitLab with project name cosign verify-blob --key gitlab://[OWNER]/[PROJECT_NAME] --signature $sig # Verify a signature against GitLab with project id cosign verify-blob --key gitlab://[PROJECT_ID] --signature $sig # Verify a signature against a certificate cosign verify-blob --certificate --signature $sig `, Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { if o.CommonVerifyOptions.PrivateInfrastructure { o.CommonVerifyOptions.IgnoreTlog = true } ko := options.KeyOpts{ KeyRef: o.Key, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, RekorURL: o.Rekor.URL, BundlePath: o.BundlePath, RFC3161TimestampPath: o.RFC3161TimestampPath, TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath, NewBundleFormat: o.CommonVerifyOptions.NewBundleFormat, } verifyBlobCmd := &verify.VerifyBlobCmd{ KeyOpts: ko, CertVerifyOptions: o.CertVerify, CertRef: o.CertVerify.Cert, CertChain: o.CertVerify.CertChain, CARoots: o.CertVerify.CARoots, CAIntermediates: o.CertVerify.CAIntermediates, SigRef: o.Signature, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSHA: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, Offline: o.CommonVerifyOptions.Offline, IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog, UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps, TrustedRootPath: o.CommonVerifyOptions.TrustedRootPath, } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) defer cancel() if o.CommonVerifyOptions.IgnoreTlog && !o.CommonVerifyOptions.PrivateInfrastructure { ui.Warnf(ctx, fmt.Sprintf(ignoreTLogMessage, "blob")) } return verifyBlobCmd.Exec(ctx, args[0]) }, } o.AddFlags(cmd) return cmd } func VerifyBlobAttestation() *cobra.Command { o := &options.VerifyBlobAttestationOptions{} cmd := &cobra.Command{ Use: "verify-blob-attestation", Short: "Verify an attestation on the supplied blob", Long: `Verify an attestation on the supplied blob input using the specified key reference. You may specify either a key or a kms reference to verify against. The signature may be specified as a path to a file or a base64 encoded string. The blob may be specified as a path to a file.`, Example: ` cosign verify-blob-attestation (--key ||) --signature [path to BLOB] # Verify a simple blob attestation with a DSSE style signature cosign verify-blob-attestation --key cosign.pub (--signature |)[path to BLOB] `, Args: cobra.MaximumNArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { if o.CommonVerifyOptions.PrivateInfrastructure { o.CommonVerifyOptions.IgnoreTlog = true } ko := options.KeyOpts{ KeyRef: o.Key, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, RekorURL: o.Rekor.URL, BundlePath: o.BundlePath, RFC3161TimestampPath: o.RFC3161TimestampPath, TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath, NewBundleFormat: o.CommonVerifyOptions.NewBundleFormat, } v := verify.VerifyBlobAttestationCommand{ KeyOpts: ko, PredicateType: o.Type, CheckClaims: o.CheckClaims, SignaturePath: o.SignaturePath, CertVerifyOptions: o.CertVerify, CertRef: o.CertVerify.Cert, CertChain: o.CertVerify.CertChain, CARoots: o.CertVerify.CARoots, CAIntermediates: o.CertVerify.CAIntermediates, CertGithubWorkflowTrigger: o.CertVerify.CertGithubWorkflowTrigger, CertGithubWorkflowSHA: o.CertVerify.CertGithubWorkflowSha, CertGithubWorkflowName: o.CertVerify.CertGithubWorkflowName, CertGithubWorkflowRepository: o.CertVerify.CertGithubWorkflowRepository, CertGithubWorkflowRef: o.CertVerify.CertGithubWorkflowRef, IgnoreSCT: o.CertVerify.IgnoreSCT, SCTRef: o.CertVerify.SCT, Offline: o.CommonVerifyOptions.Offline, IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog, UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps, TrustedRootPath: o.CommonVerifyOptions.TrustedRootPath, } // We only use the blob if we are checking claims. if len(args) == 0 && o.CheckClaims { return fmt.Errorf("no path to blob passed in, run `cosign verify-blob-attestation -h` for more help") } var path string if len(args) > 0 { path = args[0] } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) defer cancel() if o.CommonVerifyOptions.IgnoreTlog && !o.CommonVerifyOptions.PrivateInfrastructure { ui.Warnf(ctx, fmt.Sprintf(ignoreTLogMessage, "blob attestation")) } return v.Exec(ctx, path) }, } o.AddFlags(cmd) return cmd } cosign-2.5.0/cmd/cosign/cli/verify/000077500000000000000000000000001477503325500171135ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/cli/verify/verify.go000066400000000000000000000443201477503325500207510ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "bytes" "context" "crypto" "crypto/x509" "encoding/base64" "encoding/json" "errors" "flag" "fmt" "os" "path/filepath" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" cosignError "github.com/sigstore/cosign/v2/cmd/cosign/errors" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/v2/pkg/oci" sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/payload" ) // VerifyCommand verifies a signature on a supplied container image // nolint type VerifyCommand struct { options.RegistryOptions options.CertVerifyOptions options.CommonVerifyOptions CheckClaims bool KeyRef string CertRef string CertGithubWorkflowTrigger string CertGithubWorkflowSha string CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string CAIntermediates string CARoots string CertChain string CertOidcProvider string IgnoreSCT bool SCTRef string Sk bool Slot string Output string RekorURL string Attachment string Annotations sigs.AnnotationsMap SignatureRef string PayloadRef string HashAlgorithm crypto.Hash LocalImage bool NameOptions []name.Option Offline bool TSACertChainPath string UseSignedTimestamps bool IgnoreTlog bool MaxWorkers int ExperimentalOCI11 bool } // Exec runs the verification command func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if len(images) == 0 { return flag.ErrHelp } switch c.Attachment { case "sbom": fmt.Fprintln(os.Stderr, options.SBOMAttachmentDeprecation) case "": break default: return flag.ErrHelp } // always default to sha256 if the algorithm hasn't been explicitly set if c.HashAlgorithm == 0 { c.HashAlgorithm = crypto.SHA256 } var identities []cosign.Identity if c.KeyRef == "" { identities, err = c.Identities() if err != nil { return err } } ociremoteOpts, err := c.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } co := &cosign.CheckOpts{ Annotations: c.Annotations.Annotations, RegistryClientOpts: ociremoteOpts, CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger, CertGithubWorkflowSha: c.CertGithubWorkflowSha, CertGithubWorkflowName: c.CertGithubWorkflowName, CertGithubWorkflowRepository: c.CertGithubWorkflowRepository, CertGithubWorkflowRef: c.CertGithubWorkflowRef, IgnoreSCT: c.IgnoreSCT, SignatureRef: c.SignatureRef, PayloadRef: c.PayloadRef, Identities: identities, Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, MaxWorkers: c.MaxWorkers, ExperimentalOCI11: c.ExperimentalOCI11, UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps, NewBundleFormat: c.NewBundleFormat, } if c.TrustedRootPath != "" { co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) if err != nil { return fmt.Errorf("loading trusted root: %w", err) } } if c.CheckClaims { co.ClaimVerifier = cosign.SimpleClaimVerifier } // If we are using signed timestamps, we need to load the TSA certificates if co.UseSignedTimestamps { tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets) if err != nil { return fmt.Errorf("unable to load TSA certificates: %w", err) } co.TSACertificate = tsaCertificates.LeafCert co.TSARootCertificates = tsaCertificates.RootCert co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts } if !c.IgnoreTlog { if c.RekorURL != "" { rekorClient, err := rekor.NewClient(c.RekorURL) if err != nil { return fmt.Errorf("creating Rekor client: %w", err) } co.RekorClient = rekorClient } // This performs an online fetch of the Rekor public keys, but this is needed // for verifying tlog entries (both online and offline). co.RekorPubKeys, err = cosign.GetRekorPubs(ctx) if err != nil { return fmt.Errorf("getting Rekor public keys: %w", err) } } if keylessVerification(c.KeyRef, c.Sk) { if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { return err } } keyRef := c.KeyRef certRef := c.CertRef // Ignore Signed Certificate Timestamp if the flag is set or a key is provided if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) { co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) if err != nil { return fmt.Errorf("getting ctlog public keys: %w", err) } } // Keys are optional! var pubKey signature.Verifier switch { case keyRef != "": pubKey, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, c.HashAlgorithm) if err != nil { return fmt.Errorf("loading public key: %w", err) } pkcs11Key, ok := pubKey.(*pkcs11key.Key) if ok { defer pkcs11Key.Close() } case c.Sk: sk, err := pivkey.GetKeyWithSlot(c.Slot) if err != nil { return fmt.Errorf("opening piv token: %w", err) } defer sk.Close() pubKey, err = sk.Verifier() if err != nil { return fmt.Errorf("initializing piv token verifier: %w", err) } case certRef != "": cert, err := loadCertFromFileOrURL(c.CertRef) if err != nil { return err } switch { case c.CertChain == "" && co.RootCerts == nil: // If no certChain and no CARoots are passed, the Fulcio root certificate will be used co.RootCerts, err = fulcio.GetRoots() if err != nil { return fmt.Errorf("getting Fulcio roots: %w", err) } co.IntermediateCerts, err = fulcio.GetIntermediates() if err != nil { return fmt.Errorf("getting Fulcio intermediates: %w", err) } pubKey, err = cosign.ValidateAndUnpackCert(cert, co) if err != nil { return err } case c.CertChain != "": // Verify certificate with chain chain, err := loadCertChainFromFileOrURL(c.CertChain) if err != nil { return err } pubKey, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) if err != nil { return err } case co.RootCerts != nil: // Verify certificate with root (and if given, intermediate) certificate pubKey, err = cosign.ValidateAndUnpackCert(cert, co) if err != nil { return err } default: return errors.New("no certificate chain provided to verify certificate") } if c.SCTRef != "" { sct, err := os.ReadFile(filepath.Clean(c.SCTRef)) if err != nil { return fmt.Errorf("reading sct from file: %w", err) } co.SCT = sct } default: // Do nothing. Neither keyRef, c.Sk, nor certRef were set - can happen for example when using Fulcio and TSA. // For an example see the TestAttachWithRFC3161Timestamp test in test/e2e_test.go. } co.SigVerifier = pubKey // NB: There are only 2 kinds of verification right now: // 1. You gave us the public key explicitly to verify against so co.SigVerifier is non-nil or, // 2. We’re going to find an x509 certificate on the signature and verify against // Fulcio root trust (or user supplied root trust) // TODO(nsmith5): Refactor this verification logic to pass back _how_ verification // was performed so we don't need to use this fragile logic here. fulcioVerified := (co.SigVerifier == nil) for _, img := range images { if c.LocalImage { verified, bundleVerified, err := cosign.VerifyLocalImageSignatures(ctx, img, co) if err != nil { return err } PrintVerificationHeader(ctx, img, co, bundleVerified, fulcioVerified) PrintVerification(ctx, verified, c.Output) } else { ref, err := name.ParseReference(img, c.NameOptions...) if err != nil { return fmt.Errorf("parsing reference: %w", err) } ref, err = sign.GetAttachedImageRef(ref, c.Attachment, ociremoteOpts...) if err != nil { return fmt.Errorf("resolving attachment type %s for image %s: %w", c.Attachment, img, err) } verified, bundleVerified, err := cosign.VerifyImageSignatures(ctx, ref, co) if err != nil { return cosignError.WrapError(err) } PrintVerificationHeader(ctx, ref.Name(), co, bundleVerified, fulcioVerified) PrintVerification(ctx, verified, c.Output) } } return nil } func PrintVerificationHeader(ctx context.Context, imgRef string, co *cosign.CheckOpts, bundleVerified, fulcioVerified bool) { ui.Infof(ctx, "\nVerification for %s --", imgRef) ui.Infof(ctx, "The following checks were performed on each of these signatures:") if co.ClaimVerifier != nil { if co.Annotations != nil { ui.Infof(ctx, " - The specified annotations were verified.") } ui.Infof(ctx, " - The cosign claims were validated") } if bundleVerified { ui.Infof(ctx, " - Existence of the claims in the transparency log was verified offline") } else if co.RekorClient != nil { ui.Infof(ctx, " - The claims were present in the transparency log") ui.Infof(ctx, " - The signatures were integrated into the transparency log when the certificate was valid") } if co.SigVerifier != nil { ui.Infof(ctx, " - The signatures were verified against the specified public key") } if fulcioVerified { ui.Infof(ctx, " - The code-signing certificate was verified using trusted certificate authority certificates") } } // PrintVerification logs details about the verification to stdout func PrintVerification(ctx context.Context, verified []oci.Signature, output string) { switch output { case "text": for _, sig := range verified { if cert, err := sig.Cert(); err == nil && cert != nil { ce := cosign.CertExtensions{Cert: cert} sub := "" if sans := cryptoutils.GetSubjectAlternateNames(cert); len(sans) > 0 { sub = sans[0] } ui.Infof(ctx, "Certificate subject: %s", sub) if issuerURL := ce.GetIssuer(); issuerURL != "" { ui.Infof(ctx, "Certificate issuer URL: %s", issuerURL) } if githubWorkflowTrigger := ce.GetCertExtensionGithubWorkflowTrigger(); githubWorkflowTrigger != "" { ui.Infof(ctx, "GitHub Workflow Trigger: %s", githubWorkflowTrigger) } if githubWorkflowSha := ce.GetExtensionGithubWorkflowSha(); githubWorkflowSha != "" { ui.Infof(ctx, "GitHub Workflow SHA: %s", githubWorkflowSha) } if githubWorkflowName := ce.GetCertExtensionGithubWorkflowName(); githubWorkflowName != "" { ui.Infof(ctx, "GitHub Workflow Name: %s", githubWorkflowName) } if githubWorkflowRepository := ce.GetCertExtensionGithubWorkflowRepository(); githubWorkflowRepository != "" { ui.Infof(ctx, "GitHub Workflow Repository: %s", githubWorkflowRepository) } if githubWorkflowRef := ce.GetCertExtensionGithubWorkflowRef(); githubWorkflowRef != "" { ui.Infof(ctx, "GitHub Workflow Ref: %s", githubWorkflowRef) } } p, err := sig.Payload() if err != nil { fmt.Fprintf(os.Stderr, "Error fetching payload: %v", err) return } fmt.Println(string(p)) } default: var outputKeys []payload.SimpleContainerImage for _, sig := range verified { p, err := sig.Payload() if err != nil { fmt.Fprintf(os.Stderr, "Error fetching payload: %v", err) return } ss := payload.SimpleContainerImage{} if err := json.Unmarshal(p, &ss); err != nil { fmt.Println("error decoding the payload:", err.Error()) return } if cert, err := sig.Cert(); err == nil && cert != nil { ce := cosign.CertExtensions{Cert: cert} if ss.Optional == nil { ss.Optional = make(map[string]interface{}) } sub := "" if sans := cryptoutils.GetSubjectAlternateNames(cert); len(sans) > 0 { sub = sans[0] } ss.Optional["Subject"] = sub if issuerURL := ce.GetIssuer(); issuerURL != "" { ss.Optional["Issuer"] = issuerURL ss.Optional[cosign.CertExtensionOIDCIssuer] = issuerURL } if githubWorkflowTrigger := ce.GetCertExtensionGithubWorkflowTrigger(); githubWorkflowTrigger != "" { ss.Optional[cosign.CertExtensionMap[cosign.CertExtensionGithubWorkflowTrigger]] = githubWorkflowTrigger ss.Optional[cosign.CertExtensionGithubWorkflowTrigger] = githubWorkflowTrigger } if githubWorkflowSha := ce.GetExtensionGithubWorkflowSha(); githubWorkflowSha != "" { ss.Optional[cosign.CertExtensionMap[cosign.CertExtensionGithubWorkflowSha]] = githubWorkflowSha ss.Optional[cosign.CertExtensionGithubWorkflowSha] = githubWorkflowSha } if githubWorkflowName := ce.GetCertExtensionGithubWorkflowName(); githubWorkflowName != "" { ss.Optional[cosign.CertExtensionMap[cosign.CertExtensionGithubWorkflowName]] = githubWorkflowName ss.Optional[cosign.CertExtensionGithubWorkflowName] = githubWorkflowName } if githubWorkflowRepository := ce.GetCertExtensionGithubWorkflowRepository(); githubWorkflowRepository != "" { ss.Optional[cosign.CertExtensionMap[cosign.CertExtensionGithubWorkflowRepository]] = githubWorkflowRepository ss.Optional[cosign.CertExtensionGithubWorkflowRepository] = githubWorkflowRepository } if githubWorkflowRef := ce.GetCertExtensionGithubWorkflowRef(); githubWorkflowRef != "" { ss.Optional[cosign.CertExtensionMap[cosign.CertExtensionGithubWorkflowRef]] = githubWorkflowRef ss.Optional[cosign.CertExtensionGithubWorkflowRef] = githubWorkflowRef } } if bundle, err := sig.Bundle(); err == nil && bundle != nil { if ss.Optional == nil { ss.Optional = make(map[string]interface{}) } ss.Optional["Bundle"] = bundle } if rfc3161Timestamp, err := sig.RFC3161Timestamp(); err == nil && rfc3161Timestamp != nil { if ss.Optional == nil { ss.Optional = make(map[string]interface{}) } ss.Optional["RFC3161Timestamp"] = rfc3161Timestamp } outputKeys = append(outputKeys, ss) } b, err := json.Marshal(outputKeys) if err != nil { fmt.Println("error when generating the output:", err.Error()) return } fmt.Printf("\n%s\n", string(b)) } } func loadCertFromFileOrURL(path string) (*x509.Certificate, error) { pems, err := blob.LoadFileOrURL(path) if err != nil { return nil, err } return loadCertFromPEM(pems) } func loadCertFromPEM(pems []byte) (*x509.Certificate, error) { var out []byte out, err := base64.StdEncoding.DecodeString(string(pems)) if err != nil { // not a base64 out = pems } certs, err := cryptoutils.UnmarshalCertificatesFromPEM(out) if err != nil { return nil, err } if len(certs) == 0 { return nil, errors.New("no certs found in pem file") } return certs[0], nil } func loadCertChainFromFileOrURL(path string) ([]*x509.Certificate, error) { pems, err := blob.LoadFileOrURL(path) if err != nil { return nil, err } certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(pems)) if err != nil { return nil, err } return certs, nil } func keylessVerification(keyRef string, sk bool) bool { if keyRef != "" { return false } if sk { return false } return true } func shouldVerifySCT(ignoreSCT bool, keyRef string, sk bool) bool { if keyRef != "" { return false } if sk { return false } if ignoreSCT { return false } return true } // loadCertsKeylessVerification loads certificates provided as a certificate chain or CA roots + CA intermediate // certificate files. If both certChain and caRootsFile are empty strings, the Fulcio roots are loaded. // // The co *cosign.CheckOpts is both input and output parameter - it gets updated // with the root and intermediate certificates needed for verification. func loadCertsKeylessVerification(certChainFile string, caRootsFile string, caIntermediatesFile string, co *cosign.CheckOpts) error { var err error switch { case certChainFile != "": chain, err := loadCertChainFromFileOrURL(certChainFile) if err != nil { return err } co.RootCerts = x509.NewCertPool() co.RootCerts.AddCert(chain[len(chain)-1]) if len(chain) > 1 { co.IntermediateCerts = x509.NewCertPool() for _, cert := range chain[:len(chain)-1] { co.IntermediateCerts.AddCert(cert) } } case caRootsFile != "": caRoots, err := loadCertChainFromFileOrURL(caRootsFile) if err != nil { return err } co.RootCerts = x509.NewCertPool() if len(caRoots) > 0 { for _, cert := range caRoots { co.RootCerts.AddCert(cert) } } if caIntermediatesFile != "" { caIntermediates, err := loadCertChainFromFileOrURL(caIntermediatesFile) if err != nil { return err } if len(caIntermediates) > 0 { co.IntermediateCerts = x509.NewCertPool() for _, cert := range caIntermediates { co.IntermediateCerts.AddCert(cert) } } } default: // This performs an online fetch of the Fulcio roots from a TUF repository. // This is needed for verifying keyless certificates (both online and offline). co.RootCerts, err = fulcio.GetRoots() if err != nil { return fmt.Errorf("getting Fulcio roots: %w", err) } co.IntermediateCerts, err = fulcio.GetIntermediates() if err != nil { return fmt.Errorf("getting Fulcio intermediates: %w", err) } } return nil } cosign-2.5.0/cmd/cosign/cli/verify/verify_attestation.go000066400000000000000000000301711477503325500233670ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "context" "errors" "flag" "fmt" "os" "path/filepath" "strings" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/cue" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/v2/pkg/cosign/rego" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/policy" sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore-go/pkg/root" ) // VerifyAttestationCommand verifies a signature on a supplied container image // nolint type VerifyAttestationCommand struct { options.RegistryOptions options.CertVerifyOptions options.CommonVerifyOptions CheckClaims bool KeyRef string CertRef string CertGithubWorkflowTrigger string CertGithubWorkflowSha string CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string CAIntermediates string CARoots string CertChain string IgnoreSCT bool SCTRef string Sk bool Slot string Output string RekorURL string PredicateType string Policies []string LocalImage bool NameOptions []name.Option Offline bool TSACertChainPath string IgnoreTlog bool MaxWorkers int UseSignedTimestamps bool } // Exec runs the verification command func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (err error) { if len(images) == 0 { return flag.ErrHelp } // We can't have both a key and a security key if options.NOf(c.KeyRef, c.Sk) > 1 { return &options.KeyParseError{} } var identities []cosign.Identity if c.KeyRef == "" { identities, err = c.Identities() if err != nil { return err } } ociremoteOpts, err := c.ClientOpts(ctx) if err != nil { return fmt.Errorf("constructing client options: %w", err) } co := &cosign.CheckOpts{ RegistryClientOpts: ociremoteOpts, CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger, CertGithubWorkflowSha: c.CertGithubWorkflowSha, CertGithubWorkflowName: c.CertGithubWorkflowName, CertGithubWorkflowRepository: c.CertGithubWorkflowRepository, CertGithubWorkflowRef: c.CertGithubWorkflowRef, IgnoreSCT: c.IgnoreSCT, Identities: identities, Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, MaxWorkers: c.MaxWorkers, UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps, NewBundleFormat: c.NewBundleFormat, } if c.CheckClaims { co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier } if c.NewBundleFormat { if err = checkSigstoreBundleUnsupportedOptions(c); err != nil { return err } co.TrustedMaterial, err = loadTrustedRoot(ctx, c.TrustedRootPath) if err != nil { return err } } // Ignore Signed Certificate Timestamp if the flag is set or a key is provided if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) && !c.NewBundleFormat { co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) if err != nil { return fmt.Errorf("getting ctlog public keys: %w", err) } } // If we are using signed timestamps, we need to load the TSA certificates if co.UseSignedTimestamps && !c.NewBundleFormat { tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets) if err != nil { return fmt.Errorf("unable to load TSA certificates: %w", err) } co.TSACertificate = tsaCertificates.LeafCert co.TSARootCertificates = tsaCertificates.RootCert co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts } if !c.IgnoreTlog && !co.NewBundleFormat { if c.RekorURL != "" { rekorClient, err := rekor.NewClient(c.RekorURL) if err != nil { return fmt.Errorf("creating Rekor client: %w", err) } co.RekorClient = rekorClient } // This performs an online fetch of the Rekor public keys, but this is needed // for verifying tlog entries (both online and offline). co.RekorPubKeys, err = cosign.GetRekorPubs(ctx) if err != nil { return fmt.Errorf("getting Rekor public keys: %w", err) } } if keylessVerification(c.KeyRef, c.Sk) { if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { return err } } keyRef := c.KeyRef // Keys are optional! switch { case keyRef != "": co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, keyRef) if err != nil { return fmt.Errorf("loading public key: %w", err) } pkcs11Key, ok := co.SigVerifier.(*pkcs11key.Key) if ok { defer pkcs11Key.Close() } case c.Sk: sk, err := pivkey.GetKeyWithSlot(c.Slot) if err != nil { return fmt.Errorf("opening piv token: %w", err) } defer sk.Close() co.SigVerifier, err = sk.Verifier() if err != nil { return fmt.Errorf("initializing piv token verifier: %w", err) } case c.CertRef != "": if c.NewBundleFormat { // This shouldn't happen because we already checked for this above in checkSigstoreBundleUnsupportedOptions return fmt.Errorf("unsupported: certificate reference currently not supported with --new-bundle-format") } cert, err := loadCertFromFileOrURL(c.CertRef) if err != nil { return fmt.Errorf("loading certificate from reference: %w", err) } if c.CertChain == "" { // If no certChain is passed, the Fulcio root certificate will be used co.RootCerts, err = fulcio.GetRoots() if err != nil { return fmt.Errorf("getting Fulcio roots: %w", err) } co.IntermediateCerts, err = fulcio.GetIntermediates() if err != nil { return fmt.Errorf("getting Fulcio intermediates: %w", err) } co.SigVerifier, err = cosign.ValidateAndUnpackCert(cert, co) if err != nil { return fmt.Errorf("creating certificate verifier: %w", err) } } else { // Verify certificate with chain chain, err := loadCertChainFromFileOrURL(c.CertChain) if err != nil { return err } co.SigVerifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) if err != nil { return fmt.Errorf("creating certificate verifier: %w", err) } } if c.SCTRef != "" { sct, err := os.ReadFile(filepath.Clean(c.SCTRef)) if err != nil { return fmt.Errorf("reading sct from file: %w", err) } co.SCT = sct } case c.TrustedRootPath != "": if !c.NewBundleFormat { return fmt.Errorf("unsupported: trusted root path currently only supported with --new-bundle-format") } // If a trusted root path is provided, we will use it to verify the bundle. // Otherwise, the verifier will default to the public good instance. co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) if err != nil { return fmt.Errorf("creating trusted root from path: %w", err) } case c.CARoots != "": // CA roots + possible intermediates are already loaded into co.RootCerts with the call to // loadCertsKeylessVerification above. } // NB: There are only 2 kinds of verification right now: // 1. You gave us the public key explicitly to verify against so co.SigVerifier is non-nil or, // 2. We're going to find an x509 certificate on the signature and verify against Fulcio root trust // TODO(nsmith5): Refactor this verification logic to pass back _how_ verification // was performed so we don't need to use this fragile logic here. fulcioVerified := (co.SigVerifier == nil) for _, imageRef := range images { var verified []oci.Signature var bundleVerified bool if c.LocalImage { verified, bundleVerified, err = cosign.VerifyLocalImageAttestations(ctx, imageRef, co) if err != nil { return err } } else { ref, err := name.ParseReference(imageRef, c.NameOptions...) if err != nil { return err } verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co) if err != nil { return err } } var cuePolicies, regoPolicies []string for _, policy := range c.Policies { switch filepath.Ext(policy) { case ".rego": regoPolicies = append(regoPolicies, policy) case ".cue": cuePolicies = append(cuePolicies, policy) default: return errors.New("invalid policy format, expected .cue or .rego") } } var checked []oci.Signature var validationErrors []error // To aid in determining if there's a mismatch in what predicateType // we're looking for and what we checked, keep track of them here so // that we can help the user figure out if there's a typo, etc. checkedPredicateTypes := []string{} for _, vp := range verified { payload, gotPredicateType, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, vp) if err != nil { return fmt.Errorf("converting to consumable policy validation: %w", err) } checkedPredicateTypes = append(checkedPredicateTypes, gotPredicateType) if len(payload) == 0 { // This is not the predicate type we're looking for. continue } if len(cuePolicies) > 0 { ui.Infof(ctx, "will be validating against CUE policies: %v", cuePolicies) cueValidationErr := cue.ValidateJSON(payload, cuePolicies) if cueValidationErr != nil { validationErrors = append(validationErrors, cueValidationErr) continue } } if len(regoPolicies) > 0 { ui.Infof(ctx, "will be validating against Rego policies: %v", regoPolicies) regoValidationErrs := rego.ValidateJSON(payload, regoPolicies) if len(regoValidationErrs) > 0 { validationErrors = append(validationErrors, regoValidationErrs...) continue } } checked = append(checked, vp) } if len(validationErrors) > 0 { ui.Infof(ctx, "There are %d number of errors occurred during the validation:\n", len(validationErrors)) for _, v := range validationErrors { ui.Infof(ctx, "- %v", v) } return fmt.Errorf("%d validation errors occurred", len(validationErrors)) } if len(checked) == 0 { return fmt.Errorf("none of the attestations matched the predicate type: %s, found: %s", c.PredicateType, strings.Join(checkedPredicateTypes, ",")) } // TODO: add CUE validation report to `PrintVerificationHeader`. PrintVerificationHeader(ctx, imageRef, co, bundleVerified, fulcioVerified) // The attestations are always JSON, so use the raw "text" mode for outputting them instead of conversion PrintVerification(ctx, checked, "text") } return nil } func checkSigstoreBundleUnsupportedOptions(c *VerifyAttestationCommand) error { if c.CertRef != "" { return fmt.Errorf("unsupported: certificate may not be provided using --certificate when using --new-bundle-format (cert must be in bundle)") } if c.CertChain != "" { return fmt.Errorf("unsupported: certificate chain may not be provided using --certificate-chain when using --new-bundle-format (cert must be in bundle)") } if c.CARoots != "" || c.CAIntermediates != "" { return fmt.Errorf("unsupported: CA roots/intermediates must be provided using --trusted-root when using --new-bundle-format") } if c.TSACertChainPath != "" { return fmt.Errorf("unsupported: TSA certificate chain path may only be provided using --trusted-root when using --new-bundle-format") } return nil } cosign-2.5.0/cmd/cosign/cli/verify/verify_attestation_test.go000066400000000000000000000027521477503325500244320ustar00rootroot00000000000000// Copyright 2022 the Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "context" "testing" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" ) func TestVerifyAttestationMissingSubject(t *testing.T) { ctx := context.Background() verifyAttestation := VerifyAttestationCommand{ CertRef: "cert.pem", CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: "issuer", }, } err := verifyAttestation.Exec(ctx, []string{"foo", "bar", "baz"}) if err == nil { t.Fatal("verifyAttestation expected 'need --certificate-identity'") } } func TestVerifyAttestationMissingIssuer(t *testing.T) { ctx := context.Background() verifyAttestation := VerifyAttestationCommand{ CertRef: "cert.pem", CertVerifyOptions: options.CertVerifyOptions{ CertIdentity: "subject", }, } err := verifyAttestation.Exec(ctx, []string{"foo", "bar", "baz"}) if err == nil { t.Fatal("verifyAttestation expected 'need --certificate-oidc-issuer'") } } cosign-2.5.0/cmd/cosign/cli/verify/verify_blob.go000066400000000000000000000300661477503325500217510ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "bytes" "context" "crypto" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/json" "errors" "fmt" "io" "io/fs" "os" "path/filepath" "strings" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/v2/pkg/oci/static" sigs "github.com/sigstore/cosign/v2/pkg/signature" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/cryptoutils" ) func isb64(data []byte) bool { _, err := base64.StdEncoding.DecodeString(string(data)) return err == nil } // nolint type VerifyBlobCmd struct { options.KeyOpts options.CertVerifyOptions CertRef string CAIntermediates string CARoots string CertChain string SigRef string TrustedRootPath string CertGithubWorkflowTrigger string CertGithubWorkflowSHA string CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string IgnoreSCT bool SCTRef string Offline bool UseSignedTimestamps bool IgnoreTlog bool } // nolint func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { // Require a certificate/key OR a local bundle file that has the cert. if options.NOf(c.KeyRef, c.CertRef, c.Sk, c.BundlePath) == 0 { return fmt.Errorf("provide a key with --key or --sk, a certificate to verify against with --certificate, or a bundle with --bundle") } // Key, sk, and cert are mutually exclusive. if options.NOf(c.KeyRef, c.Sk, c.CertRef) > 1 { return &options.PubKeyParseError{} } var identities []cosign.Identity var err error if c.KeyRef == "" { identities, err = c.Identities() if err != nil { return err } } co := &cosign.CheckOpts{ CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger, CertGithubWorkflowSha: c.CertGithubWorkflowSHA, CertGithubWorkflowName: c.CertGithubWorkflowName, CertGithubWorkflowRepository: c.CertGithubWorkflowRepository, CertGithubWorkflowRef: c.CertGithubWorkflowRef, IgnoreSCT: c.IgnoreSCT, Identities: identities, Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps, NewBundleFormat: c.KeyOpts.NewBundleFormat || checkNewBundle(c.BundlePath), } // Keys are optional! var cert *x509.Certificate opts := make([]static.Option, 0) switch { case c.KeyRef != "": co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef) if err != nil { return fmt.Errorf("loading public key: %w", err) } pkcs11Key, ok := co.SigVerifier.(*pkcs11key.Key) if ok { defer pkcs11Key.Close() } case c.Sk: sk, err := pivkey.GetKeyWithSlot(c.Slot) if err != nil { return fmt.Errorf("opening piv token: %w", err) } defer sk.Close() co.SigVerifier, err = sk.Verifier() if err != nil { return fmt.Errorf("loading public key from token: %w", err) } case c.CertRef != "": cert, err = loadCertFromFileOrURL(c.CertRef) if err != nil { return err } } if co.NewBundleFormat { if options.NOf(c.RFC3161TimestampPath, c.TSACertChainPath, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SigRef, c.SCTRef) > 0 { return fmt.Errorf("when using --new-bundle-format, please supply signed content with --bundle and verification content with --trusted-root") } if co.TrustedMaterial == nil { co.TrustedMaterial, err = loadTrustedRoot(ctx, c.TrustedRootPath) if err != nil { return err } } bundle, err := sgbundle.LoadJSONFromPath(c.BundlePath) if err != nil { return err } var artifactPolicyOption sgverify.ArtifactPolicyOption blobBytes, err := payloadBytes(blobRef) if err != nil { alg, digest, payloadDigestError := payloadDigest(blobRef) if payloadDigestError != nil { return err } artifactPolicyOption = sgverify.WithArtifactDigest(alg, digest) } else { artifactPolicyOption = sgverify.WithArtifact(bytes.NewReader(blobBytes)) } _, err = cosign.VerifyNewBundle(ctx, co, artifactPolicyOption, bundle) if err != nil { return err } ui.Infof(ctx, "Verified OK") return nil } blobBytes, err := payloadBytes(blobRef) if err != nil { return err } if c.TrustedRootPath != "" { return fmt.Errorf("--trusted-root only supported with --new-bundle-format") } if c.RFC3161TimestampPath != "" && !co.UseSignedTimestamps { return fmt.Errorf("when specifying --rfc3161-timestamp-path, you must also specify --use-signed-timestamps or --timestamp-certificate-chain") } else if c.RFC3161TimestampPath == "" && co.UseSignedTimestamps { return fmt.Errorf("when specifying --use-signed-timestamps or --timestamp-certificate-chain, you must also specify --rfc3161-timestamp-path") } if co.UseSignedTimestamps { tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets) if err != nil { return fmt.Errorf("unable to load TSA certificates: %w", err) } co.TSACertificate = tsaCertificates.LeafCert co.TSARootCertificates = tsaCertificates.RootCert co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts } if !c.IgnoreTlog { if c.RekorURL != "" { rekorClient, err := rekor.NewClient(c.RekorURL) if err != nil { return fmt.Errorf("creating Rekor client: %w", err) } co.RekorClient = rekorClient } // This performs an online fetch of the Rekor public keys, but this is needed // for verifying tlog entries (both online and offline). co.RekorPubKeys, err = cosign.GetRekorPubs(ctx) if err != nil { return fmt.Errorf("getting Rekor public keys: %w", err) } } if keylessVerification(c.KeyRef, c.Sk) { if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { return err } } if c.BundlePath != "" { b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath) if err != nil { return err } // A certificate is required in the bundle unless we specified with // --key, --sk, or --certificate. if b.Cert == "" && co.SigVerifier == nil && cert == nil { return fmt.Errorf("bundle does not contain cert for verification, please provide public key") } // We have to condition on this because sign-blob may not output the signing // key to the bundle when there is no tlog upload. if b.Cert != "" { // b.Cert can either be a certificate or public key certBytes := []byte(b.Cert) if isb64(certBytes) { certBytes, _ = base64.StdEncoding.DecodeString(b.Cert) } bundleCert, err := loadCertFromPEM(certBytes) if err != nil { // check if cert is actually a public key co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256) if err != nil { return fmt.Errorf("loading verifier from bundle: %w", err) } } // if a cert was passed in, make sure it matches the cert in the bundle if cert != nil && !cert.Equal(bundleCert) { return fmt.Errorf("the cert passed in does not match the cert in the provided bundle") } cert = bundleCert } opts = append(opts, static.WithBundle(b.Bundle)) } if c.RFC3161TimestampPath != "" { var rfc3161Timestamp bundle.RFC3161Timestamp ts, err := blob.LoadFileOrURL(c.RFC3161TimestampPath) if err != nil { return err } if err := json.Unmarshal(ts, &rfc3161Timestamp); err != nil { return err } opts = append(opts, static.WithRFC3161Timestamp(&rfc3161Timestamp)) } // Set an SCT if provided via the CLI. if c.SCTRef != "" { sct, err := os.ReadFile(filepath.Clean(c.SCTRef)) if err != nil { return fmt.Errorf("reading sct from file: %w", err) } co.SCT = sct } // Set a cert chain if provided. var chainPEM []byte switch { case c.CertChain != "": chain, err := loadCertChainFromFileOrURL(c.CertChain) if err != nil { return err } if chain == nil { return errors.New("expected certificate chain in --certificate-chain") } // Set the last one in the co.RootCerts. This is trusted, as its passed in // via the CLI. if co.RootCerts == nil { co.RootCerts = x509.NewCertPool() } co.RootCerts.AddCert(chain[len(chain)-1]) // Use the whole as the cert chain in the signature object. // The last one is omitted because it is considered the "root". chainPEM, err = cryptoutils.MarshalCertificatesToPEM(chain) if err != nil { return err } case c.CARoots != "": // CA roots + possible intermediates are already loaded into co.RootCerts with the call to // loadCertsKeylessVerification above. } // Gather the cert for the signature and add the cert along with the // cert chain into the signature object. var certPEM []byte if cert != nil { certPEM, err = cryptoutils.MarshalCertificateToPEM(cert) if err != nil { return err } opts = append(opts, static.WithCertChain(certPEM, chainPEM)) } // Ignore Signed Certificate Timestamp if the flag is set or a key is provided if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) { co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) if err != nil { return fmt.Errorf("getting ctlog public keys: %w", err) } } sig, err := base64signature(c.SigRef, c.BundlePath) if err != nil { return err } signature, err := static.NewSignature(blobBytes, sig, opts...) if err != nil { return err } if _, err = cosign.VerifyBlobSignature(ctx, signature, co); err != nil { return err } ui.Infof(ctx, "Verified OK") return nil } // base64signature returns the base64 encoded signature func base64signature(sigRef, bundlePath string) (string, error) { var targetSig []byte var err error switch { case sigRef != "": targetSig, err = blob.LoadFileOrURL(sigRef) if err != nil { if !errors.Is(err, fs.ErrNotExist) { // ignore if file does not exist, it can be a base64 encoded string as well return "", err } targetSig = []byte(sigRef) } case bundlePath != "": b, err := cosign.FetchLocalSignedPayloadFromPath(bundlePath) if err != nil { return "", err } targetSig = []byte(b.Base64Signature) default: return "", fmt.Errorf("missing flag '--signature'") } if isb64(targetSig) { return string(targetSig), nil } return base64.StdEncoding.EncodeToString(targetSig), nil } func payloadBytes(blobRef string) ([]byte, error) { var blobBytes []byte var err error if blobRef == "-" { blobBytes, err = io.ReadAll(os.Stdin) } else { blobBytes, err = blob.LoadFileOrURL(blobRef) } if err != nil { return nil, err } return blobBytes, nil } func payloadDigest(blobRef string) (string, []byte, error) { hexAlg, hexDigest, ok := strings.Cut(blobRef, ":") if !ok { return "", nil, fmt.Errorf("invalid digest format") } digestBytes, err := hex.DecodeString(hexDigest) if err != nil { return "", nil, err } return hexAlg, digestBytes, nil } func loadTrustedRoot(_ context.Context, trustedRootPath string) (*root.TrustedRoot, error) { if trustedRootPath != "" { return root.NewTrustedRootFromPath(trustedRootPath) } // Assume we're using public good instance; fetch via TUF // TODO: allow custom TUF settings return root.FetchTrustedRoot() } cosign-2.5.0/cmd/cosign/cli/verify/verify_blob_attestation.go000066400000000000000000000273631477503325500243760ustar00rootroot00000000000000// // Copyright 2022 the Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "context" "crypto" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/json" "errors" "fmt" "io" "os" "path/filepath" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" internal "github.com/sigstore/cosign/v2/internal/pkg/cosign" payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/policy" sigs "github.com/sigstore/cosign/v2/pkg/signature" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/cryptoutils" ) // VerifyBlobAttestationCommand verifies an attestation on a supplied blob // nolint type VerifyBlobAttestationCommand struct { options.KeyOpts options.CertVerifyOptions CertRef string CertChain string CAIntermediates string CARoots string TrustedRootPath string CertGithubWorkflowTrigger string CertGithubWorkflowSHA string CertGithubWorkflowName string CertGithubWorkflowRepository string CertGithubWorkflowRef string IgnoreSCT bool SCTRef string Offline bool IgnoreTlog bool CheckClaims bool PredicateType string // TODO: Add policies SignaturePath string // Path to the signature UseSignedTimestamps bool } // Exec runs the verification command func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath string) (err error) { if options.NOf(c.SignaturePath, c.BundlePath) == 0 { return fmt.Errorf("please specify path to the DSSE envelope signature via --signature or --bundle") } // Require a certificate/key OR a local bundle file that has the cert. if options.NOf(c.KeyRef, c.CertRef, c.Sk, c.BundlePath) == 0 { return fmt.Errorf("provide a key with --key or --sk, a certificate to verify against with --certificate, or a bundle with --bundle") } // We can't have both a key and a security key if options.NOf(c.KeyRef, c.Sk) > 1 { return &options.KeyParseError{} } var identities []cosign.Identity if c.KeyRef == "" { identities, err = c.Identities() if err != nil { return err } } co := &cosign.CheckOpts{ Identities: identities, CertGithubWorkflowTrigger: c.CertGithubWorkflowTrigger, CertGithubWorkflowSha: c.CertGithubWorkflowSHA, CertGithubWorkflowName: c.CertGithubWorkflowName, CertGithubWorkflowRepository: c.CertGithubWorkflowRepository, CertGithubWorkflowRef: c.CertGithubWorkflowRef, IgnoreSCT: c.IgnoreSCT, Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps, NewBundleFormat: c.NewBundleFormat || checkNewBundle(c.BundlePath), } // Keys are optional! var cert *x509.Certificate opts := make([]static.Option, 0) switch { case c.KeyRef != "": co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef) if err != nil { return fmt.Errorf("loading public key: %w", err) } pkcs11Key, ok := co.SigVerifier.(*pkcs11key.Key) if ok { defer pkcs11Key.Close() } case c.Sk: sk, err := pivkey.GetKeyWithSlot(c.Slot) if err != nil { return fmt.Errorf("opening piv token: %w", err) } defer sk.Close() co.SigVerifier, err = sk.Verifier() if err != nil { return fmt.Errorf("loading public key from token: %w", err) } case c.CertRef != "": cert, err = loadCertFromFileOrURL(c.CertRef) if err != nil { return err } case c.CARoots != "": // CA roots + possible intermediates are already loaded into co.RootCerts with the call to // loadCertsKeylessVerification above. } var h v1.Hash var digest []byte if c.CheckClaims { // Get the actual digest of the blob var payload internal.HashReader f, err := os.Open(filepath.Clean(artifactPath)) if err != nil { return err } defer f.Close() fileInfo, err := f.Stat() if err != nil { return err } err = payloadsize.CheckSize(uint64(fileInfo.Size())) if err != nil { return err } payload = internal.NewHashReader(f, sha256.New()) if _, err := io.ReadAll(&payload); err != nil { return err } digest = payload.Sum(nil) h = v1.Hash{ Hex: hex.EncodeToString(digest), Algorithm: "sha256", } co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier } if co.NewBundleFormat { if options.NOf(c.RFC3161TimestampPath, c.TSACertChainPath, c.CertChain, c.CARoots, c.CAIntermediates, c.CertRef, c.SCTRef) > 0 { return fmt.Errorf("when using --new-bundle-format, please supply signed content with --bundle and verification content with --trusted-root") } if co.TrustedMaterial == nil { co.TrustedMaterial, err = loadTrustedRoot(ctx, c.TrustedRootPath) if err != nil { return err } } bundle, err := sgbundle.LoadJSONFromPath(c.BundlePath) if err != nil { return err } _, err = cosign.VerifyNewBundle(ctx, co, sgverify.WithArtifactDigest(h.Algorithm, digest), bundle) if err != nil { return err } ui.Infof(ctx, "Verified OK") return nil } if c.TrustedRootPath != "" { return fmt.Errorf("--trusted-root only supported with --new-bundle-format") } if c.RFC3161TimestampPath != "" && !co.UseSignedTimestamps { return fmt.Errorf("when specifying --rfc3161-timestamp-path, you must also specify --use-signed-timestamps or --timestamp-certificate-chain") } else if c.RFC3161TimestampPath == "" && co.UseSignedTimestamps { return fmt.Errorf("when specifying --use-signed-timestamps or --timestamp-certificate-chain, you must also specify --rfc3161-timestamp-path") } if co.UseSignedTimestamps { tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets) if err != nil { return fmt.Errorf("unable to load TSA certificates: %w", err) } co.TSACertificate = tsaCertificates.LeafCert co.TSARootCertificates = tsaCertificates.RootCert co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts } if !c.IgnoreTlog { if c.RekorURL != "" { rekorClient, err := rekor.NewClient(c.RekorURL) if err != nil { return fmt.Errorf("creating Rekor client: %w", err) } co.RekorClient = rekorClient } // This performs an online fetch of the Rekor public keys, but this is needed // for verifying tlog entries (both online and offline). co.RekorPubKeys, err = cosign.GetRekorPubs(ctx) if err != nil { return fmt.Errorf("getting Rekor public keys: %w", err) } } if keylessVerification(c.KeyRef, c.Sk) { if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { return err } } // Ignore Signed Certificate Timestamp if the flag is set or a key is provided if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) { co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) if err != nil { return fmt.Errorf("getting ctlog public keys: %w", err) } } var encodedSig []byte if c.SignaturePath != "" { encodedSig, err = os.ReadFile(filepath.Clean(c.SignaturePath)) if err != nil { return fmt.Errorf("reading %s: %w", c.SignaturePath, err) } } if c.BundlePath != "" { b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath) if err != nil { return err } // A certificate is required in the bundle unless we specified with // --key, --sk, or --certificate. if b.Cert == "" && co.SigVerifier == nil && cert == nil { return fmt.Errorf("bundle does not contain cert for verification, please provide public key") } // We have to condition on this because sign-blob may not output the signing // key to the bundle when there is no tlog upload. if b.Cert != "" { // b.Cert can either be a certificate or public key certBytes := []byte(b.Cert) if isb64(certBytes) { certBytes, _ = base64.StdEncoding.DecodeString(b.Cert) } bundleCert, err := loadCertFromPEM(certBytes) if err != nil { // check if cert is actually a public key co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256) if err != nil { return fmt.Errorf("loading verifier from bundle: %w", err) } } // if a cert was passed in, make sure it matches the cert in the bundle if cert != nil && !cert.Equal(bundleCert) { return fmt.Errorf("the cert passed in does not match the cert in the provided bundle") } cert = bundleCert } encodedSig, err = base64.StdEncoding.DecodeString(b.Base64Signature) if err != nil { return fmt.Errorf("decoding signature: %w", err) } opts = append(opts, static.WithBundle(b.Bundle)) } if c.RFC3161TimestampPath != "" { var rfc3161Timestamp bundle.RFC3161Timestamp ts, err := blob.LoadFileOrURL(c.RFC3161TimestampPath) if err != nil { return err } if err := json.Unmarshal(ts, &rfc3161Timestamp); err != nil { return err } opts = append(opts, static.WithRFC3161Timestamp(&rfc3161Timestamp)) } // Set an SCT if provided via the CLI. if c.SCTRef != "" { sct, err := os.ReadFile(filepath.Clean(c.SCTRef)) if err != nil { return fmt.Errorf("reading sct from file: %w", err) } co.SCT = sct } // Set a cert chain if provided. var chainPEM []byte if c.CertChain != "" { chain, err := loadCertChainFromFileOrURL(c.CertChain) if err != nil { return err } if chain == nil { return errors.New("expected certificate chain in --certificate-chain") } // Set the last one in the co.RootCerts. This is trusted, as its passed in // via the CLI. if co.RootCerts == nil { co.RootCerts = x509.NewCertPool() } co.RootCerts.AddCert(chain[len(chain)-1]) // Use the whole as the cert chain in the signature object. // The last one is omitted because it is considered the "root". chainPEM, err = cryptoutils.MarshalCertificatesToPEM(chain) if err != nil { return err } } // Gather the cert for the signature and add the cert along with the // cert chain into the signature object. var certPEM []byte if cert != nil { certPEM, err = cryptoutils.MarshalCertificateToPEM(cert) if err != nil { return err } opts = append(opts, static.WithCertChain(certPEM, chainPEM)) } signature, err := static.NewAttestation(encodedSig, opts...) if err != nil { return err } // TODO: This verifier only supports verification of a single signer/signature on // the envelope. Either have the verifier validate that only one signature exists, // or use a multi-signature verifier. if _, err = cosign.VerifyBlobAttestation(ctx, signature, h, co); err != nil { return err } // This checks the predicate type -- if no error is returned and no payload is, then // the attestation is not of the given predicate type. if b, gotPredicateType, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, signature); b == nil && err == nil { return fmt.Errorf("invalid predicate type, expected %s got %s", c.PredicateType, gotPredicateType) } fmt.Fprintln(os.Stderr, "Verified OK") return nil } cosign-2.5.0/cmd/cosign/cli/verify/verify_blob_attestation_test.go000066400000000000000000000310711477503325500254240ustar00rootroot00000000000000// Copyright 2022 the Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "context" "encoding/base64" "os" "path/filepath" "testing" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protodsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse" "google.golang.org/protobuf/encoding/protojson" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" ) const pubkey = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESF79b1ToAtoakhBOHEU5UjnEiihV gZPFIp557+TOoDxf14FODWc+sIPETk0OgCplAk60doVXbCv33IU4rXZHrg== -----END PUBLIC KEY----- ` const ( blobContents = "some-payload" anotherBlobContents = "another-blob" hugeBlobContents = "hugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayloadhugepayload" blobSLSAProvenanceSignature = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdJbk5vWVRJMU5pSTZJalkxT0RjNE1XTmtOR1ZrT1dKallUWXdaR0ZqWkRBNVpqZGlZamt4TkdKaU5URTFNREpsT0dJMVpEWXhPV1kxTjJZek9XRXhaRFkxTWpVNU5tTmpNalFpZlgxZExDSndjbVZrYVdOaGRHVWlPbnNpWW5WcGJHUmxjaUk2ZXlKcFpDSTZJaklpZlN3aVluVnBiR1JVZVhCbElqb2llQ0lzSW1sdWRtOWpZWFJwYjI0aU9uc2lZMjl1Wm1sblUyOTFjbU5sSWpwN2ZYMTlmUT09Iiwic2lnbmF0dXJlcyI6W3sia2V5aWQiOiIiLCJzaWciOiJNRVVDSUE4S2pacWtydDkwZnpCb2pTd3d0ajNCcWI0MUU2cnV4UWs5N1RMbnB6ZFlBaUVBek9Bak9Uenl2VEhxYnBGREFuNnpocmc2RVp2N2t4SzVmYVJvVkdZTWgyYz0ifV19" dssePredicateEmptySubject = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHRkTENKd2NtVmthV05oZEdVaU9uc2lZblZwYkdSbGNpSTZleUpwWkNJNklqSWlmU3dpWW5WcGJHUlVlWEJsSWpvaWVDSXNJbWx1ZG05allYUnBiMjRpT25zaVkyOXVabWxuVTI5MWNtTmxJanA3ZlgxOWZRPT0iLCJzaWduYXR1cmVzIjpbeyJrZXlpZCI6IiIsInNpZyI6Ik1FWUNJUUNrTEV2NkhZZ0svZDdUK0N3NTdXbkZGaHFUTC9WalAyVDA5Q2t1dk1nbDRnSWhBT1hBM0lhWWg1M1FscVk1eVU4cWZxRXJma2tGajlEakZnaWovUTQ2NnJSViJ9XX0=" dssePredicateMissingSha256 = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdmWDFkTENKd2NtVmthV05oZEdVaU9uc2lZblZwYkdSbGNpSTZleUpwWkNJNklqSWlmU3dpWW5WcGJHUlVlWEJsSWpvaWVDSXNJbWx1ZG05allYUnBiMjRpT25zaVkyOXVabWxuVTI5MWNtTmxJanA3ZlgxOWZRPT0iLCJzaWduYXR1cmVzIjpbeyJrZXlpZCI6IiIsInNpZyI6Ik1FVUNJQysvM2M4RFo1TGFZTEx6SFZGejE3ZmxHUENlZXVNZ2tIKy8wa2s1cFFLUEFpRUFqTStyYnBBRlJybDdpV0I2Vm9BYVZPZ3U3NjRRM0JKdHI1bHk4VEFHczNrPSJ9XX0=" dssePredicateMultipleSubjects = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdJbk5vWVRJMU5pSTZJalkxT0RjNE1XTmtOR1ZrT1dKallUWXdaR0ZqWkRBNVpqZGlZamt4TkdKaU5URTFNREpsT0dJMVpEWXhPV1kxTjJZek9XRXhaRFkxTWpVNU5tTmpNalFpZlgwc2V5SnVZVzFsSWpvaWIzUm9aWElpTENKa2FXZGxjM1FpT25zaWMyaGhNalUySWpvaU1HUmhOVFU1WXpKbU1USTNNak13WVRGbVlXSmpabUppTWpCa05XUmlPR1JpWVRjMk5Ua3lNMk0yWldaak5tWTBPRE14TmpVeE1UbGpOR015WXpWa05DSjlmVjBzSW5CeVpXUnBZMkYwWlNJNmV5SmlkV2xzWkdWeUlqcDdJbWxrSWpvaU1pSjlMQ0ppZFdsc1pGUjVjR1VpT2lKNElpd2lhVzUyYjJOaGRHbHZiaUk2ZXlKamIyNW1hV2RUYjNWeVkyVWlPbnQ5ZlgxOSIsInNpZ25hdHVyZXMiOlt7ImtleWlkIjoiIiwic2lnIjoiTUVZQ0lRQ20yR2FwNzRzbDkyRC80V2FoWHZiVHFrNFVCaHZsb3oreDZSZm1NQXUyaWdJaEFNcXRFV29DalpGdkpmZWJxRDJFank3aTlHaGc0a0V0WE51bVdLbVBtdEphIn1dfQ==" dssePredicateMultipleSubjectsInvalid = "eyJwYXlsb2FkVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5pbi10b3RvK2pzb24iLCJwYXlsb2FkIjoiZXlKZmRIbHdaU0k2SW1oMGRIQnpPaTh2YVc0dGRHOTBieTVwYnk5VGRHRjBaVzFsYm5RdmRqQXVNU0lzSW5CeVpXUnBZMkYwWlZSNWNHVWlPaUpvZEhSd2N6b3ZMM05zYzJFdVpHVjJMM0J5YjNabGJtRnVZMlV2ZGpBdU1pSXNJbk4xWW1wbFkzUWlPbHQ3SW01aGJXVWlPaUppYkc5aUlpd2laR2xuWlhOMElqcDdJbk5vWVRJMU5pSTZJbUUyT0RJelpqbGpOekEyTWpCalltWmpOVGt4T0dJMVpUWmtOR0ZoTVRjMFlUaGhNakJrTlRaa1lUVm1NVEEyWWpZMU5qSTNOR013TldRMlptVXhZVGNpZlgwc2V5SnVZVzFsSWpvaWIzUm9aWElpTENKa2FXZGxjM1FpT25zaWMyaGhNalUySWpvaU1HUmhOVFU1WXpKbU1USTNNak13WVRGbVlXSmpabUppTWpCa05XUmlPR1JpWVRjMk5Ua3lNMk0yWldaak5tWTBPRE14TmpVeE1UbGpOR015WXpWa05DSjlmVjBzSW5CeVpXUnBZMkYwWlNJNmV5SmlkV2xzWkdWeUlqcDdJbWxrSWpvaU1pSjlMQ0ppZFdsc1pGUjVjR1VpT2lKNElpd2lhVzUyYjJOaGRHbHZiaUk2ZXlKamIyNW1hV2RUYjNWeVkyVWlPbnQ5ZlgxOSIsInNpZ25hdHVyZXMiOlt7ImtleWlkIjoiIiwic2lnIjoiTUVVQ0lRRGhZbCtWUlBtcWFJc2xxdS9yWGRVbnc2VmpQcXR4RG84bHdqc3p1cWl6MmdJZ0NNRVVlcUZ5RkFZejcyM2IvSTI2L0p3K0U3YkFLMExqeElsUExvTGxPczQ9In1dfQ==" ) func TestVerifyBlobAttestation(t *testing.T) { ctx := context.Background() td := t.TempDir() defer os.RemoveAll(td) blobPath := writeBlobFile(t, td, blobContents, "blob") anotherBlobPath := writeBlobFile(t, td, anotherBlobContents, "other-blob") hugeBlobPath := writeBlobFile(t, td, hugeBlobContents, "huge-blob") keyRef := writeBlobFile(t, td, pubkey, "cosign.pub") tests := []struct { description string blobPath string bundlePath string signature string predicateType string env map[string]string shouldErr bool }{ { description: "verify a slsaprovenance predicate", predicateType: "slsaprovenance", blobPath: blobPath, signature: blobSLSAProvenanceSignature, }, { description: "fail with incorrect predicate", signature: blobSLSAProvenanceSignature, blobPath: blobPath, predicateType: "custom", shouldErr: true, }, { description: "fail with incorrect blob", signature: blobSLSAProvenanceSignature, blobPath: anotherBlobPath, shouldErr: true, }, { description: "dsse envelope predicate has no subject", signature: dssePredicateEmptySubject, blobPath: blobPath, shouldErr: true, }, { description: "dsse envelope predicate missing sha256 digest", signature: dssePredicateMissingSha256, blobPath: blobPath, shouldErr: true, }, { description: "dsse envelope has multiple subjects, one is valid", predicateType: "slsaprovenance", signature: dssePredicateMultipleSubjects, blobPath: blobPath, }, { description: "dsse envelope has multiple subjects, one is valid, but we are looking for different predicatetype", predicateType: "notreallyslsaprovenance", signature: dssePredicateMultipleSubjects, blobPath: blobPath, shouldErr: true, }, { description: "dsse envelope has multiple subjects, none has correct sha256 digest", predicateType: "slsaprovenance", signature: dssePredicateMultipleSubjectsInvalid, blobPath: blobPath, shouldErr: true, }, { description: "override file size limit", signature: blobSLSAProvenanceSignature, blobPath: hugeBlobPath, env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "128"}, shouldErr: true, }, { description: "verify new bundle with public key", // From blobSLSAProvenanceSignature bundlePath: makeLocalAttestNewBundle(t, "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJibG9iIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjY1ODc4MWNkNGVkOWJjYTYwZGFjZDA5ZjdiYjkxNGJiNTE1MDJlOGI1ZDYxOWY1N2YzOWExZDY1MjU5NmNjMjQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6IjIifSwiYnVpbGRUeXBlIjoieCIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", "application/vnd.in-toto+json", "MEUCIA8KjZqkrt90fzBojSwwtj3Bqb41E6ruxQk97TLnpzdYAiEAzOAjOTzyvTHqbpFDAn6zhrg6EZv7kxK5faRoVGYMh2c="), blobPath: blobPath, }, { description: "verify new bundle with public key - bad sig", // From blobSLSAProvenanceSignature bundlePath: makeLocalAttestNewBundle(t, "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJibG9iIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjY1ODc4MWNkNGVkOWJjYTYwZGFjZDA5ZjdiYjkxNGJiNTE1MDJlOGI1ZDYxOWY1N2YzOWExZDY1MjU5NmNjMjQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6IjIifSwiYnVpbGRUeXBlIjoieCIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", "application/vnd.in-toto+json", "c29tZXRoaW5nCg=="), blobPath: blobPath, shouldErr: true, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { for k, v := range test.env { t.Setenv(k, v) } decodedSig, err := base64.StdEncoding.DecodeString(test.signature) if err != nil { t.Fatal(err) } sigRef := writeBlobFile(t, td, string(decodedSig), "signature") cmd := VerifyBlobAttestationCommand{ KeyOpts: options.KeyOpts{KeyRef: keyRef}, SignaturePath: sigRef, IgnoreTlog: true, CheckClaims: true, PredicateType: test.predicateType, } if test.bundlePath != "" { cmd.BundlePath = test.bundlePath cmd.NewBundleFormat = true cmd.TrustedRootPath = writeTrustedRootFile(t, td, "{\"mediaType\":\"application/vnd.dev.sigstore.trustedroot+json;version=0.1\"}") } err = cmd.Exec(ctx, test.blobPath) if (err != nil) != test.shouldErr { t.Fatalf("verifyBlobAttestation()= %s, expected shouldErr=%t ", err, test.shouldErr) } }) } } func TestVerifyBlobAttestationNoCheckClaims(t *testing.T) { ctx := context.Background() td := t.TempDir() defer os.RemoveAll(td) blobPath := writeBlobFile(t, td, blobContents, "blob") anotherBlobPath := writeBlobFile(t, td, anotherBlobContents, "other-blob") keyRef := writeBlobFile(t, td, pubkey, "cosign.pub") tests := []struct { description string blobPath string signature string }{ { description: "verify a predicate", blobPath: blobPath, signature: blobSLSAProvenanceSignature, }, { description: "verify a predicate no path", signature: blobSLSAProvenanceSignature, }, { description: "verify a predicate with another blob path", signature: blobSLSAProvenanceSignature, // This works because we're not checking the claims. It doesn't matter what we put in here - it should pass so long as the DSSE signagure can be verified. blobPath: anotherBlobPath, }, { description: "verify a predicate with /dev/null", signature: blobSLSAProvenanceSignature, blobPath: "/dev/null", }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { decodedSig, err := base64.StdEncoding.DecodeString(test.signature) if err != nil { t.Fatal(err) } sigRef := writeBlobFile(t, td, string(decodedSig), "signature") cmd := VerifyBlobAttestationCommand{ KeyOpts: options.KeyOpts{KeyRef: keyRef}, SignaturePath: sigRef, IgnoreTlog: true, CheckClaims: false, PredicateType: "slsaprovenance", } if err := cmd.Exec(ctx, test.blobPath); err != nil { t.Fatalf("verifyBlobAttestation()= %v", err) } }) } } func makeLocalAttestNewBundle(t *testing.T, payload, payloadType, sig string) string { b, err := bundle.MakeProtobufBundle("hint", []byte{}, nil, []byte{}) if err != nil { t.Fatal(err) } decodedPayload, err := base64.StdEncoding.DecodeString(payload) if err != nil { t.Fatal(err) } decodedSig, err := base64.StdEncoding.DecodeString(sig) if err != nil { t.Fatal(err) } b.Content = &protobundle.Bundle_DsseEnvelope{ DsseEnvelope: &protodsse.Envelope{ Payload: decodedPayload, PayloadType: payloadType, Signatures: []*protodsse.Signature{ { Sig: decodedSig, }, }, }, } contents, err := protojson.Marshal(b) if err != nil { t.Fatal(err) } // write bundle to disk td := t.TempDir() bundlePath := filepath.Join(td, "bundle.sigstore.json") if err := os.WriteFile(bundlePath, contents, 0644); err != nil { t.Fatal(err) } return bundlePath } cosign-2.5.0/cmd/cosign/cli/verify/verify_blob_test.go000066400000000000000000001531701477503325500230120ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/go-openapi/runtime" "github.com/go-openapi/swag" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/mock" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" sigs "github.com/sigstore/cosign/v2/pkg/signature" ctypes "github.com/sigstore/cosign/v2/pkg/types" "github.com/sigstore/cosign/v2/test" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/pki" "github.com/sigstore/rekor/pkg/types" rekor_dsse "github.com/sigstore/rekor/pkg/types/dsse" "github.com/sigstore/rekor/pkg/types/hashedrekord" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" "github.com/sigstore/rekor/pkg/types/rekord" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" "google.golang.org/protobuf/encoding/protojson" ) func TestSignaturesRef(t *testing.T) { sig := "a==" b64sig := "YT09" tests := []struct { description string sigRef string shouldErr bool }{ { description: "raw sig", sigRef: sig, }, { description: "encoded sig", sigRef: b64sig, }, { description: "empty ref", shouldErr: true, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { gotSig, err := base64signature(test.sigRef, "") if test.shouldErr && err != nil { return } if test.shouldErr { t.Fatal("should have received an error") } if gotSig != b64sig { t.Fatalf("unexpected signature, expected: %s got: %s", sig, gotSig) } }) } } func TestSignaturesBundle(t *testing.T) { td := t.TempDir() fp := filepath.Join(td, "file") b64sig := "YT09" // save as a LocalSignedPayload to the file lsp := cosign.LocalSignedPayload{ Base64Signature: b64sig, } contents, err := json.Marshal(lsp) if err != nil { t.Fatal(err) } if err := os.WriteFile(fp, contents, 0644); err != nil { t.Fatal(err) } gotSig, err := base64signature("", fp) if err != nil { t.Fatal(err) } if gotSig != b64sig { t.Fatalf("unexpected signature, expected: %s got: %s", b64sig, gotSig) } } // Does not test identity options, only blob verification with different // options. func TestVerifyBlob(t *testing.T) { ctx := context.Background() td := t.TempDir() leafPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } signer, err := signature.LoadECDSASignerVerifier(leafPriv, crypto.SHA256) if err != nil { t.Fatal(err) } pubKeyBytes, err := sigs.PublicKeyPem(signer, signatureoptions.WithContext(ctx)) if err != nil { t.Fatal(err) } // Generate expired and unexpired certificates identity := "hello@foo.com" issuer := "issuer" rootCert, rootPriv, _ := test.GenerateRootCa() rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) chain, _ := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{rootCert}) chainPath := writeBlobFile(t, td, string(chain), "chain.pem") unexpiredLeafCert, _ := test.GenerateLeafCertWithExpiration(identity, issuer, time.Now(), leafPriv, rootCert, rootPriv) unexpiredCertPem, _ := cryptoutils.MarshalCertificateToPEM(unexpiredLeafCert) expiredLeafCert, _ := test.GenerateLeafCertWithExpiration(identity, issuer, time.Now().Add(-time.Hour), leafPriv, rootCert, rootPriv) expiredLeafPem, _ := cryptoutils.MarshalCertificateToPEM(expiredLeafCert) // Make rekor signer rekorPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } rekorSigner, err := signature.LoadECDSASignerVerifier(rekorPriv, crypto.SHA256) if err != nil { t.Fatal(err) } pemRekor, err := cryptoutils.MarshalPublicKeyToPEM(rekorSigner.Public()) if err != nil { t.Fatal(err) } tmpRekorPubFile := writeBlobFile(t, td, string(pemRekor), "rekor_pub.key") t.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", tmpRekorPubFile) var makeSignature = func(blob []byte) string { sig, err := signer.SignMessage(bytes.NewReader(blob)) if err != nil { t.Fatal(err) } return string(sig) } blobBytes := []byte("foo") blobSignature := makeSignature(blobBytes) otherBytes := []byte("bar") otherSignature := makeSignature(otherBytes) // initialize timestamp for expired and unexpired certificates expiredTSAOpts := mock.TSAClientOptions{Time: time.Now().Add(-time.Hour), Message: []byte(blobSignature)} unexpiredTSAOpts := mock.TSAClientOptions{Time: time.Now(), Message: []byte(blobSignature)} tsaClient, err := mock.NewTSAClient(expiredTSAOpts) if err != nil { t.Fatal(err) } certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(tsaClient.CertChain) if err != nil { t.Fatalf("unexpected error marshalling cert chain: %v", err) } expiredTSACertChainPath := filepath.Join(td, "exptsacertchain.pem") if err := os.WriteFile(expiredTSACertChainPath, certChainPEM, 0644); err != nil { t.Fatal(err) } tsr, err := tsaClient.GetTimestampResponse(nil) if err != nil { t.Fatalf("unable to generate a timestamp response: %v", err) } rfc3161Timestamp := &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsr} expiredTSPath := writeTimestampFile(t, td, rfc3161Timestamp, "expiredrfc3161TS.json") tsaClient, err = mock.NewTSAClient(unexpiredTSAOpts) if err != nil { t.Fatal(err) } tsr, err = tsaClient.GetTimestampResponse(nil) if err != nil { t.Fatalf("unable to generate a timestamp response: %v", err) } rfc3161Timestamp = &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsr} unexpiredTSPath := writeTimestampFile(t, td, rfc3161Timestamp, "unexpiredrfc3161TS.json") certChainPEM, err = cryptoutils.MarshalCertificatesToPEM(tsaClient.CertChain) if err != nil { t.Fatalf("unexpected error marshalling cert chain: %v", err) } unexpiredTSACertChainPath := filepath.Join(td, "unexptsacertchain.pem") if err := os.WriteFile(unexpiredTSACertChainPath, certChainPEM, 0644); err != nil { t.Fatal(err) } tts := []struct { name string blob []byte signature string key []byte cert *x509.Certificate bundlePath string newBundle bool // The rekor entry response when Rekor is enabled rekorEntry []*models.LogEntry skipTlogVerify bool shouldErr bool tsPath string tsChainPath string }{ { name: "valid signature with public key", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, shouldErr: false, skipTlogVerify: true, }, { name: "valid signature with public key - experimental no rekor fail", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, rekorEntry: nil, shouldErr: true, }, { name: "valid signature with public key - experimental rekor entry success", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), pubKeyBytes, true)}, shouldErr: false, }, { name: "valid signature with public key - good bundle provided", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), pubKeyBytes, true), shouldErr: false, }, { name: "valid signature with public key - bundle without rekor bundle fails", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundleWithoutRekorBundle(t, []byte(blobSignature), pubKeyBytes), shouldErr: true, }, { name: "valid signature with public key - bad bundle SET", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *signer, blobBytes, []byte(blobSignature), unexpiredCertPem, true), shouldErr: true, }, { name: "valid signature with public key - bad bundle cert mismatch", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), unexpiredCertPem, true), shouldErr: true, }, { name: "valid signature with public key - bad bundle signature mismatch", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)), pubKeyBytes, true), shouldErr: true, }, { name: "valid signature with public key - bad bundle msg & signature mismatch", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, otherBytes, []byte(otherSignature), pubKeyBytes, true), shouldErr: true, }, { name: "valid signature with public key - new bundle", blob: blobBytes, signature: "", key: pubKeyBytes, bundlePath: makeLocalNewBundle(t, []byte(blobSignature), sha256.Sum256(blobBytes)), newBundle: true, skipTlogVerify: true, shouldErr: false, }, { name: "invalid signature with public key - new bundle", blob: blobBytes, signature: "", key: pubKeyBytes, bundlePath: makeLocalNewBundle(t, []byte(otherSignature), sha256.Sum256(blobBytes)), newBundle: true, skipTlogVerify: true, shouldErr: true, }, { name: "invalid signature with public key", blob: blobBytes, signature: otherSignature, key: pubKeyBytes, shouldErr: true, }, { name: "invalid signature with public key - experimental", blob: blobBytes, signature: otherSignature, key: pubKeyBytes, shouldErr: true, }, { name: "valid signature with unexpired certificate - no rekor entry", blob: blobBytes, signature: blobSignature, cert: unexpiredLeafCert, shouldErr: true, }, { name: "valid signature with unexpired certificate - bad bundle cert mismatch", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), unexpiredCertPem, true), shouldErr: true, }, { name: "valid signature with unexpired certificate - bad bundle signature mismatch", blob: blobBytes, signature: blobSignature, cert: unexpiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)), unexpiredCertPem, true), shouldErr: true, }, { name: "valid signature with unexpired certificate - bad bundle msg & signature mismatch", blob: blobBytes, signature: blobSignature, cert: unexpiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, otherBytes, []byte(otherSignature), unexpiredCertPem, true), shouldErr: true, }, { name: "invalid signature with unexpired certificate", blob: blobBytes, signature: otherSignature, cert: unexpiredLeafCert, shouldErr: true, }, { name: "valid signature with unexpired certificate - experimental", blob: blobBytes, signature: blobSignature, cert: unexpiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), unexpiredCertPem, true)}, shouldErr: false, }, { name: "valid signature with unexpired certificate - experimental & rekor entry found", blob: blobBytes, signature: blobSignature, cert: unexpiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), unexpiredCertPem, true)}, shouldErr: false, }, { name: "valid signature with expired certificate + Rekor", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, shouldErr: true, }, { name: "valid signature with expired certificate, no Rekor", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, skipTlogVerify: true, shouldErr: true, }, { name: "valid signature with expired certificate - experimental good rekor lookup", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), expiredLeafPem, true)}, shouldErr: false, }, { name: "valid signature with expired certificate - experimental multiple rekor entries", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), expiredLeafPem, true), makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), expiredLeafPem, false)}, shouldErr: false, }, { name: "valid signature with expired certificate - experimental bad rekor integrated time", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), expiredLeafPem, false)}, shouldErr: true, }, { name: "valid signature with unexpired certificate - good bundle, nonexperimental", blob: blobBytes, signature: blobSignature, cert: unexpiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), unexpiredCertPem, true), shouldErr: false, }, { name: "valid signature with expired certificate - good bundle, nonexperimental", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), expiredLeafPem, true), shouldErr: false, }, { name: "valid signature with expired certificate - bundle with bad expiration", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), expiredLeafPem, false), shouldErr: true, }, { name: "valid signature with expired certificate - bundle with bad SET", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *signer, blobBytes, []byte(blobSignature), expiredLeafPem, true), shouldErr: true, }, { name: "valid signature with expired certificate - experimental good bundle", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), expiredLeafPem, true), shouldErr: false, }, { name: "valid signature with expired certificate - experimental bad rekor entry", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, // This is the wrong signer for the SET! rekorEntry: []*models.LogEntry{makeRekorEntry(t, *signer, blobBytes, []byte(blobSignature), expiredLeafPem, true)}, shouldErr: true, }, { name: "valid signature with expired certificate - good bundle, good timestamp", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), expiredLeafPem, true), tsPath: expiredTSPath, tsChainPath: expiredTSACertChainPath, shouldErr: false, }, { name: "valid signature with expired certificate - no bundle, good timestamp", blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, tsPath: expiredTSPath, tsChainPath: expiredTSACertChainPath, skipTlogVerify: true, shouldErr: false, }, { name: "mismatched signature with expired certificate", blob: otherBytes, signature: otherSignature, cert: expiredLeafCert, tsPath: expiredTSPath, tsChainPath: expiredTSACertChainPath, skipTlogVerify: true, shouldErr: true, }, { name: "valid signature with unexpired certificate - good bundle, good timestamp", blob: blobBytes, signature: blobSignature, cert: unexpiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), unexpiredCertPem, true), tsPath: unexpiredTSPath, tsChainPath: unexpiredTSACertChainPath, shouldErr: false, }, { name: "valid signature with unexpired certificate - no bundle, good timestamp", blob: blobBytes, signature: blobSignature, cert: unexpiredLeafCert, tsPath: unexpiredTSPath, tsChainPath: unexpiredTSACertChainPath, skipTlogVerify: true, shouldErr: false, }, } for _, tt := range tts { t.Run(tt.name, func(t *testing.T) { tt := tt entries := make([]models.LogEntry, 0) for _, entry := range tt.rekorEntry { entries = append(entries, *entry) } testServer := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(entries) })) defer testServer.Close() // Verify command cmd := VerifyBlobCmd{ KeyOpts: options.KeyOpts{ BundlePath: tt.bundlePath, NewBundleFormat: tt.newBundle, RekorURL: testServer.URL, RFC3161TimestampPath: tt.tsPath, TSACertChainPath: tt.tsChainPath, }, CertVerifyOptions: options.CertVerifyOptions{ CertIdentity: identity, CertOidcIssuer: issuer, }, IgnoreSCT: true, CertChain: chainPath, IgnoreTlog: tt.skipTlogVerify, } blobPath := writeBlobFile(t, td, string(blobBytes), "blob.txt") if tt.signature != "" { sigPath := writeBlobFile(t, td, tt.signature, "signature.txt") cmd.SigRef = sigPath } if tt.cert != nil { certPEM, err := cryptoutils.MarshalCertificateToPEM(tt.cert) if err != nil { t.Fatal("MarshalCertificateToPEM: %w", err) } certPath := writeBlobFile(t, td, string(certPEM), "cert.pem") cmd.CertRef = certPath } if tt.key != nil { keyPath := writeBlobFile(t, td, string(tt.key), "key.pem") cmd.KeyRef = keyPath } if tt.newBundle { cmd.TrustedRootPath = writeTrustedRootFile(t, td, "{\"mediaType\":\"application/vnd.dev.sigstore.trustedroot+json;version=0.1\"}") cmd.RekorURL = "" cmd.RFC3161TimestampPath = "" cmd.TSACertChainPath = "" cmd.CertChain = "" } err := cmd.Exec(context.Background(), blobPath) if (err != nil) != tt.shouldErr { t.Fatalf("verifyBlob()= %s, expected shouldErr=%t ", err, tt.shouldErr) } }) } } func TestVerifyBlobCertMissingSubject(t *testing.T) { ctx := context.Background() verifyBlob := VerifyBlobCmd{ CertRef: "cert.pem", CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: "issuer", }, } err := verifyBlob.Exec(ctx, "blob") if err == nil { t.Fatalf("verifyBlob() expected '--certificate-identity required'") } } func TestVerifyBlobCertMissingIssuer(t *testing.T) { ctx := context.Background() verifyBlob := VerifyBlobCmd{ CertRef: "cert.pem", CertVerifyOptions: options.CertVerifyOptions{ CertIdentity: "subject", }, } err := verifyBlob.Exec(ctx, "blob") if err == nil { t.Fatalf("verifyBlob() expected '--certificate-oidc-issuer required'") } } func makeRekorEntry(t *testing.T, rekorSigner signature.ECDSASignerVerifier, pyld, sig, svBytes []byte, expiryValid bool) *models.LogEntry { ctx := context.Background() // Calculate log ID, the digest of the Rekor public key logID, err := getLogID(rekorSigner.Public()) if err != nil { t.Fatal(err) } hashedrekord := &hashedrekord_v001.V001Entry{} h := sha256.Sum256(pyld) pe, err := hashedrekord.CreateFromArtifactProperties(ctx, types.ArtifactProperties{ ArtifactHash: hex.EncodeToString(h[:]), SignatureBytes: sig, PublicKeyBytes: [][]byte{svBytes}, PKIFormat: "x509", }) if err != nil { t.Fatal(err) } entry, err := types.UnmarshalEntry(pe) if err != nil { t.Fatal(err) } leaf, err := entry.Canonicalize(ctx) if err != nil { t.Fatal(err) } integratedTime := time.Now() certs, _ := cryptoutils.UnmarshalCertificatesFromPEM(svBytes) if len(certs) > 0 { if expiryValid { integratedTime = certs[0].NotAfter.Add(-time.Second) } else { integratedTime = certs[0].NotAfter.Add(time.Second) } } e := models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString(leaf), IntegratedTime: swag.Int64(integratedTime.Unix()), LogIndex: swag.Int64(0), LogID: swag.String(logID), } // Marshal payload, sign, and set SET in Bundle jsonPayload, err := json.Marshal(e) if err != nil { t.Fatal(err) } canonicalized, err := jsoncanonicalizer.Transform(jsonPayload) if err != nil { t.Fatal(err) } bundleSig, err := rekorSigner.SignMessage(bytes.NewReader(canonicalized)) if err != nil { t.Fatal(err) } uuid, _ := cosign.ComputeLeafHash(&e) e.Verification = &models.LogEntryAnonVerification{ SignedEntryTimestamp: bundleSig, InclusionProof: &models.InclusionProof{ LogIndex: swag.Int64(0), TreeSize: swag.Int64(1), RootHash: swag.String(hex.EncodeToString(uuid)), Hashes: []string{}, }, } return &models.LogEntry{hex.EncodeToString(uuid): e} } func makeLocalBundle(t *testing.T, rekorSigner signature.ECDSASignerVerifier, pyld []byte, sig []byte, svBytes []byte, expiryValid bool) string { td := t.TempDir() // Create bundle. entry := makeRekorEntry(t, rekorSigner, pyld, sig, svBytes, expiryValid) var e models.LogEntryAnon for _, v := range *entry { e = v } b := cosign.LocalSignedPayload{ Base64Signature: base64.StdEncoding.EncodeToString(sig), Cert: string(svBytes), Bundle: &bundle.RekorBundle{ Payload: bundle.RekorPayload{ Body: e.Body, IntegratedTime: *e.IntegratedTime, LogIndex: *e.LogIndex, LogID: *e.LogID, }, SignedEntryTimestamp: e.Verification.SignedEntryTimestamp, }, } // Write bundle to disk jsonBundle, err := json.Marshal(b) if err != nil { t.Fatal(err) } bundlePath := filepath.Join(td, "bundle.sig") if err := os.WriteFile(bundlePath, jsonBundle, 0644); err != nil { t.Fatal(err) } return bundlePath } func makeLocalBundleWithoutRekorBundle(t *testing.T, sig []byte, svBytes []byte) string { td := t.TempDir() b := cosign.LocalSignedPayload{ Base64Signature: base64.StdEncoding.EncodeToString(sig), Cert: string(svBytes), } // Write bundle to disk jsonBundle, err := json.Marshal(b) if err != nil { t.Fatal(err) } bundlePath := filepath.Join(td, "bundle.sig") if err := os.WriteFile(bundlePath, jsonBundle, 0644); err != nil { t.Fatal(err) } return bundlePath } func makeLocalNewBundle(t *testing.T, sig []byte, digest [32]byte) string { b, err := bundle.MakeProtobufBundle("hint", []byte{}, nil, []byte{}) if err != nil { t.Fatal(err) } b.Content = &protobundle.Bundle_MessageSignature{ MessageSignature: &protocommon.MessageSignature{ MessageDigest: &protocommon.HashOutput{ Algorithm: protocommon.HashAlgorithm_SHA2_256, Digest: digest[:], }, Signature: sig, }, } contents, err := protojson.Marshal(b) if err != nil { t.Fatal(err) } // write bundle to disk td := t.TempDir() bundlePath := filepath.Join(td, "bundle.sigstore.json") if err := os.WriteFile(bundlePath, contents, 0644); err != nil { t.Fatal(err) } return bundlePath } func TestVerifyBlobCmdWithBundle(t *testing.T) { keyless := newKeylessStack(t) defer os.RemoveAll(keyless.td) t.Run("Normal verification", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ KeyOpts: options.KeyOpts{BundlePath: bundlePath}, CertVerifyOptions: options.CertVerifyOptions{ CertIdentity: identity, CertOidcIssuer: issuer, }, IgnoreSCT: true, } if err := cmd.Exec(context.Background(), blobPath); err != nil { t.Fatal(err) } }) t.Run("Mismatched cert/sig", func(t *testing.T) { // This test ensures that the signature and cert at the top level in the LocalSignedPayload must be identical to the ones in the RekorBundle. identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) _, _, leafPemCert2, signer2 := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } sig2, err := signer2.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert2, sig2) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } if err := cmd.Exec(context.Background(), blobPath); err == nil { t.Fatal("expecting err due to mismatched signatures, got nil") } }) t.Run("Expired cert", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()-1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } if err := cmd.Exec(context.Background(), blobPath); err == nil { t.Fatal("expected error due to expired cert, received nil") } }) t.Run("dsse Attestation", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) stmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}` wrapped := dsse.WrapSigner(signer, ctypes.IntotoPayloadType) signedPayload, err := wrapped.SignMessage(bytes.NewReader([]byte(stmt)), signatureoptions.WithContext(context.Background())) if err != nil { t.Fatal(err) } // intoto sig = json-serialized dsse envelope sig := signedPayload // Create bundle entry := genRekorEntry(t, rekor_dsse.KIND, "0.0.1", signedPayload, leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, string(signedPayload), "attestation.txt") // Verify command cmd := VerifyBlobAttestationCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertIdentity: identity, CertOidcIssuer: issuer, }, CertRef: "", // Cert is fetched from bundle CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SignaturePath: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } if err := cmd.Exec(context.Background(), blobPath); err != nil { t.Fatal(err) } }) t.Run("intoto Attestation", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) stmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}` wrapped := dsse.WrapSigner(signer, ctypes.IntotoPayloadType) signedPayload, err := wrapped.SignMessage(bytes.NewReader([]byte(stmt)), signatureoptions.WithContext(context.Background())) if err != nil { t.Fatal(err) } // intoto sig = json-serialized dsse envelope sig := signedPayload // Create bundle entry := genRekorEntry(t, intoto.KIND, "0.0.1", signedPayload, leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, string(signedPayload), "attestation.txt") // Verify command cmd := VerifyBlobAttestationCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertIdentity: identity, CertOidcIssuer: issuer, }, CertRef: "", // Cert is fetched from bundle CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SignaturePath: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } if err := cmd.Exec(context.Background(), blobPath); err != nil { t.Fatal(err) } }) t.Run("Invalid blob signature", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = []byte{'i', 'n', 'v', 'a', 'l', 'i', 'd'} bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ CertVerifyOptions: options.CertVerifyOptions{ CertIdentity: identity, CertOidcIssuer: issuer, }, CertRef: "", // Cert is fetched from bundle CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SigRef: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "unable to verify SET") { t.Fatalf("expected error verifying SET, got %v", err) } }) t.Run("Mismatched certificate email", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ KeyOpts: options.KeyOpts{BundlePath: bundlePath}, CertRef: "", // Cert is fetched from bundle CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: "invalid@example.com", }, CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SigRef: "", // Sig is fetched from bundle IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "none of the expected identities matched what was in the certificate") { t.Fatalf("expected error with mismatched identity, got %v", err) } }) t.Run("Mismatched certificate issuer", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ CertRef: "", // Cert is fetched from bundle CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: "invalid", CertIdentity: identity, }, CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SigRef: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "none of the expected identities matched what was in the certificate") { t.Fatalf("expected error with mismatched issuer, got %v", err) } }) t.Run("Implicit Fulcio chain with bundle in non-experimental mode", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") certPath := writeBlobFile(t, keyless.td, string(leafPemCert), "cert.pem") // Verify command cmd := VerifyBlobCmd{ CertRef: certPath, CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: identity, }, CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SigRef: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err != nil { t.Fatalf("expected success without specifying the intermediates, got %v", err) } }) t.Run("Explicit Fulcio chain with rekor and timestamp bundles in non-experimental mode", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Initialize timestamp with mock client tsaClient, err := mock.NewTSAClient((mock.TSAClientOptions{Time: time.Now(), Message: sig})) if err != nil { t.Fatal(err) } certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(tsaClient.CertChain) if err != nil { t.Fatalf("unexpected error marshalling cert chain: %v", err) } tsaCertChainPath := filepath.Join(keyless.td, "tsacertchain.pem") if err := os.WriteFile(tsaCertChainPath, certChainPEM, 0644); err != nil { t.Fatal(err) } tsr, err := tsaClient.GetTimestampResponse(nil) if err != nil { t.Fatalf("unable to generate a timestamp response: %v", err) } rfc3161Timestamp := &bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsr} tsPath := writeTimestampFile(t, keyless.td, rfc3161Timestamp, "rfc3161TS.json") entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: identity, }, CertChain: os.Getenv("SIGSTORE_ROOT_FILE"), SigRef: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath, TSACertChainPath: tsaCertChainPath, RFC3161TimestampPath: tsPath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err != nil { t.Fatalf("expected success verifying with timestamp, got %v", err) } }) t.Run("Explicit Fulcio chain with bundle in non-experimental mode", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: identity, }, CertChain: os.Getenv("SIGSTORE_ROOT_FILE"), SigRef: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err != nil { t.Fatalf("expected success specifying the intermediates, got %v", err) } }) t.Run("Explicit Fulcio mismatched chain failure", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") rootCert, _, _ := test.GenerateRootCa() rootPemCert, _ := cryptoutils.MarshalCertificateToPEM(rootCert) tmpChainFile, err := os.CreateTemp(t.TempDir(), "cosign_fulcio_root_*.cert") if err != nil { t.Fatalf("failed to create temp chain file: %v", err) } defer tmpChainFile.Close() if _, err := tmpChainFile.Write(rootPemCert); err != nil { t.Fatalf("failed to write chain file: %v", err) } // Verify command cmd := VerifyBlobCmd{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: identity, }, CertChain: tmpChainFile.Name(), SigRef: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { t.Fatalf("expected error with mismatched root, got %v", err) } }) t.Run("intoto Attestation with keyless", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) stmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}` wrapped := dsse.WrapSigner(signer, ctypes.IntotoPayloadType) signedPayload, err := wrapped.SignMessage(bytes.NewReader([]byte(stmt)), signatureoptions.WithContext(context.Background())) if err != nil { t.Fatal(err) } // intoto sig = json-serialized dsse envelope sig := signedPayload // Create bundle entry := genRekorEntry(t, intoto.KIND, "0.0.1", signedPayload, leafPemCert, sig) b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, string(signedPayload), "attestation.txt") // Verify command with bundle cmd := VerifyBlobAttestationCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: identity, }, CertRef: "", // Cert is fetched from bundle CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SignaturePath: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, CheckClaims: false, // Intentionally false. This checks the subject claim. This is tested in verify_blob_attestation_test.go } if err := cmd.Exec(context.Background(), blobPath); err != nil { t.Fatal(err) } }) } func TestVerifyBlobCmdInvalidRootCA(t *testing.T) { keyless := newKeylessStack(t) defer os.RemoveAll(keyless.td) // Change the keyless stack. newKeyless := newKeylessStack(t) defer os.RemoveAll(newKeyless.td) t.Run("Invalid certificate root when specifying cert via certRef", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, newKeyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = newKeyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") certPath := writeBlobFile(t, keyless.td, string(leafPemCert), "cert.pem") // Verify command cmd := VerifyBlobCmd{ CertRef: certPath, CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: identity, }, CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SigRef: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "certificate signed by unknown authority") { t.Fatalf("expected error with certificate signed by unknown authority, got %v", err) } }) t.Run("Invalid certificate root when specifying cert in bundle", func(t *testing.T) { identity := "hello@foo.com" issuer := "issuer" leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer) // Create blob blob := "someblob" // Sign blob with private key sig, err := signer.SignMessage(bytes.NewReader([]byte(blob))) if err != nil { t.Fatal(err) } // Create bundle entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig) b := createBundle(t, sig, leafPemCert, newKeyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry) b.Bundle.SignedEntryTimestamp = newKeyless.rekorSignPayload(t, b.Bundle.Payload) bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json") blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt") // Verify command cmd := VerifyBlobCmd{ CertRef: "", CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, // Fetched from bundle CertIdentity: identity, }, CertChain: "", // Chain is fetched from TUF/SIGSTORE_ROOT_FILE SigRef: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, } err = cmd.Exec(context.Background(), blobPath) if err == nil || !strings.Contains(err.Error(), "certificate signed by unknown authority") { t.Fatalf("expected error with certificate signed by unknown authority, got %v", err) } }) } type keylessStack struct { rootCert *x509.Certificate rootPriv *ecdsa.PrivateKey rootPemCert []byte subCert *x509.Certificate subPriv *ecdsa.PrivateKey subPemCert []byte rekorSigner *signature.ECDSASignerVerifier rekorLogID string td string // temporary directory } func newKeylessStack(t *testing.T) *keylessStack { stack := &keylessStack{td: t.TempDir()} stack.rootCert, stack.rootPriv, _ = test.GenerateRootCa() stack.rootPemCert, _ = cryptoutils.MarshalCertificateToPEM(stack.rootCert) stack.subCert, stack.subPriv, _ = test.GenerateSubordinateCa(stack.rootCert, stack.rootPriv) stack.subPemCert, _ = cryptoutils.MarshalCertificateToPEM(stack.subCert) stack.genChainFile(t) stack.genRekor(t) return stack } func (s *keylessStack) genLeafCert(t *testing.T, subject string, issuer string) (*x509.Certificate, *ecdsa.PrivateKey, []byte, *signature.ECDSASignerVerifier) { //nolint: unparam cert, priv, _ := test.GenerateLeafCert(subject, issuer, s.subCert, s.subPriv) pemCert, _ := cryptoutils.MarshalCertificateToPEM(cert) signer, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256) if err != nil { t.Fatal(err) } return cert, priv, pemCert, signer } func (s *keylessStack) genChainFile(t *testing.T) { var chain []byte chain = append(chain, s.subPemCert...) chain = append(chain, s.rootPemCert...) tmpChainFile, err := os.CreateTemp(s.td, "cosign_fulcio_chain_*.cert") if err != nil { t.Fatalf("failed to create temp chain file: %v", err) } defer tmpChainFile.Close() if _, err := tmpChainFile.Write(chain); err != nil { t.Fatalf("failed to write chain file: %v", err) } // Override for Fulcio root so it doesn't use TUF t.Setenv("SIGSTORE_ROOT_FILE", tmpChainFile.Name()) } func (s *keylessStack) genRekor(t *testing.T) { // Create Rekor private key and write to disk rekorPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatal(err) } s.rekorSigner, err = signature.LoadECDSASignerVerifier(rekorPriv, crypto.SHA256) if err != nil { t.Fatal(err) } rekorPub := s.rekorSigner.Public() pemRekor, err := cryptoutils.MarshalPublicKeyToPEM(rekorPub) if err != nil { t.Fatal(err) } tmpRekorPubFile, err := os.CreateTemp(s.td, "cosign_rekor_pub_*.key") if err != nil { t.Fatalf("failed to create temp rekor pub file: %v", err) } defer tmpRekorPubFile.Close() if _, err := tmpRekorPubFile.Write(pemRekor); err != nil { t.Fatalf("failed to write rekor pub file: %v", err) } // Calculate log ID, the digest of the Rekor public key s.rekorLogID, err = getLogID(rekorPub) if err != nil { t.Fatal(err) } // Override for Rekor public key so it doesn't use TUF t.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", tmpRekorPubFile.Name()) } func (s *keylessStack) rekorSignPayload(t *testing.T, payload bundle.RekorPayload) []byte { // Marshal payload, sign, and return SET jsonPayload, err := json.Marshal(payload) if err != nil { t.Fatal(err) } canonicalized, err := jsoncanonicalizer.Transform(jsonPayload) if err != nil { t.Fatal(err) } bundleSig, err := s.rekorSigner.SignMessage(bytes.NewReader(canonicalized)) if err != nil { t.Fatal(err) } return bundleSig } // getLogID calculates the digest of a PKIX-encoded public key func getLogID(pub crypto.PublicKey) (string, error) { pubBytes, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return "", err } digest := sha256.Sum256(pubBytes) return hex.EncodeToString(digest[:]), nil } func genRekorEntry(t *testing.T, kind, version string, artifact []byte, cert []byte, sig []byte) string { // Generate the Rekor Entry entryImpl, err := createEntry(context.Background(), kind, version, artifact, cert, sig) if err != nil { t.Fatal(err) } entryBytes, err := entryImpl.Canonicalize(context.Background()) if err != nil { t.Fatal(err) } return base64.StdEncoding.EncodeToString(entryBytes) } func createBundle(_ *testing.T, sig []byte, certPem []byte, logID string, integratedTime int64, rekorEntry string) *cosign.LocalSignedPayload { // Create bundle with: // * Blob signature // * Signing certificate // * Bundle with a payload and signature over the payload b := &cosign.LocalSignedPayload{ Base64Signature: base64.StdEncoding.EncodeToString(sig), Cert: string(certPem), Bundle: &bundle.RekorBundle{ SignedEntryTimestamp: []byte{}, Payload: bundle.RekorPayload{ LogID: logID, IntegratedTime: integratedTime, LogIndex: 1, Body: rekorEntry, }, }, } return b } func createEntry(ctx context.Context, kind, apiVersion string, blobBytes, certBytes, sigBytes []byte) (types.EntryImpl, error) { props := types.ArtifactProperties{ PublicKeyBytes: [][]byte{certBytes}, PKIFormat: string(pki.X509), } switch kind { case rekord.KIND: props.ArtifactBytes = blobBytes props.SignatureBytes = sigBytes case hashedrekord.KIND: blobHash := sha256.Sum256(blobBytes) props.ArtifactHash = strings.ToLower(hex.EncodeToString(blobHash[:])) props.SignatureBytes = sigBytes case intoto.KIND: props.ArtifactBytes = blobBytes case rekor_dsse.KIND: props.ArtifactBytes = blobBytes default: return nil, fmt.Errorf("unexpected entry kind: %s", kind) } proposedEntry, err := types.NewProposedEntry(ctx, kind, apiVersion, props) if err != nil { return nil, err } eimpl, err := types.CreateVersionedEntry(proposedEntry) if err != nil { return nil, err } can, err := types.CanonicalizeEntry(ctx, eimpl) if err != nil { return nil, err } proposedEntryCan, err := models.UnmarshalProposedEntry(bytes.NewReader(can), runtime.JSONConsumer()) if err != nil { return nil, err } return types.UnmarshalEntry(proposedEntryCan) } func writeBundleFile(t *testing.T, td string, b *cosign.LocalSignedPayload, name string) string { //nolint: unparam // Write bundle to disk jsonBundle, err := json.Marshal(b) if err != nil { t.Fatal(err) } bundlePath := filepath.Join(td, name) if err := os.WriteFile(bundlePath, jsonBundle, 0644); err != nil { t.Fatal(err) } return bundlePath } func writeBlobFile(t *testing.T, td string, blob string, name string) string { // Write blob to disk blobPath := filepath.Join(td, name) if err := os.WriteFile(blobPath, []byte(blob), 0644); err != nil { t.Fatal(err) } return blobPath } func writeTimestampFile(t *testing.T, td string, ts *bundle.RFC3161Timestamp, name string) string { jsonBundle, err := json.Marshal(ts) if err != nil { t.Fatal(err) } path := filepath.Join(td, name) if err := os.WriteFile(path, jsonBundle, 0644); err != nil { t.Fatal(err) } return path } func writeTrustedRootFile(t *testing.T, td, contents string) string { path := filepath.Join(td, "trusted_root.json") if err := os.WriteFile(path, []byte(contents), 0644); err != nil { t.Fatal(err) } return path } cosign-2.5.0/cmd/cosign/cli/verify/verify_bundle.go000066400000000000000000000120021477503325500222720ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "bytes" "context" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "fmt" "github.com/secure-systems-lab/go-securesystemslib/dsse" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" protodsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse" protorekor "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/tle" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/cosign/v2/pkg/cosign" ) func checkNewBundle(bundlePath string) bool { _, err := sgbundle.LoadJSONFromPath(bundlePath) return err == nil } func AssembleNewBundle(ctx context.Context, sigBytes, signedTimestamp []byte, envelope *dsse.Envelope, artifactRef string, cert *x509.Certificate, ignoreTlog bool, sigVerifier signature.Verifier, pkOpts []signature.PublicKeyOption, rekorClient *client.Rekor) (*sgbundle.Bundle, error) { payload, err := payloadBytes(artifactRef) if err != nil { return nil, err } buf := bytes.NewBuffer(payload) digest := sha256.Sum256(buf.Bytes()) pb := &protobundle.Bundle{ MediaType: "application/vnd.dev.sigstore.bundle+json;version=0.3", VerificationMaterial: &protobundle.VerificationMaterial{}, } if envelope != nil && len(envelope.Signatures) > 0 { sigDecode, err := base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig) if err != nil { return nil, err } sig := &protodsse.Signature{ Sig: sigDecode, } payloadDecode, err := base64.StdEncoding.DecodeString(envelope.Payload) if err != nil { return nil, err } pb.Content = &protobundle.Bundle_DsseEnvelope{ DsseEnvelope: &protodsse.Envelope{ Payload: payloadDecode, PayloadType: envelope.PayloadType, Signatures: []*protodsse.Signature{sig}, }, } } else { pb.Content = &protobundle.Bundle_MessageSignature{ MessageSignature: &protocommon.MessageSignature{ MessageDigest: &protocommon.HashOutput{ Algorithm: protocommon.HashAlgorithm_SHA2_256, Digest: digest[:], }, Signature: sigBytes, }, } } if cert != nil { pb.VerificationMaterial.Content = &protobundle.VerificationMaterial_Certificate{ Certificate: &protocommon.X509Certificate{ RawBytes: cert.Raw, }, } } else if sigVerifier != nil { pub, err := sigVerifier.PublicKey(pkOpts...) if err != nil { return nil, err } pubKeyBytes, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return nil, err } hashedBytes := sha256.Sum256(pubKeyBytes) pb.VerificationMaterial.Content = &protobundle.VerificationMaterial_PublicKey{ PublicKey: &protocommon.PublicKeyIdentifier{ Hint: base64.StdEncoding.EncodeToString(hashedBytes[:]), }, } } if len(signedTimestamp) > 0 { ts := &protocommon.RFC3161SignedTimestamp{ SignedTimestamp: signedTimestamp, } pb.VerificationMaterial.TimestampVerificationData = &protobundle.TimestampVerificationData{ Rfc3161Timestamps: []*protocommon.RFC3161SignedTimestamp{ts}, } } if !ignoreTlog { var pem []byte var err error if cert != nil { pem, err = cryptoutils.MarshalCertificateToPEM(cert) if err != nil { return nil, err } } else if sigVerifier != nil { pub, err := sigVerifier.PublicKey(pkOpts...) if err != nil { return nil, err } pem, err = cryptoutils.MarshalPublicKeyToPEM(pub) if err != nil { return nil, err } } var sigB64 string var payload []byte if envelope != nil && len(envelope.Signatures) > 0 { payload, err = json.Marshal(*envelope) if err != nil { return nil, err } } else { sigB64 = base64.StdEncoding.EncodeToString(sigBytes) payload = buf.Bytes() } tlogEntries, err := cosign.FindTlogEntry(ctx, rekorClient, sigB64, payload, pem) if err != nil { return nil, err } if len(tlogEntries) == 0 { return nil, fmt.Errorf("unable to find tlog entry") } if len(tlogEntries) > 1 { return nil, fmt.Errorf("too many tlog entries; should only have 1") } tlogEntry, err := tle.GenerateTransparencyLogEntry(tlogEntries[0]) if err != nil { return nil, err } pb.VerificationMaterial.TlogEntries = []*protorekor.TransparencyLogEntry{tlogEntry} } b, err := sgbundle.NewBundle(pb) if err != nil { return nil, err } return b, nil } cosign-2.5.0/cmd/cosign/cli/verify/verify_test.go000066400000000000000000000232311477503325500220060ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "io" "log" "os" "sync" "testing" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/internal/pkg/cosign/fulcio/fulcioroots" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/test" "github.com/sigstore/sigstore/pkg/signature/payload" "github.com/stretchr/testify/assert" ) type certData struct { RootCert *x509.Certificate RootKey *ecdsa.PrivateKey SubCert *x509.Certificate SubKey *ecdsa.PrivateKey LeafCert *x509.Certificate PrivKey *ecdsa.PrivateKey RootCertPEM []byte SubCertPEM []byte LeafCertPEM []byte } func getTestCerts(t *testing.T) *certData { t.Helper() eexts := []pkix.Extension{ {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, Value: []byte("myWorkflowTrigger")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}, Value: []byte("myWorkflowSha")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}, Value: []byte("myWorkflowName")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}, Value: []byte("myWorkflowRepository")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, Value: []byte("myWorkflowRef")}, } cd := &certData{} var err error if cd.RootCert, cd.RootKey, err = test.GenerateRootCa(); err != nil { t.Fatal(err) } if cd.SubCert, cd.SubKey, err = test.GenerateSubordinateCa(cd.RootCert, cd.RootKey); err != nil { t.Fatal(err) } if cd.LeafCert, cd.PrivKey, err = test.GenerateLeafCert("subject", "oidc-issuer", cd.SubCert, cd.SubKey, eexts...); err != nil { t.Fatal(err) } cd.RootCertPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cd.RootCert.Raw}) cd.SubCertPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cd.SubCert.Raw}) cd.LeafCertPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cd.LeafCert.Raw}) return cd } func makeCertChainFile(t *testing.T, rootCert, subCert, leafCert []byte) string { t.Helper() f, err := os.CreateTemp("", "certchain") if err != nil { t.Fatal(err) } defer f.Close() _, err = f.Write(append(append(rootCert, subCert...), leafCert...)) if err != nil { t.Fatal(err) } return f.Name() } func makeRootsIntermediatesFiles(t *testing.T, roots, intermediates []byte) (string, string) { t.Helper() rootF, err := os.CreateTemp("", "roots") if err != nil { t.Fatal(err) } defer rootF.Close() _, err = rootF.Write(roots) if err != nil { t.Fatal(err) } intermediateF, err := os.CreateTemp("", "intermediates") if err != nil { t.Fatal(err) } defer intermediateF.Close() _, err = intermediateF.Write(intermediates) if err != nil { t.Fatal(err) } return rootF.Name(), intermediateF.Name() } func TestPrintVerification(t *testing.T) { // while we are adding a more human-readable output for cert extensions, on the other hand // we want as backward compatible as possible, so we are keeping the old OIDs field names as well. wantPayload := ` [ { "critical": { "identity": { "docker-reference": "gcr.io/baz/baz" }, "image": { "docker-manifest-digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }, "type": "cosign container image signature" }, "optional": { "1.3.6.1.4.1.57264.1.1": "oidc-issuer", "1.3.6.1.4.1.57264.1.2": "myWorkflowTrigger", "1.3.6.1.4.1.57264.1.3": "myWorkflowSha", "1.3.6.1.4.1.57264.1.4": "myWorkflowName", "1.3.6.1.4.1.57264.1.5": "myWorkflowRepository", "1.3.6.1.4.1.57264.1.6": "myWorkflowRef", "Issuer": "oidc-issuer", "Subject": "subject", "githubWorkflowName": "myWorkflowName", "githubWorkflowRef": "myWorkflowRef", "githubWorkflowRepository": "myWorkflowRepository", "githubWorkflowSha": "myWorkflowSha", "githubWorkflowTrigger": "myWorkflowTrigger" } } ] ` certs := getTestCerts(t) rootPool := x509.NewCertPool() rootPool.AddCert(certs.RootCert) // Generate the payload for the image, and check the digest. b := bytes.Buffer{} dig3, err := name.NewDigest("gcr.io/baz/baz@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", name.StrictValidation) if err != nil { t.Fatalf("Error creating test dig3.") } pp, err := (&payload.Cosign{Image: dig3, Annotations: map[string]interface{}{}}).MarshalJSON() if err != nil { t.Fatalf("Error creating cosign payload") } fmt.Fprintln(&b, string(pp)) p := b.Bytes() h := sha256.Sum256(p) signature, _ := certs.PrivKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(p, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(certs.LeafCertPEM, appendSlices([][]byte{certs.SubCertPEM, certs.RootCertPEM}))) captureOutput := func(f func()) string { reader, writer, err := os.Pipe() if err != nil { panic(err) } stdout := os.Stdout stderr := os.Stderr defer func() { os.Stdout = stdout os.Stderr = stderr log.SetOutput(os.Stderr) }() os.Stdout = writer os.Stderr = writer log.SetOutput(writer) out := make(chan string) wg := new(sync.WaitGroup) wg.Add(1) go func() { var buf bytes.Buffer wg.Done() io.Copy(&buf, reader) out <- buf.String() }() wg.Wait() f() writer.Close() return <-out } _ = captureOutput out := captureOutput(func() { ui.RunWithTestCtx(func(ctx context.Context, _ ui.WriteFunc) { PrintVerification(ctx, []oci.Signature{ociSig}, "json") }) }) prettyPrint := func(b []byte) ([]byte, error) { var out bytes.Buffer err := json.Indent(&out, b, "", " ") return out.Bytes(), err } i, err := prettyPrint([]byte(out)) if err != nil { t.Fatal(err.Error()) } assert.JSONEq(t, wantPayload, string(i)) } func appendSlices(slices [][]byte) []byte { var tmp []byte for _, s := range slices { tmp = append(tmp, s...) } return tmp } func TestVerifyCertMissingSubject(t *testing.T) { ctx := context.Background() verifyCommand := VerifyCommand{ CertRef: "cert.pem", CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: "issuer", }, } err := verifyCommand.Exec(ctx, []string{"foo", "bar", "baz"}) if err == nil { t.Fatal("verify expected 'need --certificate-identity'") } } func TestVerifyCertMissingIssuer(t *testing.T) { ctx := context.Background() verifyCommand := VerifyCommand{ CertRef: "cert.pem", CertVerifyOptions: options.CertVerifyOptions{ CertIdentity: "identity", }, } err := verifyCommand.Exec(ctx, []string{"foo", "bar", "baz"}) if err == nil { t.Fatal("verify expected 'need --certificate-oidc-issuer'") } } func TestLoadCertsKeylessVerification(t *testing.T) { certs := getTestCerts(t) certChainFile := makeCertChainFile(t, certs.RootCertPEM, certs.SubCertPEM, certs.LeafCertPEM) rootsFile, intermediatesFile := makeRootsIntermediatesFiles(t, certs.RootCertPEM, certs.SubCertPEM) tests := []struct { name string certChain string caRoots string caIntermediates string co *cosign.CheckOpts sigstoreRootFile string wantErr bool }{ { name: "default fulcio", wantErr: false, }, { name: "non-existent SIGSTORE_ROOT_FILE", sigstoreRootFile: "tesdata/nosuch-asdfjkl.pem", wantErr: true, }, { name: "good certchain", certChain: certChainFile, wantErr: false, }, { name: "bad certchain", certChain: "testdata/nosuch-certchain-file.pem", wantErr: true, }, { name: "roots", caRoots: rootsFile, wantErr: false, }, { name: "bad roots", caRoots: "testdata/nosuch-roots-file.pem", wantErr: true, }, { name: "roots and intermediate", caRoots: rootsFile, caIntermediates: intermediatesFile, wantErr: false, }, { name: "bad roots good intermediate", caRoots: "testdata/nosuch-roots-file.pem", caIntermediates: intermediatesFile, wantErr: true, }, { name: "good roots bad intermediate", caRoots: rootsFile, caIntermediates: "testdata/nosuch-intermediates-file.pem", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.sigstoreRootFile != "" { os.Setenv("SIGSTORE_ROOT_FILE", tt.sigstoreRootFile) } else { t.Setenv("SIGSTORE_ROOT_FILE", "") } fulcioroots.ReInit() if tt.co == nil { tt.co = &cosign.CheckOpts{} } err := loadCertsKeylessVerification(tt.certChain, tt.caRoots, tt.caIntermediates, tt.co) if err == nil && tt.wantErr { t.Fatalf("expected error but got none") } else if err != nil && !tt.wantErr { t.Fatalf("unexpected error: %v", err) } }) } } cosign-2.5.0/cmd/cosign/cli/version_test.go000066400000000000000000000070261477503325500206670ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "bytes" "encoding/json" "errors" "fmt" "io" "os" "regexp" "testing" "github.com/stretchr/testify/assert" "sigs.k8s.io/release-utils/version" ) var ( expectedVersionInfo = version.GetVersionInfo() reGitVersion = regexp.MustCompile(fmt.Sprintf("\nGitVersion:\\s+%s\n", expectedVersionInfo.GitVersion)) reGitCommmit = regexp.MustCompile(fmt.Sprintf("GitCommit:\\s+%s\n", expectedVersionInfo.GitCommit)) reBuildDate = regexp.MustCompile(fmt.Sprintf("BuildDate:\\s+%s\n", expectedVersionInfo.BuildDate)) reGoVersion = regexp.MustCompile(fmt.Sprintf("GoVersion:\\s+%s\n", expectedVersionInfo.GoVersion)) reCompiler = regexp.MustCompile(fmt.Sprintf("Compiler:\\s+%s\n", expectedVersionInfo.Compiler)) rePlatform = regexp.MustCompile(fmt.Sprintf("Platform:\\s+%s\n", expectedVersionInfo.Platform)) ) func getVersionSTDOUT(json bool) (bytes.Buffer, error) { command := New() if json { command.SetArgs([]string{"version", "--json"}) } else { command.SetArgs([]string{"version"}) } // testing approach inspired by https://github.com/zenizh/go-capturer/blob/master/main.go reader, writer, err := os.Pipe() if err != nil { return bytes.Buffer{}, errors.New("failed to create a pipe for testing os.Stdout") } stdout := os.Stdout os.Stdout = writer err = command.Execute() os.Stdout = stdout if err != nil { return bytes.Buffer{}, errors.New("version is expected to run with a") } writer.Close() var buffer bytes.Buffer _, err = io.Copy(&buffer, reader) return buffer, err } func testVersionASCII(t *testing.T) { buffer, err := getVersionSTDOUT(false) output := buffer.String() assert.NoError(t, err) assert.Regexp(t, reGitVersion, output, "output doesn't contain the Git version tag") assert.Regexp(t, reGitCommmit, output, "output doesn't contain the Git commit hash") assert.Regexp(t, reBuildDate, output, "output doesn't contain the build date") assert.Regexp(t, reGoVersion, output, "output doesn't contain the Go version") assert.Regexp(t, reCompiler, output, "output doesn't contain the compiler name") assert.Regexp(t, rePlatform, output, "output doesn't contain the platform name") } func testVersionJSON(t *testing.T) { buffer, err := getVersionSTDOUT(true) assert.NoError(t, err) output := buffer.Bytes() var actualVersionInfo version.Info err = json.Unmarshal(output, &actualVersionInfo) assert.NoError(t, err) assert.Equal(t, expectedVersionInfo.GitVersion, actualVersionInfo.GitVersion) assert.Equal(t, expectedVersionInfo.GitCommit, actualVersionInfo.GitCommit) assert.Equal(t, expectedVersionInfo.BuildDate, actualVersionInfo.BuildDate) assert.Equal(t, expectedVersionInfo.GoVersion, actualVersionInfo.GoVersion) assert.Equal(t, expectedVersionInfo.Compiler, actualVersionInfo.Compiler) assert.Equal(t, expectedVersionInfo.Platform, actualVersionInfo.Platform) } func TestVersionOutput(t *testing.T) { t.Run("ASCII", testVersionASCII) t.Run("JSON", testVersionJSON) } cosign-2.5.0/cmd/cosign/errors/000077500000000000000000000000001477503325500163545ustar00rootroot00000000000000cosign-2.5.0/cmd/cosign/errors/error_wrap.go000066400000000000000000000022101477503325500210600ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors // WrapError takes an error type and depending on the type of error // passed, will access it's error message and errorType (and return // the associated exitCode) and wrap them in a generic `CosignError`. // If no custom error has been found, then it will still return a // `CosignError` with an error message, but the `exitCode` will be `1`. func WrapError(err error) error { // return default cosign error with error message and default exit code return &CosignError{ Message: err.Error(), Code: LookupExitCodeForError(err), } } cosign-2.5.0/cmd/cosign/errors/error_wrap_test.go000066400000000000000000000020431477503325500221230ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import ( "errors" "testing" ) func TestWrapWithGenericCosignError(t *testing.T) { errorText := "i am a generic cosign error" err := WrapError(errors.New(errorText)) var cosignError *CosignError if errors.As(err, &cosignError) { if cosignError.ExitCode() == 1 && cosignError.Message == errorText { t.Logf("generic cosign error successfully returned") return } t.Fatalf("generic cosign error unsuccessfully returned") } } cosign-2.5.0/cmd/cosign/errors/errors.go000066400000000000000000000020171477503325500202170ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors type CosignError struct { Message string Code int } func Error(cosignError CosignError) error { return &CosignError{ Message: cosignError.Message, Code: cosignError.Code, } } // Assert that we implement error at build time. var _ error = (*CosignError)(nil) // Error implements error func (ce *CosignError) Error() string { return ce.Message } func (ce *CosignError) ExitCode() int { return ce.Code } cosign-2.5.0/cmd/cosign/errors/exit_code_lookup.go000066400000000000000000000035751477503325500222510ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import ( "errors" cosignError "github.com/sigstore/cosign/v2/pkg/cosign" ) func LookupExitCodeForError(err interface{ error }) int { if noMatchingSignatureError(err) { return NoMatchingSignature } if imageTagNotFoundError(err) { return NonExistentTag } if noSignaturesFoundError(err) { return ImageWithoutSignature } if noCertificateFoundOnSignature(err) { return NoCertificateFoundOnSignature } // we want to return exit code = `1` at this point because there is // no valid exit code found for the error type passed, so we default to 1. return 1 } func noMatchingSignatureError(err interface{ error }) bool { var errNoMatchingSignatures *cosignError.ErrNoMatchingSignatures return errors.As(err, &errNoMatchingSignatures) } func imageTagNotFoundError(err interface{ error }) bool { var errImageTagNotFound *cosignError.ErrImageTagNotFound return errors.As(err, &errImageTagNotFound) } func noSignaturesFoundError(err interface{ error }) bool { var errNoSignaturesFound *cosignError.ErrNoSignaturesFound return errors.As(err, &errNoSignaturesFound) } func noCertificateFoundOnSignature(err interface{ error }) bool { var errNoCertificateFoundOnSignature *cosignError.ErrNoCertificateFoundOnSignature return errors.As(err, &errNoCertificateFoundOnSignature) } cosign-2.5.0/cmd/cosign/errors/exit_code_lookup_test.go000066400000000000000000000026741477503325500233070ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import ( "fmt" "testing" pkgError "github.com/sigstore/cosign/v2/pkg/cosign" ) func TestDefaultExitCodeReturnIfErrorTypeToExitCodeMappingDoesNotExist(t *testing.T) { exitCode := LookupExitCodeForError(fmt.Errorf("I do not exist as an error type")) if exitCode != 1 { t.Fatalf("default exit code not returned when an error type doesn't exist. default should be 1") } t.Logf("Correct default exit code returned") } func TestDefaultExitCodeReturnIfErrorTypeToExitCodeMappingExists(t *testing.T) { // We test with any error that is not a generic CosignError. // In this case, ErrNoMatchingSignatures exitCode := LookupExitCodeForError(&pkgError.ErrNoMatchingSignatures{}) if exitCode != NoMatchingSignature { t.Fatalf("NoMatchingSignature exit code not returned when error is thrown") } t.Logf("Correct default exit code returned") } cosign-2.5.0/cmd/cosign/errors/exit_codes.go000066400000000000000000000025171477503325500210360ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors // Exit codes for cosign. // To allow for document generation of exit codes the following convention is // to be followed. // Convention: // | // comment that explains the error // | const NamedConstant = ERRORCODE // // This is so when `make docgen` is run, the cosign_exit-codes.md doc is automatically // generated inside of the docs dir following the format of "Exit Code : Comment". // Error verifying image due to no signature const ImageWithoutSignature = 10 // Error verifying image due to non-existent tag const NonExistentTag = 11 // Error verifying image due to no matching signature const NoMatchingSignature = 12 // Error verifying image due to no certificate found on signature const NoCertificateFoundOnSignature = 13 cosign-2.5.0/cmd/cosign/errors/generate_docs.go000066400000000000000000000054111477503325500215060ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package errors import ( "fmt" "go/ast" godoc "go/doc" "go/parser" "go/token" "os" "sort" "strconv" "strings" ) // GenerateExitCodeDocs will generate documentation for the exit codes // that the cosign application will throw. Inspired by elemental-cli who // convieniently solved this problem prior: https://github.com/rancher/elemental-cli/blob/main/docs/generate_docs.go // there was no need to change (apart from some file paths) due to it // giving us exactly what we want without error. func GenerateExitCodeDocs(dir string) error { fset := token.NewFileSet() files := []*ast.File{ mustParse(fset, "./cmd/cosign/errors/exit_codes.go"), } p, err := godoc.NewFromFiles(fset, files, "github.com/sigstore/cosign") if err != nil { panic(err) } // nolint prealloc var ( exitCodes []*ErrorCode used map[int]bool ) used = make(map[int]bool) for _, c := range p.Consts { // Cast it, its safe as these are constants v := c.Decl.Specs[0].(*ast.ValueSpec) val := v.Values[0].(*ast.BasicLit) code, _ := strconv.Atoi(val.Value) if _, ok := used[code]; ok { return fmt.Errorf("duplicate exit-code found: %v", code) } used[code] = true exitCodes = append(exitCodes, &ErrorCode{code: code, doc: c.Doc}) } sort.Slice(exitCodes, func(i, j int) bool { return exitCodes[i].code < exitCodes[j].code }) exitCodesFile, err := os.Create(dir + "/cosign_exit_codes.md") if err != nil { fmt.Print(err) return err } defer func() { _ = exitCodesFile.Close() }() _, _ = exitCodesFile.WriteString("# Exit codes for cosign CLI\n\n") _, _ = exitCodesFile.WriteString("> The following exit codes may be subject to change\n\n") _, _ = exitCodesFile.WriteString("| Exit code | Meaning |\n") _, _ = exitCodesFile.WriteString("| :----: | :---- |\n") for _, code := range exitCodes { _, err = fmt.Fprintf(exitCodesFile, "| %d | %s|\n", code.code, strings.Replace(code.doc, "\n", "", 1)) if err != nil { return err } } return nil } func mustParse(fset *token.FileSet, filename string) *ast.File { f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) if err != nil { panic(err) } return f } type ErrorCode struct { code int doc string } cosign-2.5.0/cmd/cosign/main.go000066400000000000000000000047561477503325500163270ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "errors" "fmt" "log" "os" "strings" "github.com/sigstore/cosign/v2/cmd/cosign/cli" cosignError "github.com/sigstore/cosign/v2/cmd/cosign/errors" "github.com/sigstore/cosign/v2/internal/ui" // Register the provider-specific plugins _ "github.com/sigstore/sigstore/pkg/signature/kms/aws" _ "github.com/sigstore/sigstore/pkg/signature/kms/azure" _ "github.com/sigstore/sigstore/pkg/signature/kms/gcp" _ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault" ) func main() { // Fix up flags to POSIX standard flags. ctx := context.Background() for i, arg := range os.Args { if (strings.HasPrefix(arg, "-") && len(arg) == 2) || (strings.HasPrefix(arg, "--") && len(arg) >= 4) { continue } if strings.HasPrefix(arg, "--") && len(arg) == 3 { // Handle --o, convert to -o newArg := fmt.Sprintf("-%c", arg[2]) ui.Warnf(ctx, "the flag %s is deprecated and will be removed in a future release. Please use the flag %s.", arg, newArg) os.Args[i] = newArg } else if strings.HasPrefix(arg, "-") && len(arg) > 1 { // Handle -output, convert to --output newArg := fmt.Sprintf("-%s", arg) newArgType := "flag" if newArg == "--version" { newArg = "version" newArgType = "subcommand" } ui.Warnf(ctx, "the %s flag is deprecated and will be removed in a future release. "+ "Please use the %s %s instead.", arg, newArg, newArgType, ) os.Args[i] = newArg } } if err := cli.New().Execute(); err != nil { // if the error is a `CosignError` then we want to use the exit code that // is related to the type of error that has occurred. var cosignError *cosignError.CosignError if errors.As(err, &cosignError) { log.Printf("error during command execution: %v", err) os.Exit(cosignError.ExitCode()) } // we don't call os.Exit as Fatalf does both PrintF and os.Exit(1) log.Fatalf("error during command execution: %v", err) } } cosign-2.5.0/cmd/help/000077500000000000000000000000001477503325500145065ustar00rootroot00000000000000cosign-2.5.0/cmd/help/main.go000066400000000000000000000026571477503325500157730ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "os" "github.com/sigstore/cosign/v2/cmd/cosign/cli" "github.com/sigstore/cosign/v2/cmd/cosign/cli/templates" errors "github.com/sigstore/cosign/v2/cmd/cosign/errors" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" ) func main() { var dir string root := &cobra.Command{ Use: "gendoc", Short: "Generate cosign's help docs", SilenceUsage: true, Args: cobra.NoArgs, RunE: func(*cobra.Command, []string) error { err := errors.GenerateExitCodeDocs(dir) if err != nil { fmt.Println(err) os.Exit(1) } return doc.GenMarkdownTree(cli.New(), dir) }, } root.Flags().StringVarP(&dir, "dir", "d", "doc", "Path to directory in which to generate docs") templates.SetCustomUsageFunc(root) if err := root.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } cosign-2.5.0/cmd/help/verify.sh000077500000000000000000000016401477503325500163520ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2021 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -e # Verify that generated Markdown docs are up-to-date. tmpdir=$(mktemp -d) go run -tags pivkey,pkcs11key,cgo cmd/help/main.go --dir "$tmpdir" echo "###########################################" echo "If diffs are found, run: make docgen" echo "###########################################" diff -Naur "$tmpdir" doc/ cosign-2.5.0/cmd/sample/000077500000000000000000000000001477503325500150375ustar00rootroot00000000000000cosign-2.5.0/cmd/sample/main.go000066400000000000000000000012431477503325500163120ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import "log" func main() { log.Printf("Hello, World!") } cosign-2.5.0/codecov.yml000066400000000000000000000011761477503325500151650ustar00rootroot00000000000000# Copyright 2023 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. coverage: status: project: off patch: off cosign-2.5.0/doc/000077500000000000000000000000001477503325500135605ustar00rootroot00000000000000cosign-2.5.0/doc/cosign.md000066400000000000000000000067121477503325500153720ustar00rootroot00000000000000## cosign A tool for Container Signing, Verification and Storage in an OCI registry. ### Options ``` -h, --help help for cosign --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign attach](cosign_attach.md) - Provides utilities for attaching artifacts to other artifacts in a registry * [cosign attest](cosign_attest.md) - Attest the supplied container image. * [cosign attest-blob](cosign_attest-blob.md) - Attest the supplied blob. * [cosign bundle](cosign_bundle.md) - Interact with a Sigstore protobuf bundle * [cosign clean](cosign_clean.md) - Remove all signatures from an image. * [cosign completion](cosign_completion.md) - Generate completion script * [cosign copy](cosign_copy.md) - Copy the supplied container image and signatures. * [cosign dockerfile](cosign_dockerfile.md) - Provides utilities for discovering images in and performing operations on Dockerfiles * [cosign download](cosign_download.md) - Provides utilities for downloading artifacts and attached artifacts in a registry * [cosign env](cosign_env.md) - Prints Cosign environment variables * [cosign generate](cosign_generate.md) - Generates (unsigned) signature payloads from the supplied container image. * [cosign generate-key-pair](cosign_generate-key-pair.md) - Generates a key-pair. * [cosign import-key-pair](cosign_import-key-pair.md) - Imports a PEM-encoded RSA or EC private key. * [cosign initialize](cosign_initialize.md) - Initializes SigStore root to retrieve trusted certificate and key targets for verification. * [cosign load](cosign_load.md) - Load a signed image on disk to a remote registry * [cosign login](cosign_login.md) - Log in to a registry * [cosign manifest](cosign_manifest.md) - Provides utilities for discovering images in and performing operations on Kubernetes manifests * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token * [cosign pkcs11-tool](cosign_pkcs11-tool.md) - Provides utilities for retrieving information from a PKCS11 token. * [cosign public-key](cosign_public-key.md) - Gets a public key from the key-pair. * [cosign save](cosign_save.md) - Save the container image and associated signatures to disk at the specified directory. * [cosign sign](cosign_sign.md) - Sign the supplied container image. * [cosign sign-blob](cosign_sign-blob.md) - Sign the supplied blob, outputting the base64-encoded signature to stdout. * [cosign tree](cosign_tree.md) - Display supply chain security related artifacts for an image such as signatures, SBOMs and attestations * [cosign triangulate](cosign_triangulate.md) - Outputs the located cosign image reference. This is the location where cosign stores the specified artifact type. * [cosign trusted-root](cosign_trusted-root.md) - Interact with a Sigstore protobuf trusted root * [cosign upload](cosign_upload.md) - Provides utilities for uploading artifacts to a registry * [cosign verify](cosign_verify.md) - Verify a signature on the supplied container image * [cosign verify-attestation](cosign_verify-attestation.md) - Verify an attestation on the supplied container image * [cosign verify-blob](cosign_verify-blob.md) - Verify a signature on the supplied blob * [cosign verify-blob-attestation](cosign_verify-blob-attestation.md) - Verify an attestation on the supplied blob * [cosign version](cosign_version.md) - Prints the version cosign-2.5.0/doc/cosign_attach.md000066400000000000000000000014451477503325500167140ustar00rootroot00000000000000## cosign attach Provides utilities for attaching artifacts to other artifacts in a registry ### Options ``` -h, --help help for attach ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign attach attestation](cosign_attach_attestation.md) - Attach attestation to the supplied container image * [cosign attach sbom](cosign_attach_sbom.md) - DEPRECATED: Attach sbom to the supplied container image * [cosign attach signature](cosign_attach_signature.md) - Attach signatures to the supplied container image cosign-2.5.0/doc/cosign_attach_attestation.md000066400000000000000000000064501477503325500213340ustar00rootroot00000000000000## cosign attach attestation Attach attestation to the supplied container image ``` cosign attach attestation [flags] ``` ### Examples ``` cosign attach attestation --attestation # attach attestations from multiple files to a container image cosign attach attestation --attestation --attestation # attach attestation from bundle files in form of JSONLines to a container image # https://github.com/in-toto/attestation/blob/main/spec/v1.0-draft/bundle.md cosign attach attestation --attestation ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --attestation stringArray path to the attestation envelope -h, --help help for attestation --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign attach](cosign_attach.md) - Provides utilities for attaching artifacts to other artifacts in a registry cosign-2.5.0/doc/cosign_attach_sbom.md000066400000000000000000000070201477503325500177270ustar00rootroot00000000000000## cosign attach sbom DEPRECATED: Attach sbom to the supplied container image ### Synopsis Attach sbom to the supplied container image WARNING: SBOM attachments are deprecated and support will be removed in a Cosign release soon after 2024-02-22 (see https://github.com/sigstore/cosign/issues/2755). Instead, please use SBOM attestations. ``` cosign attach sbom [flags] ``` ### Examples ``` cosign attach sbom ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -h, --help help for sbom --input-format string type of sbom input format (json|xml|text) --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-referrers-mode registryReferrersMode mode for fetching references from the registry. allowed: legacy, oci-1-1 --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --sbom string path to the sbom, or {-} for stdin --type string type of sbom (spdx|cyclonedx|syft) (default "spdx") ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign attach](cosign_attach.md) - Provides utilities for attaching artifacts to other artifacts in a registry cosign-2.5.0/doc/cosign_attach_signature.md000066400000000000000000000116621477503325500207770ustar00rootroot00000000000000## cosign attach signature Attach signatures to the supplied container image ``` cosign attach signature [flags] ``` ### Examples ``` cosign attach signature [--payload ] [--signature < path>] [--rekor-response < path>] cosign attach signature command attaches payload, signature, rekor-bundle, etc in a new layer of provided image. # Attach signature can attach payload to a supplied image cosign attach signature --payload $IMAGE # Attach signature can attach payload, signature to a supplied image cosign attach signature --payload --signature $IMAGE # Attach signature can attach payload, signature, time stamped response to a supplied image cosign attach signature --payload --signature --tsr= $IMAGE # Attach signature attaches payload, signature and rekor-bundle via rekor-response to a supplied image cosign attach signature --payload --signature --rekor-response $IMAGE # Attach signature attaches payload, signature and rekor-bundle directly to a supplied image cosign attach signature --payload --signature --rekor-response $IMAGE ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --certificate string path to the X.509 certificate in PEM format to include in the OCI Signature --certificate-chain string path to a list of CA X.509 certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Included in the OCI Signature -h, --help help for signature --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --payload string path to the payload covered by the signature --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --rekor-response string path to the rekor bundle --signature string path to the signature, or {-} for stdin --tsr string path to the Time Stamped Signature Response from RFC3161 compliant TSA ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign attach](cosign_attach.md) - Provides utilities for attaching artifacts to other artifacts in a registry cosign-2.5.0/doc/cosign_attest-blob.md000066400000000000000000000136551477503325500176760ustar00rootroot00000000000000## cosign attest-blob Attest the supplied blob. ``` cosign attest-blob [flags] ``` ### Examples ``` cosign attest-blob --key | [--predicate ] [--a key=value] [--f] [--r] # attach an attestation to a blob with a local key pair file and print the attestation cosign attest-blob --predicate --type --key cosign.key --output-attestation # attach an attestation to a blob with a key pair stored in Azure Key Vault cosign attest-blob --predicate --type --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # attach an attestation to a blob with a key pair stored in AWS KMS cosign attest-blob --predicate --type --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # attach an attestation to a blob with a key pair stored in Google Cloud KMS cosign attest-blob --predicate --type --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY]/versions/[VERSION] # attach an attestation to a blob with a key pair stored in Hashicorp Vault cosign attest-blob --predicate --type --key hashivault://[KEY] # supply attestation via stdin echo | cosign attest-blob --predicate - --yes ``` ### Options ``` --bundle string write everything required to verify the blob to a FILE --certificate string path to the X.509 certificate in PEM format to include in the OCI Signature --certificate-chain string path to a list of CA X.509 certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Included in the OCI Signature --fulcio-auth-flow string fulcio interactive oauth2 flow to use for certificate from fulcio. Defaults to determining the flow based on the runtime environment. (options) normal|device|token|client_credentials --fulcio-url string address of sigstore PKI server (default "https://fulcio.sigstore.dev") --hash string hash of blob in hexadecimal (base16). Used if you want to sign an artifact stored elsewhere and have the hash -h, --help help for attest-blob --identity-token string identity token to use for certificate from fulcio. the token or a path to a file containing the token is accepted. --insecure-skip-verify skip verifying fulcio published to the SCT (this should only be used for testing). --key string path to the private key file, KMS URI or Kubernetes Secret --new-bundle-format output bundle in new format that contains all verification material --oidc-client-id string OIDC client ID for application (default "sigstore") --oidc-client-secret-file string Path to file containing OIDC client secret for application --oidc-disable-ambient-providers Disable ambient OIDC providers. When true, ambient credentials will not be read --oidc-issuer string OIDC provider to be used to issue ID token (default "https://oauth2.sigstore.dev/auth") --oidc-provider string Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github-actions, filesystem, buildkite-agent] --oidc-redirect-url string OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. --output-attestation string write the attestation to FILE --output-certificate string write the certificate to FILE --output-signature string write the signature to FILE --predicate string path to the predicate file. --rekor-entry-type string specifies the type to be used for a rekor entry upload (dsse|intoto) (default "dsse") --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --rfc3161-timestamp-bundle string path to an RFC 3161 timestamp bundle FILE --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-client-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server --timestamp-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server --timestamp-client-key string path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server --timestamp-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) --type string specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom") -y, --yes skip confirmation prompts for non-destructive operations ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_attest.md000066400000000000000000000245451477503325500167620ustar00rootroot00000000000000## cosign attest Attest the supplied container image. ``` cosign attest [flags] ``` ### Examples ``` cosign attest --key | [--predicate ] [--a key=value] [--no-upload=true|false] [--record-creation-timestamp=true|false] [--f] [--r] # attach an attestation to a container image Google sign-in cosign attest --timeout 90s --predicate --type # attach an attestation to a container image with a local key pair file cosign attest --predicate --type --key cosign.key # attach an attestation to a container image with a key pair stored in Azure Key Vault cosign attest --predicate --type --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # attach an attestation to a container image with a key pair stored in AWS KMS cosign attest --predicate --type --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # attach an attestation to a container image with a key pair stored in Google Cloud KMS cosign attest --predicate --type --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY]/versions/[VERSION] # attach an attestation to a container image with a key pair stored in Hashicorp Vault cosign attest --predicate --type --key hashivault://[KEY] # attach an attestation to a container image with a local key pair file, including a certificate and certificate chain cosign attest --predicate --type --key cosign.key --cert cosign.crt --cert-chain chain.crt # attach an attestation to a container image which does not fully support OCI media types COSIGN_DOCKER_MEDIA_TYPES=1 cosign attest --predicate --type --key cosign.key legacy-registry.example.com/my/image # supply attestation via stdin echo | cosign attest --predicate - # attach an attestation to a container image and honor the creation timestamp of the signature cosign attest --predicate --type --key cosign.key --record-creation-timestamp ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --certificate string path to the X.509 certificate in PEM format to include in the OCI Signature --certificate-chain string path to a list of CA X.509 certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Included in the OCI Signature --fulcio-auth-flow string fulcio interactive oauth2 flow to use for certificate from fulcio. Defaults to determining the flow based on the runtime environment. (options) normal|device|token|client_credentials --fulcio-url string address of sigstore PKI server (default "https://fulcio.sigstore.dev") -h, --help help for attest --identity-token string identity token to use for certificate from fulcio. the token or a path to a file containing the token is accepted. --insecure-skip-verify skip verifying fulcio published to the SCT (this should only be used for testing). --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --key string path to the private key file, KMS URI or Kubernetes Secret --new-bundle-format attach a Sigstore bundle using OCI referrers API --no-upload do not upload the generated attestation --oidc-client-id string OIDC client ID for application (default "sigstore") --oidc-client-secret-file string Path to file containing OIDC client secret for application --oidc-disable-ambient-providers Disable ambient OIDC providers. When true, ambient credentials will not be read --oidc-issuer string OIDC provider to be used to issue ID token (default "https://oauth2.sigstore.dev/auth") --oidc-provider string Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github-actions, filesystem, buildkite-agent] --oidc-redirect-url string OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. --predicate string path to the predicate file. --record-creation-timestamp set the createdAt timestamp in the attestation artifact to the time it was created; by default, cosign sets this to the zero value -r, --recursive if a multi-arch image is specified, additionally sign each discrete image --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --rekor-entry-type string specifies the type to be used for a rekor entry upload (dsse|intoto) (default "dsse") --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --replace --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-client-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server --timestamp-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server --timestamp-client-key string path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server --timestamp-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) --type string specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom") -y, --yes skip confirmation prompts for non-destructive operations ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_bundle.md000066400000000000000000000011261477503325500167150ustar00rootroot00000000000000## cosign bundle Interact with a Sigstore protobuf bundle ### Synopsis Tools for interacting with a Sigstore protobuf bundle ### Options ``` -h, --help help for bundle ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign bundle create](cosign_bundle_create.md) - Create a Sigstore protobuf bundle cosign-2.5.0/doc/cosign_bundle_create.md000066400000000000000000000031501477503325500202370ustar00rootroot00000000000000## cosign bundle create Create a Sigstore protobuf bundle ### Synopsis Create a Sigstore protobuf bundle by supplying signed material ``` cosign bundle create [flags] ``` ### Options ``` --artifact string path to artifact FILE --attestation string path to attestation FILE --bundle string path to old format bundle FILE --certificate string path to the signing certificate, likely from Fulco. -h, --help help for create --ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. --key string path to the public key file, KMS URI or Kubernetes Secret --out string path to output bundle --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --rfc3161-timestamp string path to RFC3161 timestamp FILE --signature string path to base64-encoded signature over attestation in DSSE format --sk whether to use a hardware security key --slot string security key slot to use for generated key (authentication|signature|card-authentication|key-management) (default "signature") ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign bundle](cosign_bundle.md) - Interact with a Sigstore protobuf bundle cosign-2.5.0/doc/cosign_clean.md000066400000000000000000000056711477503325500165370ustar00rootroot00000000000000## cosign clean Remove all signatures from an image. ``` cosign clean [flags] ``` ### Examples ``` cosign clean ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -f, --force do not prompt for confirmation -h, --help help for clean --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --type CLEAN_TYPE a type of clean: (sbom is deprecated) (default all) ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_completion.md000066400000000000000000000030151477503325500176140ustar00rootroot00000000000000## cosign completion Generate completion script ### Synopsis To load completions: Bash: $ source <(cosign completion bash) # To load completions for each session, execute once: # Linux: $ cosign completion bash > /etc/bash_completion.d/cosign # macOS: $ cosign completion bash > /usr/local/etc/bash_completion.d/cosign Zsh: # If shell completion is not already enabled in your environment, # you will need to enable it. You can execute the following once: $ echo "autoload -U compinit; compinit" >> ~/.zshrc # To load completions for each session, execute once: $ cosign completion zsh > "${fpath[1]}/_cosign" # You will need to start a new shell for this setup to take effect. fish: $ cosign completion fish | source # To load completions for each session, execute once: $ cosign completion fish > ~/.config/fish/completions/cosign.fish PowerShell: PS> cosign completion powershell | Out-String | Invoke-Expression # To load completions for every new session, run: PS> cosign completion powershell > cosign.ps1 # and source this file from your PowerShell profile. ``` cosign completion [bash|zsh|fish|powershell] ``` ### Options ``` -h, --help help for completion ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_copy.md000066400000000000000000000075531477503325500164300ustar00rootroot00000000000000## cosign copy Copy the supplied container image and signatures. ``` cosign copy [flags] ``` ### Examples ``` cosign copy # copy a container image and its signatures cosign copy example.com/src:latest example.com/dest:latest # copy the signatures only cosign copy --only=sig example.com/src example.com/dest # copy the signatures, attestations, sbom only cosign copy --only=sig,att,sbom example.com/src example.com/dest # overwrite destination image and signatures cosign copy -f example.com/src example.com/dest # copy a container image and its signatures for a specific platform cosign copy --platform=linux/amd64 example.com/src:latest example.com/dest:latest ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -f, --force overwrite destination image(s), if necessary -h, --help help for copy --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --only strings custom string array to only copy specific items, this flag is comma delimited. ex: --only=sig,att,sbom --platform string only copy container image and its signatures for a specific platform image --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --sig-only [DEPRECATED] only copy the image signature ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_dockerfile.md000066400000000000000000000011551477503325500175550ustar00rootroot00000000000000## cosign dockerfile Provides utilities for discovering images in and performing operations on Dockerfiles ### Options ``` -h, --help help for dockerfile ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign dockerfile verify](cosign_dockerfile_verify.md) - Verify a signature on the base image specified in the Dockerfile cosign-2.5.0/doc/cosign_dockerfile_verify.md000066400000000000000000000301001477503325500211310ustar00rootroot00000000000000## cosign dockerfile verify Verify a signature on the base image specified in the Dockerfile ### Synopsis Verify signature and annotations on images in a Dockerfile by checking claims against the transparency log. Shell-like variables in the Dockerfile's FROM lines will be substituted with values from the OS ENV. ``` cosign dockerfile verify [flags] ``` ### Examples ``` cosign dockerfile verify --key || # verify cosign claims and signing certificates on the FROM images in the Dockerfile cosign dockerfile verify # only verify the base image (the last FROM image) cosign dockerfile verify --base-image-only # additionally verify specified annotations cosign dockerfile verify -a key1=val1 -a key2=val2 # verify images with public key cosign dockerfile verify --key cosign.pub # verify images with public key provided by URL cosign dockerfile verify --key https://host.for/ # verify images with public key stored in Azure Key Vault cosign dockerfile verify --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # verify images with public key stored in AWS KMS cosign dockerfile verify --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # verify images with public key stored in Google Cloud KMS cosign dockerfile verify --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # verify images with public key stored in Hashicorp Vault cosign dockerfile verify --key hashivault://[KEY] ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing -a, --annotations strings extra key=value pairs to sign --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --base-image-only only verify the base image (the last FROM image in the Dockerfile) --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. The flag is optional and must be used together with --ca-roots, conflicts with --certificate-chain. --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --key string path to the public key file, KMS URI or Kubernetes Secret --local-image whether the specified image is a path to an image saved locally via 'cosign save' --max-workers int the amount of maximum workers for parallel executions (default 10) --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification -o, --output string output format for the signing image information (json|text) (default "json") --payload string payload path or remote URL --private-infrastructure skip transparency log verification when verifying artifacts in a privately deployed infrastructure --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --sct string path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs. --signature string signature content or path or remote URL --signature-digest-algorithm string digest algorithm to use when processing a signature (sha224|sha256|sha384|sha512) (default "sha256") --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp --trusted-root string Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set. --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign dockerfile](cosign_dockerfile.md) - Provides utilities for discovering images in and performing operations on Dockerfiles cosign-2.5.0/doc/cosign_download.md000066400000000000000000000015211477503325500172520ustar00rootroot00000000000000## cosign download Provides utilities for downloading artifacts and attached artifacts in a registry ### Options ``` -h, --help help for download ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign download attestation](cosign_download_attestation.md) - Download in-toto attestations from the supplied container image * [cosign download sbom](cosign_download_sbom.md) - DEPRECATED: Download SBOMs from the supplied container image * [cosign download signature](cosign_download_signature.md) - Download signatures from the supplied container image cosign-2.5.0/doc/cosign_download_attestation.md000066400000000000000000000060471477503325500217010ustar00rootroot00000000000000## cosign download attestation Download in-toto attestations from the supplied container image ``` cosign download attestation [flags] ``` ### Examples ``` cosign download attestation [--predicate-type] ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -h, --help help for attestation --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --platform string download attestation for a specific platform image --predicate-type string download attestation with matching predicateType --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign download](cosign_download.md) - Provides utilities for downloading artifacts and attached artifacts in a registry cosign-2.5.0/doc/cosign_download_sbom.md000066400000000000000000000061511477503325500202760ustar00rootroot00000000000000## cosign download sbom DEPRECATED: Download SBOMs from the supplied container image ### Synopsis Download SBOMs from the supplied container image WARNING: SBOM attachments are deprecated and support will be removed in a Cosign release soon after 2024-02-22 (see https://github.com/sigstore/cosign/issues/2755). Instead, please use SBOM attestations. ``` cosign download sbom [flags] ``` ### Examples ``` cosign download sbom ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -h, --help help for sbom --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --platform string download SBOM for a specific platform image --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign download](cosign_download.md) - Provides utilities for downloading artifacts and attached artifacts in a registry cosign-2.5.0/doc/cosign_download_signature.md000066400000000000000000000053341477503325500213410ustar00rootroot00000000000000## cosign download signature Download signatures from the supplied container image ``` cosign download signature [flags] ``` ### Examples ``` cosign download signature ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -h, --help help for signature --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign download](cosign_download.md) - Provides utilities for downloading artifacts and attached artifacts in a registry cosign-2.5.0/doc/cosign_env.md000066400000000000000000000012061477503325500162330ustar00rootroot00000000000000## cosign env Prints Cosign environment variables ``` cosign env [flags] ``` ### Options ``` -h, --help help for env --show-descriptions show descriptions for environment variables (default true) --show-sensitive-values show values of sensitive environment variables ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_exit_codes.md000066400000000000000000000005471477503325500176000ustar00rootroot00000000000000# Exit codes for cosign CLI > The following exit codes may be subject to change | Exit code | Meaning | | :----: | :---- | | 10 | Error verifying image due to no signature| | 11 | Error verifying image due to non-existent tag| | 12 | Error verifying image due to no matching signature| | 13 | Error verifying image due to no certificate found on signature| cosign-2.5.0/doc/cosign_generate-key-pair.md000066400000000000000000000040321477503325500207540ustar00rootroot00000000000000## cosign generate-key-pair Generates a key-pair. ### Synopsis Generates a key-pair for signing. ``` cosign generate-key-pair [flags] ``` ### Examples ``` cosign generate-key-pair [--kms KMSPATH] # generate key-pair and write to cosign.key and cosign.pub files cosign generate-key-pair # generate key-pair and write to custom named my-name.key and my-name.pub files cosign generate-key-pair --output-key-prefix my-name # generate a key-pair in Azure Key Vault cosign generate-key-pair --kms azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # generate a key-pair in AWS KMS cosign generate-key-pair --kms awskms://[ENDPOINT]/[ID/ALIAS/ARN] # generate a key-pair in Google Cloud KMS cosign generate-key-pair --kms gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # generate a key-pair in Hashicorp Vault cosign generate-key-pair --kms hashivault://[KEY] # generate a key-pair in Kubernetes Secret cosign generate-key-pair k8s://[NAMESPACE]/[NAME] # generate a key-pair in GitHub cosign generate-key-pair github://[OWNER]/[PROJECT_NAME] # generate a key-pair in GitLab with project name cosign generate-key-pair gitlab://[OWNER]/[PROJECT_NAME] # generate a key-pair in GitLab with project id cosign generate-key-pair gitlab://[PROJECT_ID] CAVEATS: This command interactively prompts for a password. You can use the COSIGN_PASSWORD environment variable to provide one. ``` ### Options ``` -h, --help help for generate-key-pair --kms string create key pair in KMS service to use for signing --output-key-prefix cosign name used for generated .pub and .key files (defaults to cosign) (default "cosign") ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_generate.md000066400000000000000000000064741477503325500172510ustar00rootroot00000000000000## cosign generate Generates (unsigned) signature payloads from the supplied container image. ### Synopsis Generates an unsigned payload from the supplied container image and flags. This payload matches the one generated by the "cosign sign" command and can be used if you need to sign payloads with your own tooling or algorithms. ``` cosign generate [flags] ``` ### Examples ``` cosign generate [--a key=value] # Generate a simple payload for an image cosign generate # Generate a payload with specific annotations cosign generate -a foo=bar # Use this payload in another tool gpg --output image.sig --detach-sig <(cosign generate ) ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing -a, --annotations strings extra key=value pairs to sign --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -h, --help help for generate --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_import-key-pair.md000066400000000000000000000025521477503325500205010ustar00rootroot00000000000000## cosign import-key-pair Imports a PEM-encoded RSA or EC private key. ### Synopsis Imports a PEM-encoded RSA or EC private key for signing. ``` cosign import-key-pair [flags] ``` ### Examples ``` cosign import-key-pair --key openssl.key --output-key-prefix my-key # import PEM-encoded RSA or EC private key and write to import-cosign.key and import-cosign.pub files cosign import-key-pair --key # import PEM-encoded RSA or EC private key and write to my-key.key and my-key.pub files cosign import-key-pair --key --output-key-prefix my-key CAVEATS: This command interactively prompts for a password. You can use the COSIGN_PASSWORD environment variable to provide one. ``` ### Options ``` -h, --help help for import-key-pair -k, --key string import key pair to use for signing -o, --output-key-prefix string name used for outputted key pairs (default "import-cosign") -y, --yes skip confirmation prompts for overwriting existing key ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_initialize.md000066400000000000000000000044261477503325500176130ustar00rootroot00000000000000## cosign initialize Initializes SigStore root to retrieve trusted certificate and key targets for verification. ### Synopsis Initializes SigStore root to retrieve trusted certificate and key targets for verification. The following options are used by default: - The current trusted Sigstore TUF root is embedded inside cosign at the time of release. - SigStore remote TUF repository is pulled from the CDN mirror at tuf-repo-cdn.sigstore.dev. To provide an out-of-band trusted initial root.json, use the -root flag with a file or URL reference. This will enable you to point cosign to a separate TUF root. Any updated TUF repository will be written to $HOME/.sigstore/root/. Trusted keys and certificate used in cosign verification (e.g. verifying Fulcio issued certificates with Fulcio root CA) are pulled form the trusted metadata. ``` cosign initialize [flags] ``` ### Examples ``` cosign initialize --mirror --out # initialize root with distributed root keys, default mirror, and default out path. cosign initialize # initialize with an out-of-band root key file, using the default mirror. cosign initialize --root # initialize with an out-of-band root key file and custom repository mirror. cosign initialize --mirror --root # initialize with an out-of-band root key file and custom repository mirror while verifying root checksum. cosign initialize --mirror --root --root-checksum ``` ### Options ``` -h, --help help for initialize --mirror string GCS bucket to a SigStore TUF repository, or HTTP(S) base URL, or file:/// for local filestore remote (air-gap) (default "https://tuf-repo-cdn.sigstore.dev") --root string path to trusted initial root. defaults to embedded root --root-checksum string checksum of the initial root, required if root is downloaded via http(s). expects sha256 by default, can be changed to sha512 by providing sha512: ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_load.md000066400000000000000000000056011477503325500163650ustar00rootroot00000000000000## cosign load Load a signed image on disk to a remote registry ### Synopsis Load a signed image on disk to a remote registry ``` cosign load [flags] ``` ### Examples ``` cosign load --dir ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --dir string path to directory where the signed image is stored on disk -h, --help help for load --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_login.md000066400000000000000000000013141477503325500165530ustar00rootroot00000000000000## cosign login Log in to a registry ``` cosign login [OPTIONS] [SERVER] [flags] ``` ### Examples ``` # Log in to reg.example.com cosign login reg.example.com -u AzureDiamond -p hunter2 ``` ### Options ``` -h, --help help for login -p, --password string Password --password-stdin Take the password from stdin -u, --username string Username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_manifest.md000066400000000000000000000011471477503325500172550ustar00rootroot00000000000000## cosign manifest Provides utilities for discovering images in and performing operations on Kubernetes manifests ### Options ``` -h, --help help for manifest ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign manifest verify](cosign_manifest_verify.md) - Verify all signatures of images specified in the manifest cosign-2.5.0/doc/cosign_manifest_verify.md000066400000000000000000000273371477503325500206520ustar00rootroot00000000000000## cosign manifest verify Verify all signatures of images specified in the manifest ### Synopsis Verify all signature of images in a Kubernetes resource manifest by checking claims against the transparency log. ``` cosign manifest verify [flags] ``` ### Examples ``` cosign manifest verify --key || # verify cosign claims and signing certificates on images in the manifest cosign manifest verify # additionally verify specified annotations cosign manifest verify -a key1=val1 -a key2=val2 # verify images with public key cosign manifest verify --key cosign.pub # verify images with public key provided by URL cosign manifest verify --key https://host.for/ # verify images with public key stored in Azure Key Vault cosign manifest verify --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # verify images with public key stored in AWS KMS cosign manifest verify --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # verify images with public key stored in Google Cloud KMS cosign manifest verify --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # verify images with public key stored in Hashicorp Vault cosign manifest verify --key hashivault://[KEY] ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing -a, --annotations strings extra key=value pairs to sign --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. The flag is optional and must be used together with --ca-roots, conflicts with --certificate-chain. --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --key string path to the public key file, KMS URI or Kubernetes Secret --local-image whether the specified image is a path to an image saved locally via 'cosign save' --max-workers int the amount of maximum workers for parallel executions (default 10) --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification -o, --output string output format for the signing image information (json|text) (default "json") --payload string payload path or remote URL --private-infrastructure skip transparency log verification when verifying artifacts in a privately deployed infrastructure --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --sct string path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs. --signature string signature content or path or remote URL --signature-digest-algorithm string digest algorithm to use when processing a signature (sha224|sha256|sha384|sha512) (default "sha256") --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp --trusted-root string Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set. --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign manifest](cosign_manifest.md) - Provides utilities for discovering images in and performing operations on Kubernetes manifests cosign-2.5.0/doc/cosign_piv-tool.md000066400000000000000000000023731477503325500172220ustar00rootroot00000000000000## cosign piv-tool Provides utilities for managing a hardware token ### Options ``` -h, --help help for piv-tool -f, --no-input skip warnings and confirmations ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign piv-tool attestation](cosign_piv-tool_attestation.md) - attestation contains commands to manage a hardware token * [cosign piv-tool generate-key](cosign_piv-tool_generate-key.md) - generate-key generates a new signing key on the hardware token * [cosign piv-tool reset](cosign_piv-tool_reset.md) - reset resets the hardware token completely * [cosign piv-tool set-management-key](cosign_piv-tool_set-management-key.md) - sets the management key of a hardware token * [cosign piv-tool set-pin](cosign_piv-tool_set-pin.md) - sets the PIN on a hardware token * [cosign piv-tool set-puk](cosign_piv-tool_set-puk.md) - sets the PUK on a hardware token * [cosign piv-tool unblock](cosign_piv-tool_unblock.md) - unblocks the hardware token, sets a new PIN cosign-2.5.0/doc/cosign_piv-tool_attestation.md000066400000000000000000000014361477503325500216400ustar00rootroot00000000000000## cosign piv-tool attestation attestation contains commands to manage a hardware token ``` cosign piv-tool attestation [flags] ``` ### Options ``` -h, --help help for attestation -o, --output string format to output attestation information in. (text|json) (default "text") --slot string Slot to use for generated key (authentication|signature|card-authentication|key-management) ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token cosign-2.5.0/doc/cosign_piv-tool_generate-key.md000066400000000000000000000020321477503325500216520ustar00rootroot00000000000000## cosign piv-tool generate-key generate-key generates a new signing key on the hardware token ``` cosign piv-tool generate-key [flags] ``` ### Options ``` -h, --help help for generate-key --management-key string management key, uses default if empty --pin-policy string PIN policy for slot (never|once|always) --random-management-key if set to true, generates a new random management key and deletes it after --slot string Slot to use for generated key (authentication|signature|card-authentication|key-management) --touch-policy string Touch policy for slot (never|always|cached) ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token cosign-2.5.0/doc/cosign_piv-tool_reset.md000066400000000000000000000010371477503325500204200ustar00rootroot00000000000000## cosign piv-tool reset reset resets the hardware token completely ``` cosign piv-tool reset [flags] ``` ### Options ``` -h, --help help for reset ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token cosign-2.5.0/doc/cosign_piv-tool_set-management-key.md000066400000000000000000000015341477503325500227730ustar00rootroot00000000000000## cosign piv-tool set-management-key sets the management key of a hardware token ``` cosign piv-tool set-management-key [flags] ``` ### Options ``` -h, --help help for set-management-key --new-key string new management key, uses default if empty --old-key string existing management key, uses default if empty --random-management-key if set to true, generates a new random management key and deletes it after ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token cosign-2.5.0/doc/cosign_piv-tool_set-pin.md000066400000000000000000000012321477503325500206520ustar00rootroot00000000000000## cosign piv-tool set-pin sets the PIN on a hardware token ``` cosign piv-tool set-pin [flags] ``` ### Options ``` -h, --help help for set-pin --new-pin string new PIN, uses default if empty --old-pin string existing PIN, uses default if empty ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token cosign-2.5.0/doc/cosign_piv-tool_set-puk.md000066400000000000000000000012321477503325500206630ustar00rootroot00000000000000## cosign piv-tool set-puk sets the PUK on a hardware token ``` cosign piv-tool set-puk [flags] ``` ### Options ``` -h, --help help for set-puk --new-puk string new PUK, uses default if empty --old-puk string existing PUK, uses default if empty ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token cosign-2.5.0/doc/cosign_piv-tool_unblock.md000066400000000000000000000012451477503325500207340ustar00rootroot00000000000000## cosign piv-tool unblock unblocks the hardware token, sets a new PIN ``` cosign piv-tool unblock [flags] ``` ### Options ``` -h, --help help for unblock --new-PIN string new PIN, uses default if empty --puk string existing PUK, uses default if empty ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token cosign-2.5.0/doc/cosign_pkcs11-tool.md000066400000000000000000000014431477503325500175230ustar00rootroot00000000000000## cosign pkcs11-tool Provides utilities for retrieving information from a PKCS11 token. ### Options ``` -h, --help help for pkcs11-tool -f, --no-input skip warnings and confirmations ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign pkcs11-tool list-keys-uris](cosign_pkcs11-tool_list-keys-uris.md) - list-keys-uris lists URIs of all keys in a PKCS11 token * [cosign pkcs11-tool list-tokens](cosign_pkcs11-tool_list-tokens.md) - list-tokens lists all PKCS11 tokens linked to a PKCS11 module cosign-2.5.0/doc/cosign_pkcs11-tool_list-keys-uris.md000066400000000000000000000015411477503325500225060ustar00rootroot00000000000000## cosign pkcs11-tool list-keys-uris list-keys-uris lists URIs of all keys in a PKCS11 token ``` cosign pkcs11-tool list-keys-uris [flags] ``` ### Options ``` -h, --help help for list-keys-uris --module-path string absolute path to the PKCS11 module --pin string pin of the PKCS11 slot, uses environment variable COSIGN_PKCS11_PIN if empty --slot-id uint id of the PKCS11 slot, uses 0 if empty ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign pkcs11-tool](cosign_pkcs11-tool.md) - Provides utilities for retrieving information from a PKCS11 token. cosign-2.5.0/doc/cosign_pkcs11-tool_list-tokens.md000066400000000000000000000012601477503325500220540ustar00rootroot00000000000000## cosign pkcs11-tool list-tokens list-tokens lists all PKCS11 tokens linked to a PKCS11 module ``` cosign pkcs11-tool list-tokens [flags] ``` ### Options ``` -h, --help help for list-tokens --module-path string absolute path to the PKCS11 module ``` ### Options inherited from parent commands ``` -f, --no-input skip warnings and confirmations --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign pkcs11-tool](cosign_pkcs11-tool.md) - Provides utilities for retrieving information from a PKCS11 token. cosign-2.5.0/doc/cosign_public-key.md000066400000000000000000000036231477503325500175140ustar00rootroot00000000000000## cosign public-key Gets a public key from the key-pair. ### Synopsis Gets a public key from the key-pair and writes to a specified file. By default, it will write to standard out. ``` cosign public-key [flags] ``` ### Examples ``` # extract public key from private key to a specified out file. cosign public-key --key --outfile # extract public key from URL. cosign public-key --key https://host.for/ --outfile # extract public key from Azure Key Vault cosign public-key --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # extract public key from AWS KMS cosign public-key --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # extract public key from Google Cloud KMS cosign public-key --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # extract public key from Hashicorp Vault KMS cosign public-key --key hashivault://[KEY] # extract public key from GitLab with project name cosign public-key --key gitlab://[OWNER]/[PROJECT_NAME] # extract public key from GitLab with project id cosign public-key --key gitlab://[PROJECT_ID] ``` ### Options ``` -h, --help help for public-key --key string path to the private key file, KMS URI or Kubernetes Secret --outfile string path to a payload file to use rather than generating one --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_save.md000066400000000000000000000057161477503325500164130ustar00rootroot00000000000000## cosign save Save the container image and associated signatures to disk at the specified directory. ### Synopsis Save the container image and associated signatures to disk at the specified directory. ``` cosign save [flags] ``` ### Examples ``` cosign save --dir ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --dir string path to dir where the signed image should be stored on disk -h, --help help for save --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_sign-blob.md000066400000000000000000000116461477503325500173300ustar00rootroot00000000000000## cosign sign-blob Sign the supplied blob, outputting the base64-encoded signature to stdout. ``` cosign sign-blob [flags] ``` ### Examples ``` cosign sign-blob --key | # sign a blob with Google sign-in (experimental) cosign sign-blob --output-signature --output-certificate # sign a blob with a local key pair file cosign sign-blob --key cosign.key # sign a blob with a key stored in an environment variable cosign sign-blob --key env://[ENV_VAR] # sign a blob with a key pair stored in Azure Key Vault cosign sign-blob --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # sign a blob with a key pair stored in AWS KMS cosign sign-blob --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # sign a blob with a key pair stored in Google Cloud KMS cosign sign-blob --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # sign a blob with a key pair stored in Hashicorp Vault cosign sign-blob --key hashivault://[KEY] ``` ### Options ``` --b64 whether to base64 encode the output (default true) --bundle string write everything required to verify the blob to a FILE --fulcio-auth-flow string fulcio interactive oauth2 flow to use for certificate from fulcio. Defaults to determining the flow based on the runtime environment. (options) normal|device|token|client_credentials --fulcio-url string address of sigstore PKI server (default "https://fulcio.sigstore.dev") -h, --help help for sign-blob --identity-token string identity token to use for certificate from fulcio. the token or a path to a file containing the token is accepted. --insecure-skip-verify skip verifying fulcio published to the SCT (this should only be used for testing). --issue-certificate issue a code signing certificate from Fulcio, even if a key is provided --key string path to the private key file, KMS URI or Kubernetes Secret --new-bundle-format output bundle in new format that contains all verification material --oidc-client-id string OIDC client ID for application (default "sigstore") --oidc-client-secret-file string Path to file containing OIDC client secret for application --oidc-disable-ambient-providers Disable ambient OIDC providers. When true, ambient credentials will not be read --oidc-issuer string OIDC provider to be used to issue ID token (default "https://oauth2.sigstore.dev/auth") --oidc-provider string Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github-actions, filesystem, buildkite-agent] --oidc-redirect-url string OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. --output string write the signature to FILE --output-certificate string write the certificate to FILE --output-signature string write the signature to FILE --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --rfc3161-timestamp string write the RFC3161 timestamp to a file --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-client-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server --timestamp-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server --timestamp-client-key string path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server --timestamp-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) -y, --yes skip confirmation prompts for non-destructive operations ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_sign.md000066400000000000000000000270751477503325500164170ustar00rootroot00000000000000## cosign sign Sign the supplied container image. ### Synopsis Sign the supplied container image. Make sure to sign the image by its digest (@sha256:...) rather than by tag (:latest) so that you actually sign what you think you're signing! This prevents race conditions or (worse) malicious tampering. ``` cosign sign [flags] ``` ### Examples ``` cosign sign --key | [--payload ] [-a key=value] [--upload=true|false] [-f] [-r] # sign a container image with the Sigstore OIDC flow cosign sign # sign a container image with a local key pair file cosign sign --key cosign.key # sign a multi-arch container image AND all referenced, discrete images cosign sign --key cosign.key --recursive # sign a container image and add annotations cosign sign --key cosign.key -a key1=value1 -a key2=value2 # sign a container image with a key stored in an environment variable cosign sign --key env://[ENV_VAR] # sign a container image with a key pair stored in Azure Key Vault cosign sign --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] # sign a container image with a key pair stored in AWS KMS cosign sign --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] # sign a container image with a key pair stored in Google Cloud KMS cosign sign --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY]/versions/[VERSION] # sign a container image with a key pair stored in Hashicorp Vault cosign sign --key hashivault://[KEY] # sign a container image with a key pair stored in a Kubernetes secret cosign sign --key k8s://[NAMESPACE]/[KEY] # sign a container image with a key, attaching a certificate and certificate chain cosign sign --key cosign.key --cert cosign.crt --cert-chain chain.crt # sign a container in a registry which does not fully support OCI media types COSIGN_DOCKER_MEDIA_TYPES=1 cosign sign --key cosign.key legacy-registry.example.com/my/image@ # sign a container image and upload to the transparency log cosign sign --key cosign.key # sign a container image and skip uploading to the transparency log cosign sign --key cosign.key --tlog-upload=false # sign a container image by manually setting the container image identity cosign sign --sign-container-identity # sign a container image and honor the creation timestamp of the signature cosign sign --key cosign.key --record-creation-timestamp ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing -a, --annotations strings extra key=value pairs to sign --attachment string DEPRECATED, related image attachment to sign (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --certificate string path to the X.509 certificate in PEM format to include in the OCI Signature --certificate-chain string path to a list of CA X.509 certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Included in the OCI Signature --fulcio-auth-flow string fulcio interactive oauth2 flow to use for certificate from fulcio. Defaults to determining the flow based on the runtime environment. (options) normal|device|token|client_credentials --fulcio-url string address of sigstore PKI server (default "https://fulcio.sigstore.dev") -h, --help help for sign --identity-token string identity token to use for certificate from fulcio. the token or a path to a file containing the token is accepted. --insecure-skip-verify skip verifying fulcio published to the SCT (this should only be used for testing). --issue-certificate issue a code signing certificate from Fulcio, even if a key is provided --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --key string path to the private key file, KMS URI or Kubernetes Secret --oidc-client-id string OIDC client ID for application (default "sigstore") --oidc-client-secret-file string Path to file containing OIDC client secret for application --oidc-disable-ambient-providers Disable ambient OIDC providers. When true, ambient credentials will not be read --oidc-issuer string OIDC provider to be used to issue ID token (default "https://oauth2.sigstore.dev/auth") --oidc-provider string Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github-actions, filesystem, buildkite-agent] --oidc-redirect-url string OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. --output-certificate string write the certificate to FILE --output-payload string write the signed payload to FILE --output-signature string write the signature to FILE --payload string path to a payload file to use rather than generating one --record-creation-timestamp set the createdAt timestamp in the signature artifact to the time it was created; by default, cosign sets this to the zero value -r, --recursive if a multi-arch image is specified, additionally sign each discrete image --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-referrers-mode registryReferrersMode mode for fetching references from the registry. allowed: legacy, oci-1-1 --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --sign-container-identity string manually set the .critical.docker-reference field for the signed identity, which is useful when image proxies are being used where the pull reference should match the signature --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-client-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server --timestamp-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server --timestamp-client-key string path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server --timestamp-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr --tlog-upload whether or not to upload to the tlog (default true) --upload whether to upload the signature (default true) -y, --yes skip confirmation prompts for non-destructive operations ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_tree.md000066400000000000000000000053021477503325500164030ustar00rootroot00000000000000## cosign tree Display supply chain security related artifacts for an image such as signatures, SBOMs and attestations ``` cosign tree [flags] ``` ### Examples ``` cosign tree ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -h, --help help for tree --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_triangulate.md000066400000000000000000000057151477503325500177730ustar00rootroot00000000000000## cosign triangulate Outputs the located cosign image reference. This is the location where cosign stores the specified artifact type. ``` cosign triangulate [flags] ``` ### Examples ``` cosign triangulate ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -h, --help help for triangulate --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --type string related attachment to triangulate (attestation|sbom|signature|digest), default signature (sbom is deprecated) (default "signature") ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_trusted-root.md000066400000000000000000000012001477503325500201100ustar00rootroot00000000000000## cosign trusted-root Interact with a Sigstore protobuf trusted root ### Synopsis Tools for interacting with a Sigstore protobuf trusted root ### Options ``` -h, --help help for trusted-root ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign trusted-root create](cosign_trusted-root_create.md) - Create a Sigstore protobuf trusted root cosign-2.5.0/doc/cosign_trusted-root_create.md000066400000000000000000000033521477503325500214450ustar00rootroot00000000000000## cosign trusted-root create Create a Sigstore protobuf trusted root ### Synopsis Create a Sigstore protobuf trusted root by supplying verification material ``` cosign trusted-root create [flags] ``` ### Options ``` --certificate-chain stringArray path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. --ctfe-key stringArray path to a PEM-encoded public key used by certificate authority for certificate transparency log. --ctfe-start-time stringArray RFC 3339 string describing validity start time for key use by certificate transparency log. -h, --help help for create --out string path to output trusted root --rekor-key stringArray path to a PEM-encoded public key used by transparency log like Rekor. --rekor-start-time stringArray RFC 3339 string describing validity start time for key use by transparency log like Rekor. --timestamp-certificate-chain stringArray path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign trusted-root](cosign_trusted-root.md) - Interact with a Sigstore protobuf trusted root cosign-2.5.0/doc/cosign_upload.md000066400000000000000000000012551477503325500167330ustar00rootroot00000000000000## cosign upload Provides utilities for uploading artifacts to a registry ### Options ``` -h, --help help for upload ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign upload blob](cosign_upload_blob.md) - Upload one or more blobs to the supplied container image address. * [cosign upload wasm](cosign_upload_wasm.md) - Upload a wasm module to the supplied container image reference cosign-2.5.0/doc/cosign_upload_blob.md000066400000000000000000000100261477503325500177250ustar00rootroot00000000000000## cosign upload blob Upload one or more blobs to the supplied container image address. ``` cosign upload blob [flags] ``` ### Examples ``` cosign upload blob -f # upload a blob named foo to the location specified by cosign upload blob -f foo # upload a blob named foo to the location specified by , setting the os field to "MYOS". cosign upload blob -f foo:MYOS # upload a blob named foo to the location specified by , setting the os field to "MYOS" and the platform field to "MYPLATFORM". cosign upload blob -f foo:MYOS/MYPLATFORM # upload two blobs named foo-darwin and foo-linux to the location specified by , setting the os fields cosign upload blob -f foo-darwin:darwin -f foo-linux:linux # upload a blob named foo to the location specified by , setting annotations mykey=myvalue. cosign upload blob -a mykey=myvalue -f foo # upload two blobs named foo-darwin and foo-linux to the location specified by , setting annotations cosign upload blob -a mykey=myvalue -a myotherkey="my other value" -f foo-darwin:darwin -f foo-linux:linux ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing -a, --annotation stringToString annotations to set (default []) --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --ct string content type to set -f, --files strings :[platform/arch] -h, --help help for blob --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign upload](cosign_upload.md) - Provides utilities for uploading artifacts to a registry cosign-2.5.0/doc/cosign_upload_wasm.md000066400000000000000000000054731477503325500177700ustar00rootroot00000000000000## cosign upload wasm Upload a wasm module to the supplied container image reference ``` cosign upload wasm [flags] ``` ### Examples ``` cosign upload wasm -f foo.wasm ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] -f, --file string path to the wasm file to upload -h, --help help for wasm --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign upload](cosign_upload.md) - Provides utilities for uploading artifacts to a registry cosign-2.5.0/doc/cosign_verify-attestation.md000066400000000000000000000274311477503325500213140ustar00rootroot00000000000000## cosign verify-attestation Verify an attestation on the supplied container image ### Synopsis Verify an attestation on an image by checking the claims against the transparency log. ``` cosign verify-attestation [flags] ``` ### Examples ``` cosign verify-attestation --key || [ ...] # verify cosign attestations on the image against the transparency log cosign verify-attestation # verify multiple images cosign verify-attestation ... # additionally verify specified annotations cosign verify-attestation -a key1=val1 -a key2=val2 # verify image with public key cosign verify-attestation --key cosign.pub # verify image attestations with an on-disk signed image from 'cosign save' cosign verify-attestation --key cosign.pub --local-image # verify image with public key provided by URL cosign verify-attestation --key https://host.for/ # verify image with public key stored in Google Cloud KMS cosign verify-attestation --key gcpkms://projects//locations/global/keyRings//cryptoKeys/ # verify image with public key stored in Hashicorp Vault cosign verify-attestation --key hashivault:/// # verify image with public key stored in GitLab with project name cosign verify-attestation --key gitlab://[OWNER]/[PROJECT_NAME] # verify image with public key stored in GitLab with project id cosign verify-attestation --key gitlab://[PROJECT_ID] # verify image with public key and validate attestation based on Rego policy cosign verify-attestation --key cosign.pub --type --policy # verify image with public key and validate attestation based on CUE policy cosign verify-attestation --key cosign.pub --type --policy ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. The flag is optional and must be used together with --ca-roots, conflicts with --certificate-chain. --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-attestation --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --key string path to the public key file, KMS URI or Kubernetes Secret --local-image whether the specified image is a path to an image saved locally via 'cosign save' --max-workers int the amount of maximum workers for parallel executions (default 10) --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification -o, --output string output format for the signing image information (json|text) (default "json") --policy strings specify CUE or Rego files with policies to be used for validation --private-infrastructure skip transparency log verification when verifying artifacts in a privately deployed infrastructure --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --sct string path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs. --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp --trusted-root string Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set. --type string specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom") --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_verify-blob-attestation.md000066400000000000000000000163001477503325500222210ustar00rootroot00000000000000## cosign verify-blob-attestation Verify an attestation on the supplied blob ### Synopsis Verify an attestation on the supplied blob input using the specified key reference. You may specify either a key or a kms reference to verify against. The signature may be specified as a path to a file or a base64 encoded string. The blob may be specified as a path to a file. ``` cosign verify-blob-attestation [flags] ``` ### Examples ``` cosign verify-blob-attestation (--key ||) --signature [path to BLOB] # Verify a simple blob attestation with a DSSE style signature cosign verify-blob-attestation --key cosign.pub (--signature |)[path to BLOB] ``` ### Options ``` --bundle string path to bundle FILE --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. The flag is optional and must be used together with --ca-roots, conflicts with --certificate-chain. --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims if true, verifies the provided blob's sha256 digest exists as an in-toto subject within the attestation. If false, only the DSSE envelope is verified. (default true) --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-blob-attestation --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log --key string path to the public key file, KMS URI or Kubernetes Secret --max-workers int the amount of maximum workers for parallel executions (default 10) --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification --private-infrastructure skip transparency log verification when verifying artifacts in a privately deployed infrastructure --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --rfc3161-timestamp string path to RFC3161 timestamp FILE --sct string path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs. --signature string path to base64-encoded signature over attestation in DSSE format --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp --trusted-root string Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set. --type string specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom") --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_verify-blob.md000066400000000000000000000207041477503325500176670ustar00rootroot00000000000000## cosign verify-blob Verify a signature on the supplied blob ### Synopsis Verify a signature on the supplied blob input using the specified key reference. You may specify either a key, a certificate or a kms reference to verify against. If you use a key or a certificate, you must specify the path to them on disk. The signature may be specified as a path to a file or a base64 encoded string. The blob may be specified as a path to a file or - for stdin. ``` cosign verify-blob [flags] ``` ### Examples ``` cosign verify-blob (--key ||)|(--certificate ) --signature # Verify a simple blob and message cosign verify-blob --key cosign.pub (--signature | msg) # Verify a signature with certificate and CA certificate chain cosign verify-blob --certificate cert.pem --certificate-chain certchain.pem --signature $sig # Verify a signature with CA roots and optional intermediate certificates cosign verify-blob --certificate cert.pem --ca-roots caroots.pem [--ca-intermediates caintermediates.pem] --signature $sig # Verify a signature from an environment variable cosign verify-blob --key cosign.pub --signature $sig msg # verify a signature with public key provided by URL cosign verify-blob --key https://host.for/ --signature $sig msg # verify a signature with signature and key provided by URL cosign verify-blob --key https://host.for/ --signature https://example.com/ # Verify a signature against Azure Key Vault cosign verify-blob --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] --signature $sig # Verify a signature against AWS KMS cosign verify-blob --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] --signature $sig # Verify a signature against Google Cloud KMS cosign verify-blob --key gcpkms://projects/[PROJECT ID]/locations/[LOCATION]/keyRings/[KEYRING]/cryptoKeys/[KEY] --signature $sig # Verify a signature against Hashicorp Vault cosign verify-blob --key hashivault://[KEY] --signature $sig # Verify a signature against GitLab with project name cosign verify-blob --key gitlab://[OWNER]/[PROJECT_NAME] --signature $sig # Verify a signature against GitLab with project id cosign verify-blob --key gitlab://[PROJECT_ID] --signature $sig # Verify a signature against a certificate cosign verify-blob --certificate --signature $sig ``` ### Options ``` --bundle string path to bundle FILE --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. The flag is optional and must be used together with --ca-roots, conflicts with --certificate-chain. --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify-blob --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log --key string path to the public key file, KMS URI or Kubernetes Secret --max-workers int the amount of maximum workers for parallel executions (default 10) --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification --private-infrastructure skip transparency log verification when verifying artifacts in a privately deployed infrastructure --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --rfc3161-timestamp string path to RFC3161 timestamp FILE --sct string path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs. --signature string signature content or path or remote URL --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp --trusted-root string Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set. --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_verify.md000066400000000000000000000311761477503325500167600ustar00rootroot00000000000000## cosign verify Verify a signature on the supplied container image ### Synopsis Verify signature and annotations on an image by checking the claims against the transparency log. ``` cosign verify [flags] ``` ### Examples ``` cosign verify --key || [ ...] # verify cosign claims and signing certificates on the image with the transparency log cosign verify # verify multiple images cosign verify ... # additionally verify specified annotations cosign verify -a key1=val1 -a key2=val2 # verify image with an on-disk public key cosign verify --key cosign.pub # verify image with an on-disk public key, manually specifying the # signature digest algorithm cosign verify --key cosign.pub --signature-digest-algorithm sha512 # verify image with an on-disk signed image from 'cosign save' cosign verify --key cosign.pub --local-image # verify image with local certificate and certificate chain cosign verify --cert cosign.crt --cert-chain chain.crt # verify image with local certificate and certificate bundles of CA roots # and (optionally) CA intermediates cosign verify --cert cosign.crt --ca-roots ca-roots.pem --ca-intermediates ca-intermediates.pem # verify image using keyless verification with the given certificate # chain and identity parameters, without Fulcio roots (for BYO PKI): cosign verify --cert-chain chain.crt --certificate-oidc-issuer https://issuer.example.com --certificate-identity foo@example.com # verify image with public key provided by URL cosign verify --key https://host.for/[FILE] # verify image with a key stored in an environment variable cosign verify --key env://[ENV_VAR] # verify image with public key stored in Google Cloud KMS cosign verify --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] # verify image with public key stored in Hashicorp Vault cosign verify --key hashivault://[KEY] # verify image with public key stored in a Kubernetes secret cosign verify --key k8s://[NAMESPACE]/[KEY] # verify image with public key stored in GitLab with project name cosign verify --key gitlab://[OWNER]/[PROJECT_NAME] # verify image with public key stored in GitLab with project id cosign verify --key gitlab://[PROJECT_ID] ``` ### Options ``` --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing -a, --annotations strings extra key=value pairs to sign --attachment string DEPRECATED, related image attachment to verify (sbom), default none --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --ca-intermediates string path to a file of intermediate CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. The flag is optional and must be used together with --ca-roots, conflicts with --certificate-chain. --ca-roots string path to a bundle file of CA certificates in PEM format which will be needed when building the certificate chains for the signing certificate. Conflicts with --certificate-chain. --certificate string path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed. --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates. --certificate-github-workflow-name string contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow. --certificate-github-workflow-ref string contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon. --certificate-github-workflow-repository string contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon --certificate-github-workflow-sha string contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon. --certificate-github-workflow-trigger string contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run --certificate-identity string The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-identity-regexp string A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows. --certificate-oidc-issuer string The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --certificate-oidc-issuer-regexp string A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows. --check-claims whether to check the claims found (default true) --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour -h, --help help for verify --insecure-ignore-sct when set, verification will not check that a certificate contains an embedded SCT, a proof of inclusion in a certificate transparency log --insecure-ignore-tlog ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts cannot be publicly verified when not included in a log --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --key string path to the public key file, KMS URI or Kubernetes Secret --local-image whether the specified image is a path to an image saved locally via 'cosign save' --max-workers int the amount of maximum workers for parallel executions (default 10) --new-bundle-format expect the signature/attestation to be packaged in a Sigstore bundle --offline only allow offline verification -o, --output string output format for the signing image information (json|text) (default "json") --payload string payload path or remote URL --private-infrastructure skip transparency log verification when verifying artifacts in a privately deployed infrastructure --registry-cacert string path to the X.509 CA certificate file in PEM format to be used for the connection to the registry --registry-client-cert string path to the X.509 certificate file in PEM format to be used for the connection to the registry --registry-client-key string path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry --registry-password string registry basic auth password --registry-server-name string SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry --registry-token string registry bearer auth token --registry-username string registry basic auth username --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --sct string path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. If a certificate contains an SCT, verification will check both the detached and embedded SCTs. --signature string signature content or path or remote URL --signature-digest-algorithm string digest algorithm to use when processing a signature (sha224|sha256|sha384|sha512) (default "sha256") --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) --timestamp-certificate-chain string path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp --trusted-root string Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set. --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/doc/cosign_version.md000066400000000000000000000007601477503325500171340ustar00rootroot00000000000000## cosign version Prints the version ``` cosign version [flags] ``` ### Options ``` -h, --help help for version --json print JSON instead of text ``` ### Options inherited from parent commands ``` --output-file string log output to a file -t, --timeout duration timeout for commands (default 3m0s) -d, --verbose log debug output ``` ### SEE ALSO * [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. cosign-2.5.0/go.mod000066400000000000000000000346371477503325500141360ustar00rootroot00000000000000module github.com/sigstore/cosign/v2 go 1.23.4 require ( cuelang.org/go v0.12.1 github.com/ThalesIgnite/crypto11 v1.2.5 github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1 github.com/buildkite/agent/v3 v3.95.1 github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936 github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 github.com/dustin/go-humanize v1.0.1 github.com/go-jose/go-jose/v3 v3.0.4 github.com/go-openapi/runtime v0.28.0 github.com/go-openapi/strfmt v0.23.0 github.com/go-openapi/swag v0.23.1 github.com/go-piv/piv-go/v2 v2.3.0 github.com/google/certificate-transparency-go v1.3.1 github.com/google/go-cmp v0.7.0 github.com/google/go-containerregistry v0.20.3 github.com/google/go-github/v55 v55.0.0 github.com/in-toto/in-toto-golang v0.9.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/manifoldco/promptui v0.9.0 github.com/miekg/pkcs11 v1.1.1 github.com/mitchellh/go-wordwrap v1.0.1 github.com/moby/term v0.5.2 github.com/mozillazg/docker-credential-acr-helper v0.4.0 github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 github.com/open-policy-agent/opa v1.1.0 github.com/secure-systems-lab/go-securesystemslib v0.9.0 github.com/sigstore/fulcio v1.6.6 github.com/sigstore/protobuf-specs v0.4.1 github.com/sigstore/rekor v1.3.9 github.com/sigstore/sigstore v1.9.1 github.com/sigstore/sigstore-go v0.7.1 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.1 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.1 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.1 github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.1 github.com/sigstore/timestamp-authority v1.2.5 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.20.1 github.com/spiffe/go-spiffe/v2 v2.5.0 github.com/stretchr/testify v1.10.0 github.com/theupdateframework/go-tuf/v2 v2.0.2 github.com/transparency-dev/merkle v0.0.2 github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 gitlab.com/gitlab-org/api/client-go v0.127.0 golang.org/x/crypto v0.37.0 golang.org/x/oauth2 v0.29.0 golang.org/x/sync v0.13.0 golang.org/x/term v0.31.0 google.golang.org/api v0.227.0 google.golang.org/protobuf v1.36.6 k8s.io/api v0.28.3 k8s.io/apimachinery v0.28.3 k8s.io/client-go v0.28.3 k8s.io/utils v0.0.0-20241210054802-24370beab758 sigs.k8s.io/release-utils v0.11.1 ) require ( cloud.google.com/go v0.118.3 // indirect cloud.google.com/go/auth v0.15.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/iam v1.4.1 // indirect cloud.google.com/go/kms v1.21.1 // indirect cloud.google.com/go/longrunning v0.6.5 // indirect cuelabs.dev/go/oci/ociregistry v0.0.0-20241125120445-2c00c104c6e1 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect github.com/agnivade/levenshtein v1.2.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect github.com/alibabacloud-go/cr-20181201 v1.0.10 // indirect github.com/alibabacloud-go/darabonba-openapi v0.2.1 // indirect github.com/alibabacloud-go/debug v1.0.0 // indirect github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect github.com/alibabacloud-go/openapi-util v0.1.0 // indirect github.com/alibabacloud-go/tea v1.2.1 // indirect github.com/alibabacloud-go/tea-utils v1.4.5 // indirect github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/aliyun/credentials-go v1.3.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect github.com/aws/aws-sdk-go-v2/config v1.29.10 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.63 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3 // indirect github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect github.com/aws/smithy-go v1.22.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/buildkite/go-pipeline v0.13.3 // indirect github.com/buildkite/interpolate v0.1.5 // indirect github.com/buildkite/roko v1.3.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/coreos/go-oidc/v3 v3.12.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/docker/cli v27.5.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/proto v1.13.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.1 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/validate v0.24.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/tink/go v1.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.5 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/vault/api v1.16.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/in-toto/attestation v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/jellydator/ttlcache/v3 v3.3.0 // indirect github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/oleiade/reflections v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a // indirect github.com/rs/cors v1.11.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/segmentio/ksuid v1.0.4 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tchap/go-patricia/v2 v2.3.2 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/urfave/negroni v1.0.0 // indirect github.com/vbatts/tar-split v0.11.6 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/zeebo/errs v1.4.0 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/sdk v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.step.sm/crypto v0.60.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.11.0 // indirect golang.org/x/tools v0.30.0 // indirect google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect google.golang.org/grpc v1.71.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) cosign-2.5.0/go.sum000066400000000000000000002673151477503325500141640ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME= cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM= cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM= cloud.google.com/go/kms v1.21.1 h1:r1Auo+jlfJSf8B7mUnVw5K0fI7jWyoUy65bV53VjKyk= cloud.google.com/go/kms v1.21.1/go.mod h1:s0wCyByc9LjTdCjG88toVs70U9W+cc6RKFc8zAqX7nE= cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q= cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY= cuelabs.dev/go/oci/ociregistry v0.0.0-20241125120445-2c00c104c6e1 h1:mRwydyTyhtRX2wXS3mqYWzR2qlv6KsmoKXmlz5vInjg= cuelabs.dev/go/oci/ociregistry v0.0.0-20241125120445-2c00c104c6e1/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg= cuelang.org/go v0.12.1 h1:5I+zxmXim9MmiN2tqRapIqowQxABv2NKTgbOspud1Eo= cuelang.org/go v0.12.1/go.mod h1:B4+kjvGGQnbkz+GuAv1dq/R308gTkp0sO28FdMrJ2Kw= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0 h1:kcnfY4vljxXliXDBrA9K9lwF8IoEZ4Up6Eg9kWTIm28= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0/go.mod h1:tlqp9mUGbsP+0z3Q+c0Q5MgSdq/OMwQhm5bffR3Q3ss= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8= github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc= github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/cr-20160607 v1.0.1 h1:WEnP1iPFKJU74ryUKh/YDPHoxMZawqlPajOymyNAkts= github.com/alibabacloud-go/cr-20160607 v1.0.1/go.mod h1:QHeKZtZ3F3FOE+/uIXCBAp8POwnUYekpLwr1dtQa5r0= github.com/alibabacloud-go/cr-20181201 v1.0.10 h1:B60f6S1imsgn2fgC6X6FrVNrONDrbCT0NwYhsJ0C9/c= github.com/alibabacloud-go/cr-20181201 v1.0.10/go.mod h1:VN9orB/w5G20FjytoSpZROqu9ZqxwycASmGqYUJSoDc= github.com/alibabacloud-go/darabonba-openapi v0.1.12/go.mod h1:sTAjsFJmVsmcVeklL9d9uDBlFsgl43wZ6jhI6BHqHqU= github.com/alibabacloud-go/darabonba-openapi v0.1.14/go.mod h1:w4CosR7O/kapCtEEMBm3JsQqWBU/CnZ2o0pHorsTWDI= github.com/alibabacloud-go/darabonba-openapi v0.2.1 h1:WyzxxKvhdVDlwpAMOHgAiCJ+NXa6g5ZWPFEzaK/ewwY= github.com/alibabacloud-go/darabonba-openapi v0.2.1/go.mod h1:zXOqLbpIqq543oioL9IuuZYOQgHQ5B8/n5OPrnko8aY= github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= github.com/alibabacloud-go/debug v1.0.0 h1:3eIEQWfay1fB24PQIEzXAswlVJtdQok8f3EVN5VrBnA= github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8= github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= github.com/alibabacloud-go/openapi-util v0.0.9/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/openapi-util v0.0.10/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.1 h1:rFF1LnrAdhaiPmKwH5xwYOKlMh66CqRwPUTzIK74ask= github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils v1.3.9/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/aliyun/credentials-go v1.3.2 h1:L4WppI9rctC8PdlMgyTkF8bBsy9pyKQEzBD1bHMRl+g= github.com/aliyun/credentials-go v1.3.2/go.mod h1:tlpz4uys4Rn7Ik4/piGRrTbXy2uLKvePgQJJduE+Y5c= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= github.com/aws/aws-sdk-go-v2/config v1.29.10 h1:yNjgjiGBp4GgaJrGythyBXg2wAs+Im9fSWIUwvi1CAc= github.com/aws/aws-sdk-go-v2/config v1.29.10/go.mod h1:A0mbLXSdtob/2t59n1X0iMkPQ5d+YzYZB4rwu7SZ7aA= github.com/aws/aws-sdk-go-v2/credentials v1.17.63 h1:rv1V3kIJ14pdmTu01hwcMJ0WAERensSiD9rEWEBb1Tk= github.com/aws/aws-sdk-go-v2/credentials v1.17.63/go.mod h1:EJj+yDf0txT26Ulo0VWTavBl31hOsaeuMxIHu2m0suY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3 h1:a+210FCU/pR5hhKRaskRfX/ogcyyzFBrehcTk5DTAyU= github.com/aws/aws-sdk-go-v2/service/ecr v1.40.3/go.mod h1:dtD3a4sjUjVL86e0NUvaqdGvds5ED6itUiZPDaT+Gh8= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2 h1:E6/Myrj9HgLF22medmDrKmbpm4ULsa+cIBNx3phirBk= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.31.2/go.mod h1:OQ8NALFcchBJ/qruak6zKUQodovnTKKaReTuCkc5/9Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 h1:tecq7+mAav5byF+Mr+iONJnCBf4B4gon8RSp4BrweSc= github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk= github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0= github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.2 h1:wK8O+j2dOolmpNVY1EWIbLgxrGCHJKVPm08Hv/u80M8= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.2/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc= github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1 h1:50sS0RWhGpW/yZx2KcDNEb1u1MANv5BMEkJgcieEDTA= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1/go.mod h1:ErZOtbzuHabipRTDTor0inoRlYwbsV1ovwSxjGs/uJo= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/buildkite/agent/v3 v3.95.1 h1:soe8EyQf068snMYtsQM10+l/l2wwbZIz0P7aDbz+Zws= github.com/buildkite/agent/v3 v3.95.1/go.mod h1:k2rfXF1U6fl/wejFXuS4YWXX4nDXZFpvKjCc5KUOAXg= github.com/buildkite/go-pipeline v0.13.3 h1:llI7sAdZ7sqYE7r8ePlmDADRhJ1K0Kua2+gv74Z9+Es= github.com/buildkite/go-pipeline v0.13.3/go.mod h1:1uC2XdHkTV1G5jYv9K8omERIwrsYbBruBrPx1Zu1uFw= github.com/buildkite/interpolate v0.1.5 h1:v2Ji3voik69UZlbfoqzx+qfcsOKLA61nHdU79VV+tPU= github.com/buildkite/interpolate v0.1.5/go.mod h1:dHnrwHew5O8VNOAgMDpwRlFnhL5VSN6M1bHVmRZ9Ccc= github.com/buildkite/roko v1.3.1 h1:t7K30ceLLYn6k7hQP4oq1c7dVlhgD5nRcuSRDEEnY1s= github.com/buildkite/roko v1.3.1/go.mod h1:23R9e6nHxgedznkwwfmqZ6+0VJZJZ2Sg/uVcp2cP46I= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589/go.mod h1:OuDyvmLnMCwa2ep4Jkm6nyA0ocJuZlGyk2gGseVzERM= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.19 h1:tUN6H7LWqNx4hQVxomd0CVsDwaDr9gaRQaI4GpSmrsA= github.com/creack/pty v1.1.19/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936 h1:foGzavPWwtoyBvjWyKJYDYsyzy+23iBV7NKTwdk+LRY= github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936/go.mod h1:ttKPnOepYt4LLzD+loXQ1rT6EmpyIYHro7TAJuIIlHo= github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps= github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA= github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I= github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I= github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.13.4 h1:myn1fyf8t7tAqIzV91Tj9qXpvyXXGXk8OS2H6IBSc9g= github.com/emicklei/proto v1.13.4/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU= github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-piv/piv-go/v2 v2.3.0 h1:kKkrYlgLQTMPA6BiSL25A7/x4CEh2YCG7rtb/aTkx+g= github.com/go-piv/piv-go/v2 v2.3.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/certificate-transparency-go v1.3.1 h1:akbcTfQg0iZlANZLn0L9xOeWtyCIdeoYhKrqi5iH3Go= github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k= github.com/google/flatbuffers v24.12.23+incompatible h1:ubBKR94NR4pXUCY/MUsRVzd9umNW7ht7EG9hHfS9FX8= github.com/google/flatbuffers v24.12.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/google/trillian v1.7.1 h1:+zX8jLM3524bAMPS+VxaDIDgsMv3/ty6DuLWerHXcek= github.com/google/trillian v1.7.1/go.mod h1:E1UMAHqpZCA8AQdrKdWmHmtUfSeiD0sDWD1cv00Xa+c= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/in-toto/attestation v1.1.1 h1:QD3d+oATQ0dFsWoNh5oT0udQ3tUrOsZZ0Fc3tSgWbzI= github.com/in-toto/attestation v1.1.1/go.mod h1:Dcq1zVwA2V7Qin8I7rgOi+i837wEf/mOZwRm047Sjys= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ= github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mozillazg/docker-credential-acr-helper v0.4.0 h1:Uoh3Z9CcpEDnLiozDx+D7oDgRq7X+R296vAqAumnOcw= github.com/mozillazg/docker-credential-acr-helper v0.4.0/go.mod h1:2kiicb3OlPytmlNC9XGkLvVC+f0qTiJw3f/mhmeeQBg= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.1.0 h1:D+I/UsXQB4esMathlt0kkZRJZdUDmhv5zGi/HOwYTWo= github.com/oleiade/reflections v1.1.0/go.mod h1:mCxx0QseeVCHs5Um5HhJeCKVC7AwS8kO67tky4rdisA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/open-policy-agent/opa v1.1.0 h1:HMz2evdEMTyNqtdLjmu3Vyx06BmhNYAx67Yz3Ll9q2s= github.com/open-policy-agent/opa v1.1.0/go.mod h1:T1pASQ1/vwfTa+e2fYcfpLCvWgYtqtiUv+IuA/dLPQs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a h1:w3tdWGKbLGBPtR/8/oO74W6hmz0qE5q0z9aqSAewaaM= github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/sigstore/fulcio v1.6.6 h1:XaMYX6TNT+8n7Npe8D94nyZ7/ERjEsNGFC+REdi/wzw= github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk= github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc= github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= github.com/sigstore/rekor v1.3.9 h1:sUjRpKVh/hhgqGMs0t+TubgYsksArZ6poLEC3MsGAzU= github.com/sigstore/rekor v1.3.9/go.mod h1:xThNUhm6eNEmkJ/SiU/FVU7pLY2f380fSDZFsdDWlcM= github.com/sigstore/sigstore v1.9.1 h1:bNMsfFATsMPaagcf+uppLk4C9rQZ2dh5ysmCxQBYWaw= github.com/sigstore/sigstore v1.9.1/go.mod h1:zUoATYzR1J3rLNp3jmp4fzIJtWdhC3ZM6MnpcBtnsE4= github.com/sigstore/sigstore-go v0.7.1 h1:lyzi3AjO6+BHc5zCf9fniycqPYOt3RaC08M/FRmQhVY= github.com/sigstore/sigstore-go v0.7.1/go.mod h1:AIRj4I3LC82qd07VFm3T2zXYiddxeBV1k/eoS8nTz0E= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.1 h1:/YcNq687WnXpIRXl04nLfJX741G4iW+w+7Nem2Zy0f4= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.1/go.mod h1:ApL9RpKsi7gkSYN0bMNdm/3jZ9EefxMmfYHfUmq2ZYM= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.1 h1:FnusXyTIInnwfIOzzl5PFilRm1I97dxMSOcCkZBu9Kc= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.1/go.mod h1:d5m5LOa/69a+t2YC9pDPwS1n2i/PhqB4cUKbpVDlKKE= github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.1 h1:LFiYK1DEWQ6Hf/nroFzBMM+s5rVSjVL45Alpb5Ctl5A= github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.1/go.mod h1:GFyFmDsE2wDuIHZD+4+JErGpA0S4zJsKNz5l2JVJd8s= github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.1 h1:sIW6xe4yU5eIMH8fve2C78d+r29KmHnIb+7po+80bsY= github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.1/go.mod h1:3pNf99GnK9eu3XUa5ebHzgEQSVYf9hqAoPFwbwD6O6M= github.com/sigstore/timestamp-authority v1.2.5 h1:W22JmwRv1Salr/NFFuP7iJuhytcZszQjldoB8GiEdnw= github.com/sigstore/timestamp-authority v1.2.5/go.mod h1:gWPKWq4HMWgPCETre0AakgBzcr9DRqHrsgbrRqsigOs= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= github.com/theupdateframework/go-tuf/v2 v2.0.2 h1:PyNnjV9BJNzN1ZE6BcWK+5JbF+if370jjzO84SS+Ebo= github.com/theupdateframework/go-tuf/v2 v2.0.2/go.mod h1:baB22nBHeHBCeuGZcIlctNq4P61PcOdyARlplg5xmLA= github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 h1:N9UxlsOzu5mttdjhxkDLbzwtEecuXmlxZVo/ds7JKJI= github.com/tink-crypto/tink-go-awskms/v2 v2.1.0/go.mod h1:PxSp9GlOkKL9rlybW804uspnHuO9nbD98V/fDX4uSis= github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 h1:3B9i6XBXNTRspfkTC0asN5W0K6GhOSgcujNiECNRNb0= github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw= github.com/tink-crypto/tink-go/v2 v2.3.0 h1:4/TA0lw0lA/iVKBL9f8R5eP7397bfc4antAMXF5JRhs= github.com/tink-crypto/tink-go/v2 v2.3.0/go.mod h1:kfPOtXIadHlekBTeBtJrHWqoGL+Fm3JQg0wtltPuxLU= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I= github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q= github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg= github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU= github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= gitlab.com/gitlab-org/api/client-go v0.127.0 h1:8xnxcNKGF2gDazEoMs+hOZfOspSSw8D0vAoWhQk9U+U= gitlab.com/gitlab-org/api/client-go v0.127.0/go.mod h1:bYC6fPORKSmtuPRyD9Z2rtbAjE7UeNatu2VWHRf4/LE= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.step.sm/crypto v0.60.0 h1:UgSw8DFG5xUOGB3GUID17UA32G4j1iNQ4qoMhBmsVFw= go.step.sm/crypto v0.60.0/go.mod h1:Ep83Lv818L4gV0vhFTdPWRKnL6/5fRMpi8SaoP5ArSw= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.227.0 h1:QvIHF9IuyG6d6ReE+BNd11kIB8hZvjN8Z5xY5t21zYc= google.golang.org/api v0.227.0/go.mod h1:EIpaG6MbTgQarWF5xJvX0eOJPK9n/5D4Bynb9j2HXvQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/release-utils v0.11.1 h1:hzvXGpHgHJfLOJB6TRuu14bzWc3XEglHmXHJqwClSZE= sigs.k8s.io/release-utils v0.11.1/go.mod h1:ybR2V/uQAOGxYfzYtBenSYeXWkBGNP2qnEiX77ACtpc= sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= cosign-2.5.0/hack/000077500000000000000000000000001477503325500137215ustar00rootroot00000000000000cosign-2.5.0/hack/boilerplate/000077500000000000000000000000001477503325500162235ustar00rootroot00000000000000cosign-2.5.0/hack/boilerplate/boilerplate.go.txt000066400000000000000000000011271477503325500216730ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. cosign-2.5.0/hack/github-oidc-setup.sh000077500000000000000000000066371477503325500176300ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2022 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Idempotent script. # # Commands based off of Google blog post # https://cloud.google.com/blog/products/identity-security/enabling-keyless-authentication-from-github-actions # # One addition is the attribute.repository=assertion.repository mapping. # This allows it to be pinned to given repo. set -o errexit set -o nounset set -o pipefail set -o verbose set -o xtrace PROJECT_ID="projectsigstore" PROJECT_NUMBER="498091336538" POOL_NAME="githubactions" PROVIDER_NAME="sigstore-cosign" LOCATION="global" REPO="sigstore/cosign" SERVICE_ACCOUNT_ID="github-actions-cosign" SERVICE_ACCOUNT="${SERVICE_ACCOUNT_ID}@${PROJECT_ID}.iam.gserviceaccount.com" # Create workload identity pool if not present. if ! (gcloud iam workload-identity-pools describe "${POOL_NAME}" --location=${LOCATION}); then gcloud iam workload-identity-pools create "${POOL_NAME}" \ --project="${PROJECT_ID}" \ --location="${LOCATION}" \ --display-name="Github Actions Pool" fi # Create workload identity provider if not present. if ! (gcloud iam workload-identity-pools providers describe "${PROVIDER_NAME}" --location="${LOCATION}" --workload-identity-pool="${POOL_NAME}"); then gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_NAME}" \ --project="${PROJECT_ID}" \ --location="${LOCATION}" \ --workload-identity-pool="${POOL_NAME}" \ --display-name="Github Actions Provider Cosign" \ --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.aud=assertion.aud,attribute.repository=assertion.repository" \ --issuer-uri="https://token.actions.githubusercontent.com" fi # Create service account if not present. if ! (gcloud iam service-accounts describe "${SERVICE_ACCOUNT}"); then gcloud iam service-accounts create ${SERVICE_ACCOUNT_ID} \ --description="Service account for Github Actions Cosign" \ --display-name="Github Actions Cosign" fi # Adding binding is idempotent. gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT}" \ --project="${PROJECT_ID}" \ --role="roles/iam.workloadIdentityUser" \ --member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/${LOCATION}/workloadIdentityPools/${POOL_NAME}/attribute.repository/${REPO}" # Adding binding is idempotent. # Used for kicking off cloud build. gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ --project="${PROJECT_ID}" \ --role="roles/cloudbuild.builds.editor" \ --member="serviceAccount:${SERVICE_ACCOUNT}" # Adding binding is idempotent. # Permission needed to run `gcloud builds` # https://cloud.google.com/build/docs/securing-builds/configure-access-to-resources#granting_permissions_to_run_gcloud_commands gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ --project="${PROJECT_ID}" \ --role="roles/serviceusage.serviceUsageConsumer" \ --member="serviceAccount:${SERVICE_ACCOUNT}" cosign-2.5.0/hack/update-deps.sh000077500000000000000000000013011477503325500164660ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2022 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail pushd $(dirname "$0")/.. go get ./... go mod tidy cosign-2.5.0/images/000077500000000000000000000000001477503325500142605ustar00rootroot00000000000000cosign-2.5.0/images/dot/000077500000000000000000000000001477503325500150465ustar00rootroot00000000000000cosign-2.5.0/images/dot/signatures.dot000066400000000000000000000025201477503325500177410ustar00rootroot00000000000000digraph { compound=true; rankdir="LR"; ordering = in; subgraph cluster_registry { label = "registry"; subgraph cluster_tags { label = "/v2/.../tags/list"; tag [label="tag", shape="rect"]; tag2 [label="tag", shape="rect"]; } subgraph cluster_manifests { label = "/v2/.../manifests/"; subgraph cluster_image { label = "image"; mconfig [label="config", shape="rect"]; layers [label="layers", shape="rect"]; } subgraph cluster_index { label = "signature index"; imanifest [label="manifests", shape="rect"]; } } subgraph cluster_blobs { label = "/v2/.../blobs/"; bconfig [label="config", shape="hexagon"]; l1 [label="layer", shape="folder"]; l2 [label="layer", shape="folder"]; desc1 [label="descriptor", shape="rect", color="green"]; desc2 [label="descriptor", shape="rect", color="green"]; } layers -> l1; layers -> l2; mconfig -> bconfig; imanifest -> desc1 [color="green"]; imanifest -> desc2 [color="green"]; desc1 -> mconfig [lhead=cluster_image, color="green"]; desc2 -> mconfig [lhead=cluster_image, color="green"]; tag -> mconfig [style="dashed", lhead=cluster_image]; tag2 -> imanifest [style="dashed", lhead=cluster_index]; } }cosign-2.5.0/images/signatures.dot.svg000066400000000000000000000172371477503325500177640ustar00rootroot00000000000000 %3 cluster_registry registry cluster_tags /v2/.../tags/list cluster_manifests /v2/.../manifests/<ref> cluster_image image cluster_index signature index cluster_blobs /v2/.../blobs/<sha256> tag tag mconfig config tag->mconfig tag2 tag imanifest manifests tag2->imanifest bconfig config mconfig->bconfig layers layers l1 layer layers->l1 l2 layer layers->l2 desc1 descriptor imanifest->desc1 desc2 descriptor imanifest->desc2 desc1->mconfig desc2->mconfig cosign-2.5.0/internal/000077500000000000000000000000001477503325500146275ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/000077500000000000000000000000001477503325500154105ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/000077500000000000000000000000001477503325500166725ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/common.go000066400000000000000000000033021477503325500205070ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "errors" "hash" "io" "io/fs" "os" ) const ( DefaultMaxWorkers int = 10 ) func FileExists(filename string) (bool, error) { info, err := os.Stat(filename) if errors.Is(err, fs.ErrNotExist) { return false, nil } if err != nil { return false, err } return !info.IsDir(), nil } // HashReader hashes while it reads. type HashReader struct { r io.Reader h hash.Hash } func NewHashReader(r io.Reader, h hash.Hash) HashReader { return HashReader{ r: io.TeeReader(r, h), h: h, } } // Read implements io.Reader. func (h *HashReader) Read(p []byte) (n int, err error) { return h.r.Read(p) } // Sum implements hash.Hash. func (h *HashReader) Sum(p []byte) []byte { return h.h.Sum(p) } // Reset implements hash.Hash. func (h *HashReader) Reset() { h.h.Reset() } // Size implements hash.Hash. func (h *HashReader) Size() int { return h.h.Size() } // BlockSize implements hash.Hash. func (h *HashReader) BlockSize() int { return h.h.BlockSize() } // Write implements hash.Hash func (h *HashReader) Write(p []byte) (int, error) { return 0, errors.New("not implemented") } //nolint: revive cosign-2.5.0/internal/pkg/cosign/common_test.go000066400000000000000000000034451477503325500215560ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "bytes" "crypto/sha256" "io" "os" "testing" ) func Test_FileExists(t *testing.T) { tmpFile, err := os.CreateTemp(os.TempDir(), "cosign_test.txt") if err != nil { t.Fatal(err) } tests := []struct { name string path string exists bool wantErr bool }{ {"file exists", tmpFile.Name(), true, false}, {"file does not exist", "testt.txt", false, false}, {"other error e.g cannot access file", "\000x", false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := FileExists(tt.path) if (err != nil) != tt.wantErr { t.Errorf("FileExists() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.exists { t.Errorf("FileExists() = %v, want %v", got, tt.exists) } }) } } func Test_HashReader(t *testing.T) { input := []byte("hello world") r := NewHashReader(bytes.NewReader(input), sha256.New()) got, err := io.ReadAll(&r) if err != nil { t.Fatal(err) } if !bytes.Equal(got, input) { t.Errorf("io.ReadAll returned %s, want %s", got, input) } gotHash := r.Sum(nil) if hash := sha256.Sum256(input); !bytes.Equal(gotHash, hash[:]) { t.Errorf("Sum returned %s, want %s", gotHash, hash) } } cosign-2.5.0/internal/pkg/cosign/dsse.go000066400000000000000000000020611477503325500201560ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "crypto" "io" "github.com/sigstore/cosign/v2/pkg/oci" ) // DSSEAttestor creates attestations in the form of `oci.Signature`s type DSSEAttestor interface { // Attest creates an attestation, in the form of an `oci.Signature`, from the given payload. // The signature and payload are stored as a DSSE envelope in `osi.Signature.Payload()` DSSEAttest(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) } cosign-2.5.0/internal/pkg/cosign/ephemeral/000077500000000000000000000000001477503325500206345ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/ephemeral/signer.go000066400000000000000000000042061477503325500224540ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ephemeral import ( "bytes" "context" "crypto" "encoding/base64" "fmt" "io" icosign "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/sigstore/pkg/signature" ) type ephemeralSigner struct { signer signature.Signer } var _ icosign.Signer = ephemeralSigner{} // Sign implements `Signer` func (ks ephemeralSigner) Sign(_ context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) { pub, err := ks.signer.PublicKey() if err != nil { return nil, nil, fmt.Errorf("retrieving the static public key somehow failed: %w", err) } payloadBytes, err := io.ReadAll(payload) if err != nil { return nil, nil, err } sig, err := ks.signer.SignMessage(bytes.NewReader(payloadBytes)) if err != nil { return nil, nil, err } b64sig := base64.StdEncoding.EncodeToString(sig) ociSig, err := static.NewSignature(payloadBytes, b64sig) if err != nil { return nil, nil, err } return ociSig, pub, err } // NewSigner generates a new private signing key and returns a `cosign.Signer` which creates signatures with it. func NewSigner() (icosign.Signer, error) { priv, err := cosign.GeneratePrivateKey() if err != nil { return nil, fmt.Errorf("generating cert: %w", err) } s, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256) if err != nil { return nil, fmt.Errorf("creating a SignerVerifier from ephemeral key: %w", err) } return ephemeralSigner{ signer: s, }, nil } cosign-2.5.0/internal/pkg/cosign/ephemeral/signer_test.go000066400000000000000000000027141477503325500235150ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ephemeral import ( "bytes" "context" "crypto" "strings" "testing" "github.com/sigstore/sigstore/pkg/signature" ) func TestEphemeralSigner(t *testing.T) { testSigner, err := NewSigner() if err != nil { t.Fatalf("NewSigner() returned error: %v", err) } testPayload := "test payload" ociSig, pub, err := testSigner.Sign(context.Background(), strings.NewReader(testPayload)) if err != nil { t.Fatalf("Sign() returned error: %v", err) } verifier, err := signature.LoadVerifier(pub, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadVerifier(pub) returned error: %v", err) } sig, err := ociSig.Signature() if err != nil { t.Fatalf("ociSig.Signature() returned error: %v", err) } err = verifier.VerifySignature(bytes.NewReader(sig), strings.NewReader(testPayload)) if err != nil { t.Fatalf("VerifySignature() returned error: %v", err) } } cosign-2.5.0/internal/pkg/cosign/fulcio/000077500000000000000000000000001477503325500201535ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/fulcio/fulcioroots/000077500000000000000000000000001477503325500225235ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go000066400000000000000000000060531477503325500254260ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcioroots import ( "bytes" "crypto/x509" "fmt" "os" "sync" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/fulcioroots" ) var ( rootsOnce sync.Once roots *x509.CertPool intermediates *x509.CertPool singletonRootErr error ) // Get returns the Fulcio root certificate. // // If the SIGSTORE_ROOT_FILE environment variable is set, the root config found // there will be used instead of the normal Fulcio roots. func Get() (*x509.CertPool, error) { rootsOnce.Do(func() { roots, intermediates, singletonRootErr = initRoots() }) return roots, singletonRootErr } // GetIntermediates returns the Fulcio intermediate certificates. // // If the SIGSTORE_ROOT_FILE environment variable is set, the root config found // there will be used instead of the normal Fulcio intermediates. func GetIntermediates() (*x509.CertPool, error) { rootsOnce.Do(func() { roots, intermediates, singletonRootErr = initRoots() }) return intermediates, singletonRootErr } // ReInit reinitializes the global roots and intermediates, overriding the sync.Once lock. // This is only to be used for tests, where the trust root environment variables may change after the roots are initialized in the module. func ReInit() error { roots, intermediates, singletonRootErr = initRoots() return singletonRootErr } func initRoots() (*x509.CertPool, *x509.CertPool, error) { rootPool := x509.NewCertPool() // intermediatePool should be nil if no intermediates are found var intermediatePool *x509.CertPool rootEnv := env.Getenv(env.VariableSigstoreRootFile) if rootEnv != "" { raw, err := os.ReadFile(rootEnv) if err != nil { return nil, nil, fmt.Errorf("error reading root PEM file: %w", err) } certs, err := cryptoutils.UnmarshalCertificatesFromPEM(raw) if err != nil { return nil, nil, fmt.Errorf("error unmarshalling certificates: %w", err) } for _, cert := range certs { // root certificates are self-signed if bytes.Equal(cert.RawSubject, cert.RawIssuer) { rootPool.AddCert(cert) } else { if intermediatePool == nil { intermediatePool = x509.NewCertPool() } intermediatePool.AddCert(cert) } } } else { var err error rootPool, err = fulcioroots.Get() if err != nil { return nil, nil, err } intermediatePool, err = fulcioroots.GetIntermediates() if err != nil { return nil, nil, err } } return rootPool, intermediatePool, nil } cosign-2.5.0/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots_test.go000066400000000000000000000061451477503325500264670ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcioroots import ( "os" "sync" "testing" "github.com/sigstore/cosign/v2/test" "github.com/sigstore/sigstore/pkg/cryptoutils" ) func resetState() { rootsOnce = sync.Once{} } func TestGetFulcioRoots(t *testing.T) { t.Cleanup(resetState) rootCert, rootPriv, _ := test.GenerateRootCa() rootPemCert, _ := cryptoutils.MarshalCertificateToPEM(rootCert) subCert, _, _ := test.GenerateSubordinateCa(rootCert, rootPriv) subPemCert, _ := cryptoutils.MarshalCertificateToPEM(subCert) var chain []byte chain = append(chain, subPemCert...) chain = append(chain, rootPemCert...) tmpCertFile, err := os.CreateTemp(t.TempDir(), "cosign_fulcio_root_*.cert") if err != nil { t.Fatalf("failed to create temp cert file: %v", err) } defer tmpCertFile.Close() if _, err := tmpCertFile.Write(chain); err != nil { t.Fatalf("failed to write cert file: %v", err) } t.Setenv("SIGSTORE_ROOT_FILE", tmpCertFile.Name()) if rootCertPool, err := Get(); err != nil { t.Fatalf("failed to get roots: %v", err) } else if len(rootCertPool.Subjects()) != 1 { // nolint:staticcheck // ignore deprecation error because certificates do not contain from SystemCertPool t.Errorf("expected 1 root certificate, got 0") } if subCertPool, err := GetIntermediates(); err != nil { t.Fatalf("failed to get intermediates: %v", err) } else if len(subCertPool.Subjects()) != 1 { // nolint:staticcheck // ignore deprecation error because certificates do not contain from SystemCertPool t.Errorf("expected 1 intermediate certificate, got 0") } } func TestGetFulcioRootsWithoutIntermediate(t *testing.T) { t.Cleanup(resetState) rootCert, _, _ := test.GenerateRootCa() rootPemCert, _ := cryptoutils.MarshalCertificateToPEM(rootCert) tmpCertFile, err := os.CreateTemp(t.TempDir(), "cosign_fulcio_root_*.cert") if err != nil { t.Fatalf("failed to create temp cert file: %v", err) } defer tmpCertFile.Close() if _, err := tmpCertFile.Write(rootPemCert); err != nil { t.Fatalf("failed to write cert file: %v", err) } t.Setenv("SIGSTORE_ROOT_FILE", tmpCertFile.Name()) if rootCertPool, err := Get(); err != nil { t.Fatalf("failed to get roots: %v", err) } else if len(rootCertPool.Subjects()) != 1 { // nolint:staticcheck // ignore deprecation error because certificates do not contain from SystemCertPool t.Errorf("expected 1 root certificate, got 0") } if subCertPool, err := GetIntermediates(); err != nil { t.Fatalf("failed to get intermediates: %v", err) } else if subCertPool != nil { t.Errorf("expected no intermediate cert pool") } } cosign-2.5.0/internal/pkg/cosign/fulcio/signer.go000066400000000000000000000033701477503325500217740ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcio import ( "context" "crypto" "io" "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ) // signerWrapper still needs to actually upload keys to Fulcio and receive // the resulting `Cert` and `Chain`, which are added to the returned `oci.Signature` type signerWrapper struct { inner cosign.Signer cert, chain []byte } var _ cosign.Signer = (*signerWrapper)(nil) // Sign implements `cosign.Signer` func (fs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) { sig, pub, err := fs.inner.Sign(ctx, payload) if err != nil { return nil, nil, err } // TODO(dekkagaijin): move the fulcio SignerVerifier logic here newSig, err := mutate.Signature(sig, mutate.WithCertChain(fs.cert, fs.chain)) if err != nil { return nil, nil, err } return newSig, pub, nil } // NewSigner returns a `cosign.Signer` which leverages Fulcio to create a Cert and Chain for the signature func NewSigner(inner cosign.Signer, cert, chain []byte) cosign.Signer { return &signerWrapper{ inner: inner, cert: cert, chain: chain, } } cosign-2.5.0/internal/pkg/cosign/fulcio/signer_test.go000066400000000000000000000107521477503325500230350ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcio import ( "bytes" "context" "crypto" "strings" "testing" "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/sigstore/pkg/signature" ) var ( testCertBytes = []byte(` -----BEGIN CERTIFICATE----- MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt 6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G 3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG 0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY= -----END CERTIFICATE----- `) testChainBytes = []byte(` -----BEGIN CERTIFICATE----- MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== -----END CERTIFICATE----- `) ) func mustGetNewSigner(t *testing.T) signature.Signer { t.Helper() priv, err := cosign.GeneratePrivateKey() if err != nil { t.Fatalf("cosign.GeneratePrivateKey() failed: %v", err) } s, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadECDSASignerVerifier(key, crypto.SHA256) failed: %v", err) } return s } func TestSigner(t *testing.T) { // Need real cert and chain payloadSigner := payload.NewSigner(mustGetNewSigner(t)) testSigner := NewSigner(payloadSigner, testCertBytes, testChainBytes) testPayload := "test payload" ociSig, pub, err := testSigner.Sign(context.Background(), strings.NewReader(testPayload)) if err != nil { t.Fatalf("Sign() returned error: %v", err) } // Verify that the OCI signature contains a cert, chain and timestamp. cert, err := ociSig.Cert() if err != nil { t.Fatalf("ociSig.Cert() returned error: %v", err) } if cert == nil { t.Fatal("ociSig.Cert() missing certificate, got nil") } chain, err := ociSig.Chain() if err != nil { t.Fatalf("ociSig.Chain() returned error: %v", err) } if len(chain) != 1 { t.Fatalf("ociSig.Chain() expected to be of length 1, got %d", len(chain)) } if chain[0] == nil { t.Fatal("ociSig.Chain()[0] missing certificate, got nil") } // Verify that the wrapped signer was called. verifier, err := signature.LoadVerifier(pub, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadVerifier(pub) returned error: %v", err) } sig, err := ociSig.Signature() if err != nil { t.Fatalf("ociSig.Signature() returned error: %v", err) } gotPayload, err := ociSig.Payload() if err != nil { t.Fatalf("ociSig.Payload() returned error: %v", err) } if string(gotPayload) != testPayload { t.Errorf("ociSig.Payload() returned %q, wanted %q", string(gotPayload), testPayload) } if err = verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(gotPayload)); err != nil { t.Errorf("VerifySignature() returned error: %v", err) } } cosign-2.5.0/internal/pkg/cosign/payload/000077500000000000000000000000001477503325500203235ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/payload/attestor.go000066400000000000000000000047621477503325500225300ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package payload import ( "context" "crypto" "encoding/base64" "encoding/json" "io" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" "github.com/sigstore/sigstore/pkg/signature" ) type payloadAttestor struct { signer payloadSigner payloadType string } var _ cosign.DSSEAttestor = (*payloadAttestor)(nil) // Attest implements `cosign.DSSEAttestor` func (pa *payloadAttestor) DSSEAttest(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) { p, err := io.ReadAll(payload) if err != nil { return nil, nil, err } pb := dsse.PAE(pa.payloadType, p) sig, err := pa.signer.signPayload(ctx, pb) if err != nil { return nil, nil, err } pk, err := pa.signer.publicKey(ctx) if err != nil { return nil, nil, err } envelope := dsse.Envelope{ PayloadType: pa.payloadType, Payload: base64.StdEncoding.EncodeToString(pb), Signatures: []dsse.Signature{{ Sig: base64.StdEncoding.EncodeToString(sig), }}, } envelopeJSON, err := json.Marshal(envelope) if err != nil { return nil, nil, err } opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} att, err := static.NewAttestation(envelopeJSON, opts...) if err != nil { return nil, nil, err } return att, pk, nil } // NewDSSEAttestor returns a `cosign.DSSEAttestor` which uses the given `signature.Signer` to sign and create a DSSE attestation of given payloads. // Option types other than `signature.SignOption` and `signature.PublicKeyOption` cause a runtime panic. func NewDSSEAttestor(payloadType string, s signature.Signer, signAndPublicKeyOptions ...interface{}) cosign.DSSEAttestor { return &payloadAttestor{ signer: newSigner(s, signAndPublicKeyOptions...), payloadType: payloadType, } } cosign-2.5.0/internal/pkg/cosign/payload/attestor_test.go000066400000000000000000000051051477503325500235570ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package payload import ( "bytes" "context" "crypto" "encoding/base64" "encoding/json" "strings" "testing" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/pkg/types" "github.com/sigstore/sigstore/pkg/signature" ) func TestDSSEAttestor(t *testing.T) { testPayloadType := "atTESTation type" testSigner := NewDSSEAttestor(testPayloadType, mustGetNewSigner(t)) testPayload := "test payload" ociSig, pub, err := testSigner.DSSEAttest(context.Background(), strings.NewReader(testPayload)) if err != nil { t.Fatalf("DSSEAttest() returned error: %v", err) } gotMT, err := ociSig.MediaType() if err != nil { t.Fatalf("ociSig.MediaType() failed: %v", err) } if gotMT != types.DssePayloadType { t.Errorf("got MediaType() %q, wanted %q", gotMT, types.DssePayloadType) } verifier, err := signature.LoadVerifier(pub, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadVerifier(pub) returned error: %v", err) } gotOCISigPayload, err := ociSig.Payload() if err != nil { t.Fatalf("ociSig.Payload() returned error: %v", err) } envelope := dsse.Envelope{} if err := json.Unmarshal(gotOCISigPayload, &envelope); err != nil { t.Fatalf("json.Unmarshal() failed: %v", err) } if envelope.PayloadType != testPayloadType { t.Errorf("got PayloadType %q, wanted %q", envelope.PayloadType, testPayloadType) } if len(envelope.Signatures) != 1 { t.Errorf("expected a single signature in the envelope, got: %v", envelope.Signatures) } gotPayload, err := base64.StdEncoding.DecodeString(envelope.Payload) if err != nil { t.Fatalf("base64.StdEncoding.DecodeString(envelope.Payload) failed: %v", err) } gotSig, err := base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig) if err != nil { t.Fatalf("base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig) failed: %v", err) } if err = verifier.VerifySignature(bytes.NewReader(gotSig), bytes.NewReader(gotPayload)); err != nil { t.Errorf("VerifySignature() returned error: %v", err) } } cosign-2.5.0/internal/pkg/cosign/payload/signer.go000066400000000000000000000065331477503325500221500ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package payload import ( "bytes" "context" "crypto" "encoding/base64" "fmt" "io" "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) type payloadSigner struct { payloadSigner signature.Signer payloadSignerOpts []signature.SignOption publicKeyProviderOpts []signature.PublicKeyOption } var _ cosign.Signer = (*payloadSigner)(nil) // Sign implements `Signer` func (ps *payloadSigner) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) { payloadBytes, err := io.ReadAll(payload) if err != nil { return nil, nil, err } sig, err := ps.signPayload(ctx, payloadBytes) if err != nil { return nil, nil, err } pk, err := ps.publicKey(ctx) if err != nil { return nil, nil, err } b64sig := base64.StdEncoding.EncodeToString(sig) ociSig, err := static.NewSignature(payloadBytes, b64sig) if err != nil { return nil, nil, err } return ociSig, pk, nil } func (ps *payloadSigner) publicKey(ctx context.Context) (pk crypto.PublicKey, err error) { pkOpts := []signature.PublicKeyOption{signatureoptions.WithContext(ctx)} pkOpts = append(pkOpts, ps.publicKeyProviderOpts...) pk, err = ps.payloadSigner.PublicKey(pkOpts...) if err != nil { return nil, err } return pk, nil } func (ps *payloadSigner) signPayload(ctx context.Context, payloadBytes []byte) (sig []byte, err error) { sOpts := []signature.SignOption{signatureoptions.WithContext(ctx)} sOpts = append(sOpts, ps.payloadSignerOpts...) sig, err = ps.payloadSigner.SignMessage(bytes.NewReader(payloadBytes), sOpts...) if err != nil { return nil, err } return sig, nil } func newSigner(s signature.Signer, signAndPublicKeyOptions ...interface{}) payloadSigner { var sOpts []signature.SignOption var pkOpts []signature.PublicKeyOption for _, opt := range signAndPublicKeyOptions { switch o := opt.(type) { case signature.SignOption: sOpts = append(sOpts, o) case signature.PublicKeyOption: pkOpts = append(pkOpts, o) default: panic(fmt.Sprintf("options must be of type `signature.SignOption` or `signature.PublicKeyOption`. Got a %T: %v", o, o)) } } return payloadSigner{ payloadSigner: s, payloadSignerOpts: sOpts, publicKeyProviderOpts: pkOpts, } } // NewSigner returns a `cosign.Signer` which uses the given `signature.Signer` to sign requested payloads. // Option types other than `signature.SignOption` and `signature.PublicKeyOption` cause a runtime panic. func NewSigner(s signature.Signer, signAndPublicKeyOptions ...interface{}) cosign.Signer { signer := newSigner(s, signAndPublicKeyOptions...) return &signer } cosign-2.5.0/internal/pkg/cosign/payload/signer_test.go000066400000000000000000000040301477503325500231750ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package payload import ( "bytes" "context" "crypto" "strings" "testing" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/sigstore/pkg/signature" ) func mustGetNewSigner(t *testing.T) signature.Signer { t.Helper() priv, err := cosign.GeneratePrivateKey() if err != nil { t.Fatalf("cosign.GeneratePrivateKey() failed: %v", err) } s, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadECDSASignerVerifier(key, crypto.SHA256) failed: %v", err) } return s } func TestSigner(t *testing.T) { testSigner := NewSigner(mustGetNewSigner(t)) testPayload := "test payload" ociSig, pub, err := testSigner.Sign(context.Background(), strings.NewReader(testPayload)) if err != nil { t.Fatalf("Sign() returned error: %v", err) } verifier, err := signature.LoadVerifier(pub, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadVerifier(pub) returned error: %v", err) } sig, err := ociSig.Signature() if err != nil { t.Fatalf("ociSig.Signature() returned error: %v", err) } gotPayload, err := ociSig.Payload() if err != nil { t.Fatalf("ociSig.Payload() returned error: %v", err) } if string(gotPayload) != testPayload { t.Errorf("ociSig.Payload() returned %q, wanted %q", string(gotPayload), testPayload) } if err = verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(gotPayload)); err != nil { t.Errorf("VerifySignature() returned error: %v", err) } } cosign-2.5.0/internal/pkg/cosign/payload/size/000077500000000000000000000000001477503325500212755ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/payload/size/errors.go000066400000000000000000000021051477503325500231360ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package payload import "fmt" // MaxLayerSizeExceeded is an error indicating that the layer is too big to read into memory and cosign should abort processing it. type MaxLayerSizeExceeded struct { value uint64 maximum uint64 } func NewMaxLayerSizeExceeded(value, maximum uint64) *MaxLayerSizeExceeded { return &MaxLayerSizeExceeded{value, maximum} } func (e *MaxLayerSizeExceeded) Error() string { return fmt.Sprintf("size of layer (%d) exceeded the limit (%d)", e.value, e.maximum) } cosign-2.5.0/internal/pkg/cosign/payload/size/size.go000066400000000000000000000021371477503325500226010ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package payload import ( "github.com/dustin/go-humanize" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) const defaultMaxSize = uint64(134217728) // 128MiB func CheckSize(size uint64) error { maxSize := defaultMaxSize maxSizeOverride, exists := env.LookupEnv(env.VariableMaxAttachmentSize) if exists { var err error maxSize, err = humanize.ParseBytes(maxSizeOverride) if err != nil { maxSize = defaultMaxSize } } if size > maxSize { return NewMaxLayerSizeExceeded(size, maxSize) } return nil } cosign-2.5.0/internal/pkg/cosign/payload/size/size_test.go000066400000000000000000000047611477503325500236450ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package payload import ( "testing" ) func TestCheckSize(t *testing.T) { tests := []struct { name string input uint64 setting string wantErr bool }{ { name: "size is within default limit", input: 1000, wantErr: false, }, { name: "size exceeds default limit", input: 200000000, wantErr: true, }, { name: "size is within overridden limit (bytes)", input: 1000, setting: "1024", wantErr: false, }, { name: "size is exceeds overridden limit (bytes)", input: 2000, setting: "1024", wantErr: true, }, { name: "size is within overridden limit (megabytes, short form)", input: 1999999, setting: "2M", wantErr: false, }, { name: "size exceeds overridden limit (megabytes, short form)", input: 2000001, setting: "2M", wantErr: true, }, { name: "size is within overridden limit (megabytes, long form)", input: 1999999, setting: "2MB", wantErr: false, }, { name: "size exceeds overridden limit (megabytes, long form)", input: 2000001, setting: "2MB", wantErr: true, }, { name: "size is within overridden limit (mebibytes)", input: 2097151, setting: "2MiB", wantErr: false, }, { name: "size exceeds overridden limit (mebibytes)", input: 2097153, setting: "2MiB", wantErr: true, }, { name: "size is negative results in default", input: 5121, setting: "-5KiB", wantErr: false, }, { name: "invalid setting results in default", input: 5121, setting: "five kilobytes", wantErr: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.setting != "" { t.Setenv("COSIGN_MAX_ATTACHMENT_SIZE", test.setting) } got := CheckSize(test.input) if (got != nil) != (test.wantErr) { t.Errorf("CheckSize() = %v, expected %v", got, test.wantErr) } }) } } cosign-2.5.0/internal/pkg/cosign/rekor/000077500000000000000000000000001477503325500200145ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/rekor/mock/000077500000000000000000000000001477503325500207455ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/rekor/mock/mock_rekor_client.go000066400000000000000000000045471477503325500247770ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mock import ( "errors" "github.com/go-openapi/runtime" "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" ) // EntriesClient is a client that implements entries.ClientService for Rekor // To use: // var mClient client.Rekor // mClient.Entries = &logEntry type EntriesClient struct { Entries []*models.LogEntry } func (m *EntriesClient) CreateLogEntry(_ *entries.CreateLogEntryParams, _ ...entries.ClientOption) (*entries.CreateLogEntryCreated, error) { if m.Entries != nil { return &entries.CreateLogEntryCreated{ ETag: "", Location: "", Payload: *m.Entries[0], }, nil } return nil, errors.New("entry not provided") } func (m *EntriesClient) GetLogEntryByIndex(_ *entries.GetLogEntryByIndexParams, _ ...entries.ClientOption) (*entries.GetLogEntryByIndexOK, error) { if m.Entries != nil { return &entries.GetLogEntryByIndexOK{ Payload: *m.Entries[0], }, nil } return nil, errors.New("entry not provided") } func (m *EntriesClient) GetLogEntryByUUID(params *entries.GetLogEntryByUUIDParams, opts ...entries.ClientOption) (*entries.GetLogEntryByUUIDOK, error) { //nolint: revive if m.Entries != nil { return &entries.GetLogEntryByUUIDOK{ Payload: *m.Entries[0], }, nil } return nil, errors.New("entry not provided") } func (m *EntriesClient) SearchLogQuery(params *entries.SearchLogQueryParams, opts ...entries.ClientOption) (*entries.SearchLogQueryOK, error) { //nolint: revive resp := []models.LogEntry{} if m.Entries != nil { for _, entry := range m.Entries { resp = append(resp, *entry) } } return &entries.SearchLogQueryOK{ Payload: resp, }, nil } // TODO: Implement mock func (m *EntriesClient) SetTransport(transport runtime.ClientTransport) { //nolint: revive // noop } cosign-2.5.0/internal/pkg/cosign/rekor/signer.go000066400000000000000000000063551477503325500216430ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rekor import ( "context" "crypto" "crypto/sha256" "encoding/base64" "fmt" "io" "os" "github.com/sigstore/cosign/v2/internal/pkg/cosign" cosignv1 "github.com/sigstore/cosign/v2/pkg/cosign" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/mutate" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/cryptoutils" ) type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error) func uploadToTlog(rekorBytes []byte, rClient *client.Rekor, upload tlogUploadFn) (*cbundle.RekorBundle, error) { entry, err := upload(rClient, rekorBytes) if err != nil { return nil, err } fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex) return cbundle.EntryToBundle(entry), nil } // signerWrapper calls a wrapped, inner signer then uploads either the Cert or Pub(licKey) of the results to Rekor, then adds the resulting `Bundle` type signerWrapper struct { inner cosign.Signer rClient *client.Rekor } var _ cosign.Signer = (*signerWrapper)(nil) // Sign implements `cosign.Signer` func (rs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) { sig, pub, err := rs.inner.Sign(ctx, payload) if err != nil { return nil, nil, err } payloadBytes, err := sig.Payload() if err != nil { return nil, nil, err } b64Sig, err := sig.Base64Signature() if err != nil { return nil, nil, err } sigBytes, err := base64.StdEncoding.DecodeString(b64Sig) if err != nil { return nil, nil, err } // Upload the cert or the public key, depending on what we have cert, err := sig.Cert() if err != nil { return nil, nil, err } var rekorBytes []byte if cert != nil { rekorBytes, err = cryptoutils.MarshalCertificateToPEM(cert) } else { rekorBytes, err = cryptoutils.MarshalPublicKeyToPEM(pub) } if err != nil { return nil, nil, err } bundle, err := uploadToTlog(rekorBytes, rs.rClient, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) { checkSum := sha256.New() if _, err := checkSum.Write(payloadBytes); err != nil { return nil, err } return cosignv1.TLogUpload(ctx, r, sigBytes, checkSum, b) }) if err != nil { return nil, nil, err } newSig, err := mutate.Signature(sig, mutate.WithBundle(bundle)) if err != nil { return nil, nil, err } return newSig, pub, nil } // NewSigner returns a `cosign.Signer` which uploads the signature to Rekor func NewSigner(inner cosign.Signer, rClient *client.Rekor) cosign.Signer { return &signerWrapper{ inner: inner, rClient: rClient, } } cosign-2.5.0/internal/pkg/cosign/rekor/signer_test.go000066400000000000000000000051341477503325500226740ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rekor import ( "bytes" "context" "crypto" "strings" "testing" "github.com/go-openapi/swag" "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload" "github.com/sigstore/cosign/v2/internal/pkg/cosign/rekor/mock" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/signature" ) func mustGetNewSigner(t *testing.T) signature.Signer { t.Helper() priv, err := cosign.GeneratePrivateKey() if err != nil { t.Fatalf("cosign.GeneratePrivateKey() failed: %v", err) } s, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadECDSASignerVerifier(key, crypto.SHA256) failed: %v", err) } return s } func TestSigner(t *testing.T) { // Need real cert and chain payloadSigner := payload.NewSigner(mustGetNewSigner(t)) // Mock out Rekor client var mClient client.Rekor mClient.Entries = &mock.EntriesClient{ Entries: []*models.LogEntry{{"123": models.LogEntryAnon{ LogIndex: swag.Int64(123), }}}, } testSigner := NewSigner(payloadSigner, &mClient) testPayload := "test payload" ociSig, pub, err := testSigner.Sign(context.Background(), strings.NewReader(testPayload)) if err != nil { t.Fatalf("Sign() returned error: %v", err) } // Verify that the wrapped signer was called. verifier, err := signature.LoadVerifier(pub, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadVerifier(pub) returned error: %v", err) } sig, err := ociSig.Signature() if err != nil { t.Fatalf("ociSig.Signature() returned error: %v", err) } gotPayload, err := ociSig.Payload() if err != nil { t.Fatalf("ociSig.Payload() returned error: %v", err) } if string(gotPayload) != testPayload { t.Errorf("ociSig.Payload() returned %q, wanted %q", string(gotPayload), testPayload) } if err = verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(gotPayload)); err != nil { t.Errorf("VerifySignature() returned error: %v", err) } } cosign-2.5.0/internal/pkg/cosign/sign.go000066400000000000000000000017551477503325500201710ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "crypto" "io" "github.com/sigstore/cosign/v2/pkg/oci" ) // Signer signs payloads in the form of `oci.Signature`s type Signer interface { // Sign signs the given payload, returning the results as an `oci.Signature` which can be verified using the returned `crypto.PublicKey`. Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) } cosign-2.5.0/internal/pkg/cosign/tsa/000077500000000000000000000000001477503325500174615ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/tsa/client/000077500000000000000000000000001477503325500207375ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/tsa/client/client.go000066400000000000000000000124771477503325500225570ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package client import ( "bytes" "crypto/tls" "crypto/x509" "fmt" "io" "net" "net/http" "os" "time" "github.com/digitorus/timestamp" ) // TimestampAuthorityClient should be implemented by clients that want to request timestamp responses type TimestampAuthorityClient interface { GetTimestampResponse(tsq []byte) ([]byte, error) } // TimestampAuthorityClient provides a client to query RFC 3161 timestamp authorities type TimestampAuthorityClientImpl struct { TimestampAuthorityClient // URL is the path to the API to request timestamp responses URL string // CACert is the filepath to the PEM-encoded CA certificate for the connection to the TSA server CACert string // Cert is the filepath to the PEM-encoded certificate for the connection to the TSA server Cert string // Cert is the filepath to the PEM-encoded key corresponding to the certificate for the connection to the TSA server Key string // ServerName is the expected SAN value in the server's certificate - used for https://pkg.go.dev/crypto/tls#Config.ServerName ServerName string // Timeout is the request timeout Timeout time.Duration } const defaultTimeout = 10 * time.Second func getHTTPTransport(cacertFilename, certFilename, keyFilename, serverName string, timeout time.Duration) (http.RoundTripper, error) { if timeout == 0 { timeout = defaultTimeout } tr := &http.Transport{ TLSClientConfig: &tls.Config{ CipherSuites: []uint16{ // TLS 1.3 cipher suites. tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384, tls.TLS_CHACHA20_POLY1305_SHA256, }, MinVersion: tls.VersionTLS13, SessionTicketsDisabled: true, }, // the rest of default settings are copied verbatim from https://golang.org/pkg/net/http/#DefaultTransport // to minimize surprises for the users. Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: timeout, KeepAlive: 30 * time.Second, DualStack: true, }).DialContext, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } var pool *x509.CertPool if cacertFilename != "" { f, err := os.Open(cacertFilename) if err != nil { return nil, err } defer f.Close() caCertBytes, err := io.ReadAll(f) if err != nil { return nil, fmt.Errorf("unable to read CA certs from %s: %w", cacertFilename, err) } pool = x509.NewCertPool() if !pool.AppendCertsFromPEM(caCertBytes) { return nil, fmt.Errorf("no valid CA certs found in %s", cacertFilename) } tr.TLSClientConfig.RootCAs = pool } if certFilename != "" && keyFilename != "" { cert, err := tls.LoadX509KeyPair(certFilename, keyFilename) if err != nil { return nil, fmt.Errorf("unable to read CA certs from cert %s, key %s: %w", certFilename, keyFilename, err) } tr.TLSClientConfig.Certificates = []tls.Certificate{cert} } if serverName != "" { tr.TLSClientConfig.ServerName = serverName } return tr, nil } // GetTimestampResponse sends a timestamp query to a timestamp authority, returning a timestamp response. // The query and response are defined by RFC 3161. func (t *TimestampAuthorityClientImpl) GetTimestampResponse(tsq []byte) ([]byte, error) { client := http.Client{ Timeout: t.Timeout, } // if mTLS-related fields are set, create a custom Transport for the Client if t.CACert != "" || t.Cert != "" { tr, err := getHTTPTransport(t.CACert, t.Cert, t.Key, t.ServerName, t.Timeout) if err != nil { return nil, err } client.Transport = tr } req, err := http.NewRequest("POST", t.URL, bytes.NewReader(tsq)) if err != nil { return nil, fmt.Errorf("error creating HTTP request: %w", err) } req.Header.Set("Content-Type", "application/timestamp-query") tsr, err := client.Do(req) if err != nil { return nil, fmt.Errorf("error making request to timestamp authority: %w", err) } if tsr.StatusCode != 200 && tsr.StatusCode != 201 { return nil, fmt.Errorf("request to timestamp authority failed with status code %d", tsr.StatusCode) } resp, err := io.ReadAll(tsr.Body) if err != nil { return nil, fmt.Errorf("error reading timestamp response: %w", err) } // validate that the timestamp response is parseable ts, err := timestamp.ParseResponse(resp) if err != nil { return nil, err } fmt.Fprintln(os.Stderr, "Timestamp fetched with time: ", ts.Time) return resp, nil } func NewTSAClient(url string) *TimestampAuthorityClientImpl { return &TimestampAuthorityClientImpl{URL: url, Timeout: defaultTimeout} } func NewTSAClientMTLS(url, cacert, cert, key, serverName string) *TimestampAuthorityClientImpl { return &TimestampAuthorityClientImpl{ URL: url, CACert: cacert, Cert: cert, Key: key, ServerName: serverName, Timeout: defaultTimeout, } } cosign-2.5.0/internal/pkg/cosign/tsa/mock/000077500000000000000000000000001477503325500204125ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/cosign/tsa/mock/mock_tsa_client.go000066400000000000000000000063671477503325500241130ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mock import ( "crypto" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/asn1" "fmt" "time" "github.com/digitorus/timestamp" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/timestamp-authority/pkg/signer" ) // TSAClient creates RFC3161 timestamps and implements client.TimestampAuthority. // Messages to sign can either be provided in the initializer or through the request. // Time can be provided in the initializer, or defaults to time.Now(). // All other timestamp parameters are hardcoded. type TSAClient struct { client.TimestampAuthorityClient Signer crypto.Signer CertChain []*x509.Certificate Time time.Time Message []byte } // TSAClientOptions provide customization for the mock TSA client. type TSAClientOptions struct { // Time is an optional timestamp. Default is time.Now(). Time time.Time // Message is the pre-hashed message to sign over, typically a raw signature. Message []byte // Signer is an optional signer created out of band. Client creates one if not set. Signer crypto.Signer } func NewTSAClient(o TSAClientOptions) (*TSAClient, error) { sv := o.Signer if sv == nil { var err error sv, _, err = signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256) if err != nil { return nil, err } } certChain, err := signer.NewTimestampingCertWithChain(sv) if err != nil { return nil, fmt.Errorf("generating timestamping cert chain: %w", err) } return &TSAClient{ Signer: sv, CertChain: certChain, Time: o.Time, Message: o.Message, }, nil } func (c *TSAClient) GetTimestampResponse(tsq []byte) ([]byte, error) { var hashAlg crypto.Hash var hashedMessage []byte if tsq != nil { req, err := timestamp.ParseRequest(tsq) if err != nil { return nil, err } hashAlg = req.HashAlgorithm hashedMessage = req.HashedMessage } else { hashAlg = crypto.SHA256 h := hashAlg.New() h.Write(c.Message) hashedMessage = h.Sum(nil) } nonce, err := cryptoutils.GenerateSerialNumber() if err != nil { return nil, err } duration, _ := time.ParseDuration("1s") tsStruct := timestamp.Timestamp{ HashAlgorithm: hashAlg, HashedMessage: hashedMessage, Nonce: nonce, Policy: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 2}, Ordering: false, Accuracy: duration, Qualified: false, AddTSACertificate: true, } if c.Time.IsZero() { tsStruct.Time = time.Now() } else { tsStruct.Time = c.Time } return tsStruct.CreateResponseWithOpts(c.CertChain[0], c.Signer, crypto.SHA256) } cosign-2.5.0/internal/pkg/cosign/tsa/signer.go000066400000000000000000000066531477503325500213110ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tsa import ( "bytes" "context" "crypto" "fmt" "io" "strconv" "strings" "github.com/digitorus/timestamp" "github.com/sigstore/cosign/v2/internal/pkg/cosign" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/mutate" "github.com/sigstore/sigstore/pkg/cryptoutils" ) // GetTimestampedSignature queries a timestamp authority to fetch an RFC3161 timestamp. sigBytes is an // opaque blob, but is typically a signature over an artifact. func GetTimestampedSignature(sigBytes []byte, tsaClient client.TimestampAuthorityClient) ([]byte, error) { requestBytes, err := createTimestampAuthorityRequest(sigBytes, crypto.SHA256, "") if err != nil { return nil, fmt.Errorf("error creating timestamp request: %w", err) } return tsaClient.GetTimestampResponse(requestBytes) } // signerWrapper calls a wrapped, inner signer then uploads either the Cert or Pub(licKey) of the results to Rekor, then adds the resulting `Bundle` type signerWrapper struct { inner cosign.Signer tsaClient client.TimestampAuthorityClient } var _ cosign.Signer = (*signerWrapper)(nil) // Sign implements `cosign.Signer` func (rs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) { sig, pub, err := rs.inner.Sign(ctx, payload) if err != nil { return nil, nil, err } // create timestamp over raw bytes of signature rawSig, err := sig.Signature() if err != nil { return nil, nil, err } // fetch rfc3161 timestamp from timestamp authority responseBytes, err := GetTimestampedSignature(rawSig, rs.tsaClient) if err != nil { return nil, nil, err } bundle := bundle.TimestampToRFC3161Timestamp(responseBytes) newSig, err := mutate.Signature(sig, mutate.WithRFC3161Timestamp(bundle)) if err != nil { return nil, nil, err } return newSig, pub, nil } func createTimestampAuthorityRequest(artifactBytes []byte, hash crypto.Hash, policyStr string) ([]byte, error) { reqOpts := ×tamp.RequestOptions{ Hash: hash, Certificates: true, // if the timestamp response should contain the leaf certificate } // specify a pseudo-random nonce in the request nonce, err := cryptoutils.GenerateSerialNumber() if err != nil { return nil, err } reqOpts.Nonce = nonce if policyStr != "" { var oidInts []int for _, v := range strings.Split(policyStr, ".") { i, _ := strconv.Atoi(v) oidInts = append(oidInts, i) } reqOpts.TSAPolicyOID = oidInts } return timestamp.CreateRequest(bytes.NewReader(artifactBytes), reqOpts) } // NewSigner returns a `cosign.Signer` which uploads the signature to a TSA func NewSigner(inner cosign.Signer, tsaClient client.TimestampAuthorityClient) cosign.Signer { return &signerWrapper{ inner: inner, tsaClient: tsaClient, } } cosign-2.5.0/internal/pkg/cosign/tsa/signer_test.go000066400000000000000000000046221477503325500223420ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tsa import ( "bytes" "context" "crypto" "strings" "testing" "time" "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/mock" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/sigstore/pkg/signature" ) func mustGetNewSigner(t *testing.T) signature.Signer { t.Helper() priv, err := cosign.GeneratePrivateKey() if err != nil { t.Fatalf("cosign.GeneratePrivateKey() failed: %v", err) } s, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadECDSASignerVerifier(key, crypto.SHA256) failed: %v", err) } return s } func TestSigner(t *testing.T) { // Need real cert and chain payloadSigner := payload.NewSigner(mustGetNewSigner(t)) tsaClient, err := mock.NewTSAClient((mock.TSAClientOptions{Time: time.Now()})) if err != nil { t.Fatal(err) } testSigner := NewSigner(payloadSigner, tsaClient) testPayload := "test payload" ociSig, pub, err := testSigner.Sign(context.Background(), strings.NewReader(testPayload)) if err != nil { t.Fatalf("Sign() returned error: %v", err) } // Verify that the wrapped signer was called. verifier, err := signature.LoadVerifier(pub, crypto.SHA256) if err != nil { t.Fatalf("signature.LoadVerifier(pub) returned error: %v", err) } sig, err := ociSig.Signature() if err != nil { t.Fatalf("ociSig.Signature() returned error: %v", err) } gotPayload, err := ociSig.Payload() if err != nil { t.Fatalf("ociSig.Payload() returned error: %v", err) } if string(gotPayload) != testPayload { t.Errorf("ociSig.Payload() returned %q, wanted %q", string(gotPayload), testPayload) } if err = verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(gotPayload)); err != nil { t.Errorf("VerifySignature() returned error: %v", err) } } cosign-2.5.0/internal/pkg/cosign/tsa/utils.go000066400000000000000000000026211477503325500211510ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tsa import ( "bytes" "crypto/x509" "github.com/sigstore/sigstore/pkg/cryptoutils" ) // SplitPEMCertificateChain returns a list of leaf (non-CA) certificates, a certificate pool for // intermediate CA certificates, and a certificate pool for root CA certificates func SplitPEMCertificateChain(pem []byte) (leaves, intermediates, roots []*x509.Certificate, err error) { certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pem) if err != nil { return nil, nil, nil, err } for _, cert := range certs { if !cert.IsCA { leaves = append(leaves, cert) } else { // root certificates are self-signed if bytes.Equal(cert.RawSubject, cert.RawIssuer) { roots = append(roots, cert) } else { intermediates = append(intermediates, cert) } } } return leaves, intermediates, roots, nil } cosign-2.5.0/internal/pkg/cosign/tsa/utils_test.go000066400000000000000000000040031477503325500222040ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tsa import ( "crypto/x509" "reflect" "testing" "github.com/sigstore/cosign/v2/test" "github.com/sigstore/sigstore/pkg/cryptoutils" ) func TestSplitPEMCertificateChain(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) rootCert2, rootKey2, _ := test.GenerateRootCa() subCert2, subKey2, _ := test.GenerateSubordinateCa(rootCert2, rootKey2) leafCert2, _, _ := test.GenerateLeafCert("subject", "oidc-issuer", subCert2, subKey2) expectedLeaves := []*x509.Certificate{leafCert, leafCert2} expectedInts := []*x509.Certificate{subCert, subCert2} expectedRoots := []*x509.Certificate{rootCert, rootCert2} pem, err := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{rootCert, subCert, leafCert, rootCert2, subCert2, leafCert2}) if err != nil { t.Fatalf("unexpected error marshalling certificates to PEM: %v", err) } leaves, intermediates, roots, err := SplitPEMCertificateChain(pem) if err != nil { t.Fatalf("unexpected error splitting certificates from PEM: %v", err) } if !reflect.DeepEqual(leaves, expectedLeaves) { t.Fatal("leaf certificates were not equal") } if !reflect.DeepEqual(intermediates, expectedInts) { t.Fatal("intermediates were not equal") } if !reflect.DeepEqual(roots, expectedRoots) { t.Fatal("roots were not equal") } } cosign-2.5.0/internal/pkg/now/000077500000000000000000000000001477503325500162135ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/now/now.go000066400000000000000000000020651477503325500173500ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package now import ( "fmt" "os" "strconv" "time" ) // Now returns SOURCE_DATE_EPOCH or time.Now(). func Now() (time.Time, error) { // nolint epoch := os.Getenv("SOURCE_DATE_EPOCH") if epoch == "" { return time.Now(), nil } seconds, err := strconv.ParseInt(epoch, 10, 64) if err != nil { return time.Now(), fmt.Errorf("SOURCE_DATE_EPOCH should be the number of seconds since January 1st 1970, 00:00 UTC, got: %w", err) } return time.Unix(seconds, 0), nil } cosign-2.5.0/internal/pkg/oci/000077500000000000000000000000001477503325500161625ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/oci/remote/000077500000000000000000000000001477503325500174555ustar00rootroot00000000000000cosign-2.5.0/internal/pkg/oci/remote/remote.go000066400000000000000000000015361477503325500213040ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "fmt" ) // ArtifactType converts a attachment name (sig/sbom/att/etc.) into a valid artifactType (OCI 1.1+). func ArtifactType(attName string) string { return fmt.Sprintf("application/vnd.dev.cosign.artifact.%s.v1+json", attName) } cosign-2.5.0/internal/ui/000077500000000000000000000000001477503325500152445ustar00rootroot00000000000000cosign-2.5.0/internal/ui/env.go000066400000000000000000000045501477503325500163670ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ui import ( "bytes" "context" "io" "os" ) // An Env is the environment that the CLI exists in. // // It contains handles to STDERR and STDIN. Eventually, it will contain // configuration pertaining to the current invocation (e.g., is this a terminal // or not). // // UI methods should be defined on an Env. Then, the Env can be // changed for easy testing. The Env will be retrieved from the current // application context. type Env struct { Stderr io.Writer Stdin io.Reader } // defaultEnv returns the default environment (writing to os.Stderr and // reading from os.Stdin). func defaultEnv() *Env { return &Env{ Stderr: os.Stderr, Stdin: os.Stdin, } } type ctxKey struct{} func (c ctxKey) String() string { return "cosign/ui:env" } var ctxKeyEnv = ctxKey{} // getEnv gets the environment from ctx. // // If ctx does not contain an environment, getEnv returns the default // environment (see defaultEnvironment). func getEnv(ctx context.Context) *Env { e, ok := ctx.Value(ctxKeyEnv).(*Env) if !ok { return defaultEnv() } return e } // WithEnv adds the environment to the context. func WithEnv(ctx context.Context, e *Env) context.Context { return context.WithValue(ctx, ctxKeyEnv, e) } type WriteFunc func(string) type callbackFunc func(context.Context, WriteFunc) // RunWithTestCtx runs the provided callback in a context with the UI // environment swapped out for one that allows for easy testing and captures // STDOUT. // // The callback has access to a function that writes to the test STDIN. func RunWithTestCtx(callback callbackFunc) string { var stdin bytes.Buffer var stderr bytes.Buffer e := Env{&stderr, &stdin} ctx := WithEnv(context.Background(), &e) write := func(msg string) { stdin.WriteString(msg) } callback(ctx, write) return stderr.String() } cosign-2.5.0/internal/ui/log.go000066400000000000000000000024111477503325500163520ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ui import ( "context" "fmt" ) func (w *Env) infof(msg string, a ...any) { msg = fmt.Sprintf(msg, a...) fmt.Fprintln(w.Stderr, msg) } // Infof logs an informational message. It works like fmt.Printf, except that it // always has a trailing newline. func Infof(ctx context.Context, msg string, a ...any) { getEnv(ctx).infof(msg, a...) } func (w *Env) warnf(msg string, a ...any) { msg = fmt.Sprintf(msg, a...) fmt.Fprintf(w.Stderr, "WARNING: %s\n", msg) } // Warnf logs a warning message (prefixed by "WARNING:"). It works like // fmt.Printf, except that it always has a trailing newline. func Warnf(ctx context.Context, msg string, a ...any) { getEnv(ctx).warnf(msg, a...) } cosign-2.5.0/internal/ui/log_test.go000066400000000000000000000031661477503325500174210ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ui_test import ( "context" "testing" "github.com/sigstore/cosign/v2/internal/ui" "github.com/stretchr/testify/assert" ) type testCase struct { name string input string args []any expected string } func TestInfof(t *testing.T) { cases := []testCase{ {"basic", "foo", nil, "foo\n"}, {"multiline", "foo\nbar", nil, "foo\nbar\n"}, {"fmt", "foo: %v", []any{"bar"}, "foo: bar\n"}, } for _, tc := range cases { stderr := ui.RunWithTestCtx(func(ctx context.Context, _ ui.WriteFunc) { ui.Infof(ctx, tc.input, tc.args...) }) assert.Equal(t, tc.expected, stderr, "Bad output to STDERR") } } func TestWarnf(t *testing.T) { cases := []testCase{ {"basic", "foo", nil, "WARNING: foo\n"}, {"multiline", "foo\nbar", nil, "WARNING: foo\nbar\n"}, {"fmt", "bar: %v", []any{"baz"}, "WARNING: bar: baz\n"}, } for _, tc := range cases { stderr := ui.RunWithTestCtx(func(ctx context.Context, _ ui.WriteFunc) { ui.Warnf(ctx, tc.input, tc.args...) }) assert.Equal(t, tc.expected, stderr, "Bad output to STDERR") } } cosign-2.5.0/internal/ui/prompt.go000066400000000000000000000035651477503325500171250ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ui import ( "bufio" "context" "errors" "fmt" "io" "strings" ) type ErrPromptDeclined struct{} func (e *ErrPromptDeclined) Error() string { return "user declined the prompt" } type ErrInvalidInput struct { Got string Allowed string } func (e *ErrInvalidInput) Error() string { return fmt.Sprintf("invalid input %#v (allowed values %v)", e.Got, e.Allowed) } func newInvalidYesOrNoInput(got string) error { return &ErrInvalidInput{Got: got, Allowed: "y, n"} } func (w *Env) prompt() error { fmt.Fprint(w.Stderr, "Are you sure you would like to continue? [y/N] ") // TODO: what if it's not a terminal? r, err := bufio.NewReader(w.Stdin).ReadString('\n') if err != nil && !errors.Is(err, io.EOF) { return err } value := strings.Trim(r, "\r\n") switch strings.ToLower(value) { case "y": return nil case "": fallthrough // TODO: allow setting default=true? case "n": return &ErrPromptDeclined{} default: // TODO: allow retry on invalid input? return newInvalidYesOrNoInput(value) } } // ConfirmContinue prompts the user whether they would like to continue and // returns the parsed answer. // // If the user enters anything other than "y" or "Y", ConfirmContinue returns an // error. func ConfirmContinue(ctx context.Context) error { return getEnv(ctx).prompt() } cosign-2.5.0/internal/ui/prompt_test.go000066400000000000000000000045361477503325500201630ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ui_test import ( "bytes" "context" "errors" "testing" "github.com/sigstore/cosign/v2/internal/ui" "github.com/stretchr/testify/assert" ) func TestConfirm(t *testing.T) { cases := []struct { name string input string expected error expectedMessage string }{ {"no", "n\n", &ui.ErrPromptDeclined{}, "user declined"}, {"no-upper", "N\n", &ui.ErrPromptDeclined{}, "user declined"}, {"yes", "y\n", nil, ""}, {"yes-upper", "Y\n", nil, ""}, {"default", "\n", &ui.ErrPromptDeclined{}, "user declined"}, {"empty", "", &ui.ErrPromptDeclined{}, "user declined"}, {"invalid", "yy", &ui.ErrInvalidInput{Got: "yy", Allowed: "y, n"}, "invalid input"}, {"no-windows", "n\r\n", &ui.ErrPromptDeclined{}, "user declined"}, {"yes-windows", "y\r\n", nil, ""}, {"default-windows", "\r\n", &ui.ErrPromptDeclined{}, "user declined"}, {"invalid", "yy\r\n", &ui.ErrInvalidInput{Got: "yy", Allowed: "y, n"}, "invalid input"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { stderr := ui.RunWithTestCtx(func(ctx context.Context, write ui.WriteFunc) { write(tc.input) err := ui.ConfirmContinue(ctx) assert.EqualValues(t, tc.expected, err) if len(tc.expectedMessage) > 0 { assert.ErrorContains(t, err, "") } }) assert.Equal(t, "Are you sure you would like to continue? [y/N] ", stderr, "Bad output to STDERR") }) } } type BadReader struct{} // BadReader implements Reader. func (b *BadReader) Read(p []byte) (n int, err error) { //nolint: revive return 0, errors.New("my error") } func TestConfirmError(t *testing.T) { var stderr bytes.Buffer stdin := BadReader{} ctx := ui.WithEnv(context.Background(), &ui.Env{&stderr, &stdin}) assert.ErrorContains(t, ui.ConfirmContinue(ctx), "my error") } cosign-2.5.0/internal/ui/warnings.go000066400000000000000000000017561477503325500174340ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ui const TagReferenceMessage string = `Image reference %s uses a tag, not a digest, to identify the image to sign. This can lead you to sign a different image than the intended one. Please use a digest (example.com/ubuntu@sha256:abc123...) rather than tag (example.com/ubuntu:latest) for the input to cosign. The ability to refer to images by tag will be removed in a future release. ` cosign-2.5.0/pkg/000077500000000000000000000000001477503325500135745ustar00rootroot00000000000000cosign-2.5.0/pkg/blob/000077500000000000000000000000001477503325500145125ustar00rootroot00000000000000cosign-2.5.0/pkg/blob/load.go000066400000000000000000000056341477503325500157700ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package blob import ( "crypto/sha256" "crypto/sha512" "encoding/hex" "fmt" "io" "net/http" "os" "path/filepath" "strings" ) type UnrecognizedSchemeError struct { Scheme string } func (e *UnrecognizedSchemeError) Error() string { return fmt.Sprintf("loading URL: unrecognized scheme: %s", e.Scheme) } func LoadFileOrURL(fileRef string) ([]byte, error) { var raw []byte var err error parts := strings.SplitAfterN(fileRef, "://", 2) if len(parts) == 2 { scheme := parts[0] switch scheme { case "http://": fallthrough case "https://": // #nosec G107 resp, err := http.Get(fileRef) if err != nil { return nil, err } defer resp.Body.Close() raw, err = io.ReadAll(resp.Body) if err != nil { return nil, err } case "env://": envVar := parts[1] // Most of Cosign should use `env.LookupEnv` (see #2236) to restrict us to known environment variables // (usually `$COSIGN_*`). However, in this case, `envVar` is user-provided and not one of the allow-listed // env vars. value, found := os.LookupEnv(envVar) //nolint:forbidigo if !found { return nil, fmt.Errorf("loading URL: env var $%s not found", envVar) } raw = []byte(value) default: return nil, &UnrecognizedSchemeError{Scheme: scheme} } } else { raw, err = os.ReadFile(filepath.Clean(fileRef)) if err != nil { return nil, err } } return raw, nil } func LoadFileOrURLWithChecksum(fileRef string, checksum string) ([]byte, error) { checksumParts := strings.Split(checksum, ":") if len(checksumParts) >= 3 { return nil, fmt.Errorf("wrong checksum input format, must have at most 1 colon: %s", checksum) } checksumAlgo := sha256.New() checksumValue := checksumParts[len(checksumParts)-1] if len(checksumParts) == 2 { switch checksumParts[0] { case "sha256": // the default set above case "sha512": checksumAlgo = sha512.New() default: return nil, fmt.Errorf("unsupported checksum algorithm: %s", checksumParts[0]) } } fileContent, err := LoadFileOrURL(fileRef) if err != nil { return nil, err } checksumAlgo.Write(fileContent) computedChecksum := hex.EncodeToString(checksumAlgo.Sum(nil)) if computedChecksum != checksumValue { return nil, fmt.Errorf("incorrect checksum for file %s: expected %s but got %s", fileRef, checksumValue, computedChecksum) } return fileContent, nil } cosign-2.5.0/pkg/blob/load_test.go000066400000000000000000000104061477503325500170200ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package blob import ( "bytes" "net/http" "net/http/httptest" "os" "path" "runtime" "strings" "testing" ) func TestLoadFile(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("Skipping on Windows due to https://github.com/golang/go/issues/51442") } temp := t.TempDir() fname := "filename.txt" path := path.Join(temp, fname) data := []byte("test") defer os.Remove(path) os.WriteFile(path, data, 0400) // absolute path actual, err := LoadFileOrURL(path) if err != nil { t.Errorf("Reading from absolute path %s failed: %v", path, err) } else if !bytes.Equal(actual, data) { t.Errorf("LoadFileOrURL(absolute path) = '%s'; want '%s'", actual, data) } if err = os.Chdir(temp); err != nil { t.Fatalf("Chdir('%s'): %v", temp, err) } actual, err = LoadFileOrURL(fname) if err != nil { t.Errorf("Reading from relative path %s failed: %v", fname, err) } else if !bytes.Equal(actual, data) { t.Errorf("LoadFileOrURL(relative path) = '%s'; want '%s'", actual, data) } } func TestLoadURL(t *testing.T) { data := []byte("test") server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.Write(data) })) defer server.Close() actual, err := LoadFileOrURL(server.URL) if err != nil { t.Errorf("Reading from HTTP failed: %v", err) } else if !bytes.Equal(actual, data) { t.Errorf("LoadFileOrURL(HTTP) = '%s'; want '%s'", actual, data) } os.Setenv("MY_ENV_VAR", string(data)) actual, err = LoadFileOrURL("env://MY_ENV_VAR") if err != nil { t.Errorf("Reading from environment failed: %v", err) } else if !bytes.Equal(actual, data) { t.Errorf("LoadFileOrURL(env) = '%s'; want '%s'", actual, data) } os.Setenv("MY_ENV_VAR", "") actual, err = LoadFileOrURL("env://MY_ENV_VAR") if err != nil { t.Errorf("Reading from environment failed: %v", err) } else if !bytes.Equal(actual, make([]byte, 0)) { t.Errorf("LoadFileOrURL(env) = '%s'; should be empty", actual) } os.Unsetenv("MY_ENV_VAR") _, err = LoadFileOrURL("env://MY_ENV_VAR") if err == nil { t.Error("LoadFileOrURL(): expected error for unset env var") } _, err = LoadFileOrURL("invalid://url") if err == nil { t.Error("LoadFileOrURL(): expected error for invalid scheme") } } func TestLoadURLWithChecksum(t *testing.T) { data := []byte("test") server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { rw.Write(data) })) defer server.Close() // default behavior with sha256 actual, err := LoadFileOrURLWithChecksum( server.URL, "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", ) if err != nil { t.Errorf("Reading from HTTP failed: %v", err) } else if !bytes.Equal(actual, data) { t.Errorf("LoadFileOrURL(HTTP) = '%s'; want '%s'", actual, data) } // override checksum algo to sha512 actual, err = LoadFileOrURLWithChecksum( server.URL, "sha512:ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", ) if err != nil { t.Errorf("Reading from HTTP failed: %v", err) } else if !bytes.Equal(actual, data) { t.Errorf("LoadFileOrURL(HTTP) = '%s'; want '%s'", actual, data) } // ensure it fails with the wrong checksum _, err = LoadFileOrURLWithChecksum( server.URL, "certainly not a correct checksum value", ) if err == nil || !strings.Contains(err.Error(), "incorrect checksum") { t.Errorf("Expected an 'incorrect checksum' error, got: %v", err) } // ensure it fails with incorrect algorithm _, err = LoadFileOrURLWithChecksum( server.URL, "sha321123:foobar", ) if err == nil || !strings.Contains(err.Error(), "unsupported checksum") { t.Errorf("Expected an 'unsupported checksum' error, got: %v", err) } } cosign-2.5.0/pkg/cosign/000077500000000000000000000000001477503325500150565ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/attestation/000077500000000000000000000000001477503325500174155ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/attestation/attestation.go000066400000000000000000000233601477503325500223070ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attestation import ( "encoding/json" "fmt" "io" "reflect" "strings" "time" "github.com/in-toto/in-toto-golang/in_toto" slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" ) const ( // CosignCustomProvenanceV01 specifies the type of the Predicate. CosignCustomProvenanceV01 = "https://cosign.sigstore.dev/attestation/v1" // CosignVulnProvenanceV01 specifies the type of VulnerabilityScan Predicate CosignVulnProvenanceV01 = "https://cosign.sigstore.dev/attestation/vuln/v1" // OpenVexNamespace holds the URI of the OpenVEX context to identify its // predicate type. More info about the specification can be found at // https://github.com/openvex/spec and the attestation spec is found here: // https://github.com/openvex/spec/blob/main/ATTESTING.md OpenVexNamespace = "https://openvex.dev/ns" ) // CosignPredicate specifies the format of the Custom Predicate. type CosignPredicate struct { Data interface{} Timestamp string } // VulnPredicate specifies the format of the Vulnerability Scan Predicate type CosignVulnPredicate struct { Invocation Invocation `json:"invocation"` Scanner Scanner `json:"scanner"` Metadata Metadata `json:"metadata"` } // I think this will be moving to upstream in-toto in the fullness of time // but creating it here for now so that we have a way to deserialize it // as a InToto Statement // https://github.com/in-toto/attestation/issues/58 type CosignVulnStatement struct { in_toto.StatementHeader Predicate CosignVulnPredicate `json:"predicate"` } type Invocation struct { Parameters interface{} `json:"parameters"` URI string `json:"uri"` EventID string `json:"event_id"` BuilderID string `json:"builder.id"` } type DB struct { URI string `json:"uri"` Version string `json:"version"` } type Scanner struct { URI string `json:"uri"` Version string `json:"version"` DB DB `json:"db"` Result interface{} `json:"result"` } type Metadata struct { ScanStartedOn time.Time `json:"scanStartedOn"` ScanFinishedOn time.Time `json:"scanFinishedOn"` } // GenerateOpts specifies the options of the Statement generator. type GenerateOpts struct { // Predicate is the source of bytes (e.g. a file) to use as the statement's predicate. Predicate io.Reader // Type is the pre-defined enums (provenance|link|spdx). // default: custom Type string // Digest of the Image reference. Digest string // Repo context of the reference. Repo string // Function to return the time to set Time func() time.Time } // GenerateStatement returns an in-toto statement based on the provided // predicate type (custom|slsaprovenance|slsaprovenance02|slsaprovenance1|spdx|spdxjson|cyclonedx|link). func GenerateStatement(opts GenerateOpts) (interface{}, error) { predicate, err := io.ReadAll(opts.Predicate) if err != nil { return nil, err } switch opts.Type { case "slsaprovenance": return generateSLSAProvenanceStatementSLSA02(predicate, opts.Digest, opts.Repo) case "slsaprovenance02": return generateSLSAProvenanceStatementSLSA02(predicate, opts.Digest, opts.Repo) case "slsaprovenance1": return generateSLSAProvenanceStatementSLSA1(predicate, opts.Digest, opts.Repo) case "spdx": return generateSPDXStatement(predicate, opts.Digest, opts.Repo, false) case "spdxjson": return generateSPDXStatement(predicate, opts.Digest, opts.Repo, true) case "cyclonedx": return generateCycloneDXStatement(predicate, opts.Digest, opts.Repo) case "link": return generateLinkStatement(predicate, opts.Digest, opts.Repo) case "vuln": return generateVulnStatement(predicate, opts.Digest, opts.Repo) case "openvex": return generateOpenVexStatement(predicate, opts.Digest, opts.Repo) default: stamp := timestamp(opts) predicateType := customType(opts) return generateCustomStatement(predicate, predicateType, opts.Digest, opts.Repo, stamp) } } func generateVulnStatement(predicate []byte, digest string, repo string) (interface{}, error) { var vuln CosignVulnPredicate err := json.Unmarshal(predicate, &vuln) if err != nil { return nil, err } return in_toto.Statement{ StatementHeader: generateStatementHeader(digest, repo, CosignVulnProvenanceV01), Predicate: vuln, }, nil } func timestamp(opts GenerateOpts) string { if opts.Time == nil { opts.Time = time.Now } now := opts.Time() return now.UTC().Format(time.RFC3339) } func customType(opts GenerateOpts) string { if opts.Type != "custom" { return opts.Type } return CosignCustomProvenanceV01 } func generateStatementHeader(digest, repo, predicateType string) in_toto.StatementHeader { return in_toto.StatementHeader{ Type: in_toto.StatementInTotoV01, PredicateType: predicateType, Subject: []in_toto.Subject{ { Name: repo, Digest: map[string]string{ "sha256": digest, }, }, }, } } func generateCustomStatement(rawPayload []byte, customType, digest, repo, timestamp string) (interface{}, error) { payload, err := generateCustomPredicate(rawPayload, customType, timestamp) if err != nil { return nil, err } return in_toto.Statement{ StatementHeader: generateStatementHeader(digest, repo, customType), Predicate: payload, }, nil } func generateCustomPredicate(rawPayload []byte, customType, timestamp string) (interface{}, error) { if customType == CosignCustomProvenanceV01 { return &CosignPredicate{ Data: string(rawPayload), Timestamp: timestamp, }, nil } var result map[string]interface{} if err := json.Unmarshal(rawPayload, &result); err != nil { return nil, fmt.Errorf("invalid JSON payload for predicate type %s: %w", customType, err) } return result, nil } func generateSLSAProvenanceStatementSLSA02(rawPayload []byte, digest string, repo string) (interface{}, error) { var predicate slsa02.ProvenancePredicate err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(predicate)) if err != nil { return nil, fmt.Errorf("provenance predicate: %w", err) } err = json.Unmarshal(rawPayload, &predicate) if err != nil { return "", fmt.Errorf("unmarshal Provenance predicate: %w", err) } return in_toto.ProvenanceStatementSLSA02{ StatementHeader: generateStatementHeader(digest, repo, slsa02.PredicateSLSAProvenance), Predicate: predicate, }, nil } func generateSLSAProvenanceStatementSLSA1(rawPayload []byte, digest string, repo string) (interface{}, error) { var predicate slsa1.ProvenancePredicate err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(predicate)) if err != nil { return nil, fmt.Errorf("provenance predicate: %w", err) } err = json.Unmarshal(rawPayload, &predicate) if err != nil { return "", fmt.Errorf("unmarshal Provenance predicate: %w", err) } return in_toto.ProvenanceStatementSLSA1{ StatementHeader: generateStatementHeader(digest, repo, slsa1.PredicateSLSAProvenance), Predicate: predicate, }, nil } func generateLinkStatement(rawPayload []byte, digest string, repo string) (interface{}, error) { var link in_toto.Link err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(link)) if err != nil { return nil, fmt.Errorf("link statement: %w", err) } err = json.Unmarshal(rawPayload, &link) if err != nil { return "", fmt.Errorf("unmarshal Link statement: %w", err) } return in_toto.LinkStatement{ StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateLinkV1), Predicate: link, }, nil } func generateOpenVexStatement(rawPayload []byte, digest string, repo string) (interface{}, error) { var data interface{} if err := json.Unmarshal(rawPayload, &data); err != nil { return nil, err } return in_toto.Statement{ StatementHeader: generateStatementHeader(digest, repo, OpenVexNamespace), Predicate: data, }, nil } func generateSPDXStatement(rawPayload []byte, digest string, repo string, parseJSON bool) (interface{}, error) { var data interface{} if parseJSON { if err := json.Unmarshal(rawPayload, &data); err != nil { return nil, err } } else { data = string(rawPayload) } return in_toto.SPDXStatement{ StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateSPDX), Predicate: data, }, nil } func generateCycloneDXStatement(rawPayload []byte, digest string, repo string) (interface{}, error) { var data interface{} if err := json.Unmarshal(rawPayload, &data); err != nil { return nil, err } return in_toto.SPDXStatement{ StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateCycloneDX), Predicate: data, }, nil } func checkRequiredJSONFields(rawPayload []byte, typ reflect.Type) error { var tmp map[string]interface{} if err := json.Unmarshal(rawPayload, &tmp); err != nil { return err } // Create list of json tags, e.g. `json:"_type"` attributeCount := typ.NumField() allFields := make([]string, 0) for i := 0; i < attributeCount; i++ { jsonTagFields := strings.SplitN(typ.Field(i).Tag.Get("json"), ",", 2) if len(jsonTagFields) < 2 { allFields = append(allFields, jsonTagFields[0]) } } // Assert that there's a key in the passed map for each tag for _, field := range allFields { if _, ok := tmp[field]; !ok { return fmt.Errorf("required field %s missing", field) } } return nil } cosign-2.5.0/pkg/cosign/attestation/fuzz_test.go000066400000000000000000000026041477503325500220030ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attestation import ( "bytes" "testing" ) func FuzzGenerateStatement(f *testing.F) { f.Fuzz(func(_ *testing.T, predicate []byte, digest, repo string, stmttType int) { var statementType string switch stmttType % 9 { case 0: statementType = "slsaprovenance" case 1: statementType = "slsaprovenance02" case 2: statementType = "slsaprovenance1" case 3: statementType = "spdx" case 4: statementType = "spdxjson" case 5: statementType = "cyclonedx" case 6: statementType = "link" case 7: statementType = "vuln" case 8: statementType = "openvex" default: statementType = "" } opts := GenerateOpts{ Predicate: bytes.NewReader(predicate), Type: statementType, Digest: digest, Repo: repo, } GenerateStatement(opts) }) } cosign-2.5.0/pkg/cosign/bundle/000077500000000000000000000000001477503325500163275ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/bundle/protobundle.go000066400000000000000000000041621477503325500212160ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bundle import ( protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" protorekor "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/tle" ) const bundleV03MediaType = "application/vnd.dev.sigstore.bundle.v0.3+json" func MakeProtobufBundle(hint string, rawCert []byte, rekorEntry *models.LogEntryAnon, timestampBytes []byte) (*protobundle.Bundle, error) { bundle := &protobundle.Bundle{MediaType: bundleV03MediaType} if hint != "" { bundle.VerificationMaterial = &protobundle.VerificationMaterial{ Content: &protobundle.VerificationMaterial_PublicKey{ PublicKey: &protocommon.PublicKeyIdentifier{ Hint: hint, }, }, } } else if len(rawCert) > 0 { bundle.VerificationMaterial = &protobundle.VerificationMaterial{ Content: &protobundle.VerificationMaterial_Certificate{ Certificate: &protocommon.X509Certificate{ RawBytes: rawCert, }, }, } } if len(timestampBytes) > 0 { bundle.VerificationMaterial.TimestampVerificationData = &protobundle.TimestampVerificationData{ Rfc3161Timestamps: []*protocommon.RFC3161SignedTimestamp{ {SignedTimestamp: timestampBytes}, }, } } if rekorEntry != nil { tlogEntry, err := tle.GenerateTransparencyLogEntry(*rekorEntry) if err != nil { return nil, err } bundle.VerificationMaterial.TlogEntries = []*protorekor.TransparencyLogEntry{tlogEntry} } return bundle, nil } cosign-2.5.0/pkg/cosign/bundle/protobundle_test.go000066400000000000000000000124561477503325500222620ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bundle import ( "testing" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" "github.com/sigstore/rekor/pkg/generated/models" _ "github.com/sigstore/rekor/pkg/types/hashedrekord" _ "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" ) func TestMakeProtobufBundle(t *testing.T) { testCases := []struct { name string hint string rawCert []byte rekorEntry *models.LogEntryAnon timestampBytes []byte }{ { name: "hint with timestamp", hint: "asdf", rawCert: []byte{}, rekorEntry: nil, timestampBytes: []byte("timestamp"), }, { name: "only cert", hint: "", rawCert: []byte("cert stuff"), rekorEntry: nil, timestampBytes: []byte{}, }, { name: "cert with rekor entry", hint: "", rawCert: []byte("cert stuff"), rekorEntry: &models.LogEntryAnon{ Body: "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2MmQwOGYyOGM2OWNhZGE3YjQyYTQ1Nzk0YjQ3ZWU2YzgxYTdkZmE3MTY4NDZiMzljODhmMGFkMTljMjA2OTk3In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJQm14U0N1TW1HSzhNQWRMd1FWZ21TZjVXKzlkdU5iQXN1cUNQNlNucUxCUkFpRUFvNGtGRVdDTG9HcTVUaysrUEhtTEgrb3N1emVTRjN4OTdBbmVicTRlbVRvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUk1ha05EUVhKVFowRjNTVUpCWjBsVVRWQkRlVXdyYmxOb2MycHdaa2hZYUZkYVRVWkNUVUZIUlVSQlMwSm5aM0ZvYTJwUFVGRlJSRUY2UVhFS1RWSlZkMFYzV1VSV1VWRkxSWGQ0ZW1GWFpIcGtSemw1V2xNMWExcFlXWGhGVkVGUVFtZE9Wa0pCVFZSRFNFNXdXak5PTUdJelNteE5RalJZUkZSSmVRcE5SRWwzVFdwRmQwNUVXWGhOVm05WVJGUkplVTFFU1hkTmFrVjNUbFJaZUUxR2IzZEZla1ZTVFVFNFIwRXhWVVZEYUUxSll6SnNibU16VW5aamJWVjNDbGRVUVZSQ1oyTnhhR3RxVDFCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlVVMVJ2VEhWS2N5OTFSV05IU2tRME5VWmFiVE5wWmxKTU4yOXVRVWNLWlZaNWJuWkhVbmN6WnpKMU0wbFhTREZuU2tSamNERjRSWFI2UVZCUWJYQmhlVGRtTmxCNE1XeFpNa0ZyWnpsMGEyb3dRa1J2UTNkdk5FbENlbXBEUXdwQlkyOTNSR2RaUkZaU01GQkJVVWd2UWtGUlJFRm5aVUZOUWsxSFFURlZaRXBSVVUxTlFXOUhRME56UjBGUlZVWkNkMDFFVFVGM1IwRXhWV1JGZDBWQ0NpOTNVVU5OUVVGM1NGRlpSRlpTTUU5Q1FsbEZSazlSYTNZNVoyMVpXVFpWU0doQ1pWSnJMMWx4VlVsaU1WRldiMDFDT0VkQk1WVmtTWGRSV1UxQ1lVRUtSa1pxUVVoc0sxSlNZVlp0Y1ZoeVRXdExSMVJKZEVGeGVHTllOazFIVFVkQk1WVmtSVkZTWTAxR2NVZFhSMmd3WkVoQ2VrOXBPSFphTW13d1lVaFdhUXBNYlU1MllsTTVjbGx1VGpCTU0xSnNZMjVLYUZwdE9YbGlVekZ5WkZkS2JHTXpVbWhaTW5OMlRHMWtjR1JIYURGWmFUa3pZak5LY2xwdGVIWmtNMDEyQ21KWFJuQmlhVFUxWWxkNFFXTnRWbTFqZVRsdldsZEdhMk41T1hSWldFNHdXbGhKZDBWbldVdExkMWxDUWtGSFJIWjZRVUpCWjFGRlkwaFdlbUZFUVcwS1FtZHZja0puUlVWQldVOHZUVUZGUmtKQ2FISlpiazR3VEROU2JHTnVTbWhhYlRsNVlsTXhjbVJYU214ak0xSm9XVEp6ZDA1bldVdExkMWxDUWtGSFJBcDJla0ZDUVhkUmIxcHFSVEZPVjFGNVdXMUplRTU2V210TlZFVTFXV3BHYlU5VVNUSk9WRlV6V1hwTk5WbDZTbWxQVkdzMVdtcFNhRnBxWjNkWmVrRTFDa0puYjNKQ1owVkZRVmxQTDAxQlJVSkNRM1J2WkVoU2QyTjZiM1pNTTFKMllUSldkVXh0Um1wa1IyeDJZbTVOZFZveWJEQmhTRlpwWkZoT2JHTnRUbllLWW01U2JHSnVVWFZaTWpsMFRVSTRSME5wYzBkQlVWRkNaemM0ZDBGUldVVkZXRXBzV201TmRtRkhWbWhhU0UxMllsZEdlbVJIVm5sTlEwRkhRMmx6UndwQlVWRkNaemM0ZDBGUlVVVkZhMG94WVZkNGEwbEdVbXhqTTFGblZVaFdhV0pIYkhwaFJFRkxRbWRuY1docmFrOVFVVkZFUVhkT2IwRkVRbXhCYWtWQkNtdDJORTFLYUdGRGFFMUJaMHBWVTNWWll6bFBWRWt3WTB0bU9XTnlObU14Y1RreVYyOXFMM1ZsV0RKRFR6Z3JMMDQyU25SM1FVNTRVSElyTjNWNlpGQUtRV3BDYVhwR2NHZEVMelJzWW5aa1NuRnplWE5HYlVSeU1TdFNNSGhKWjI1S1N5c3JaWGROYmtKaVMxQkVMemd3VTNJelFYTTVMMWxxV1U5M05EVjRkUXA2ZVdzOUNpMHRMUzB0UlU1RUlFTkZVbFJKUmtsRFFWUkZMUzB0TFMwSyJ9fX19", IntegratedTime: swag.Int64(123), LogID: swag.String("deadbeef"), LogIndex: swag.Int64(2), Verification: &models.LogEntryAnonVerification{ InclusionProof: &models.InclusionProof{ Checkpoint: swag.String("checkpoint"), Hashes: []string{"deadbeef", "abcdefaa"}, LogIndex: swag.Int64(1), RootHash: swag.String("abcdefaa"), TreeSize: swag.Int64(2), }, SignedEntryTimestamp: strfmt.Base64("set"), }, }, timestampBytes: []byte{}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { bundle, err := MakeProtobufBundle(tc.hint, tc.rawCert, tc.rekorEntry, tc.timestampBytes) if err != nil { t.Errorf("unexpected err %s", err) } if tc.hint != "" && bundle.VerificationMaterial.GetPublicKey() == nil { t.Errorf("Verification material should be public key") } if len(tc.rawCert) > 0 && bundle.VerificationMaterial.GetCertificate() == nil { t.Errorf("Verification material should be certificate") } if tc.rekorEntry != nil && len(bundle.VerificationMaterial.GetTlogEntries()) == 0 { t.Errorf("Verification material should contain Tlog Entries") } if len(tc.timestampBytes) > 0 && bundle.VerificationMaterial.GetTimestampVerificationData() == nil { t.Errorf("Verification material should have timestamp") } }) } } cosign-2.5.0/pkg/cosign/bundle/rekor.go000066400000000000000000000026671477503325500200130ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bundle import ( "github.com/sigstore/rekor/pkg/generated/models" ) // RekorBundle holds metadata about recording a Signature's ephemeral key to // a Rekor transparency log. type RekorBundle struct { SignedEntryTimestamp []byte Payload RekorPayload } type RekorPayload struct { Body interface{} `json:"body"` IntegratedTime int64 `json:"integratedTime"` LogIndex int64 `json:"logIndex"` LogID string `json:"logID"` } func EntryToBundle(entry *models.LogEntryAnon) *RekorBundle { if entry.Verification == nil { return nil } return &RekorBundle{ SignedEntryTimestamp: entry.Verification.SignedEntryTimestamp, Payload: RekorPayload{ Body: entry.Body, IntegratedTime: *entry.IntegratedTime, LogIndex: *entry.LogIndex, LogID: *entry.LogID, }, } } cosign-2.5.0/pkg/cosign/bundle/rekor_test.go000066400000000000000000000050621477503325500210420ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bundle import ( "encoding/base64" "reflect" "testing" "time" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" "github.com/sigstore/rekor/pkg/generated/models" ) func TestRekorBundle(t *testing.T) { testCases := []struct { name string logEntry *models.LogEntryAnon expectedRekorBundle *RekorBundle }{{ name: "tlog entry without verification - nil bundle", logEntry: &models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString([]byte("TEST")), IntegratedTime: swag.Int64(time.Now().Unix()), LogIndex: swag.Int64(0), LogID: swag.String("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"), }, expectedRekorBundle: nil, }, { name: "tlog entry with verification", logEntry: &models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString([]byte("TEST")), IntegratedTime: swag.Int64(time.Now().Unix()), LogIndex: swag.Int64(0), LogID: swag.String("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"), Verification: &models.LogEntryAnonVerification{ SignedEntryTimestamp: strfmt.Base64([]byte("signature")), InclusionProof: &models.InclusionProof{ LogIndex: swag.Int64(0), TreeSize: swag.Int64(1), RootHash: swag.String("TEST"), Hashes: []string{}, }, }, }, expectedRekorBundle: &RekorBundle{ Payload: RekorPayload{ Body: base64.StdEncoding.EncodeToString([]byte("TEST")), IntegratedTime: time.Now().Unix(), LogIndex: 0, LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", }, SignedEntryTimestamp: strfmt.Base64([]byte("signature")), }, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { gotBundle := EntryToBundle(tc.logEntry) if !reflect.DeepEqual(gotBundle, tc.expectedRekorBundle) { t.Errorf("EntryToBundle returned %v, wanted %v", gotBundle, tc.expectedRekorBundle) } }) } } cosign-2.5.0/pkg/cosign/bundle/tsa.go000066400000000000000000000024021477503325500174430ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bundle // RFC3161Timestamp holds metadata about timestamp RFC3161 verification data. type RFC3161Timestamp struct { // SignedRFC3161Timestamp contains a DER encoded TimeStampResponse. // See https://www.rfc-editor.org/rfc/rfc3161.html#section-2.4.2 // Clients MUST verify the hashed message in the message imprint, // typically using the artifact signature. SignedRFC3161Timestamp []byte } // TimestampToRFC3161Timestamp receives a base64 encoded RFC3161 timestamp. func TimestampToRFC3161Timestamp(timestampRFC3161 []byte) *RFC3161Timestamp { if timestampRFC3161 != nil { return &RFC3161Timestamp{ SignedRFC3161Timestamp: timestampRFC3161, } } return nil } cosign-2.5.0/pkg/cosign/bundle/tsa_test.go000066400000000000000000000030071477503325500205040ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bundle import ( "reflect" "testing" "github.com/go-openapi/strfmt" ) func TestRFC3161Timestamp(t *testing.T) { testCases := []struct { name string timestampRFC3161Entry []byte expectedRFC3161Timestamp *RFC3161Timestamp }{{ name: "nil timestamp entry", timestampRFC3161Entry: nil, expectedRFC3161Timestamp: nil, }, { name: "timestamp entry", timestampRFC3161Entry: strfmt.Base64([]byte("signature")), expectedRFC3161Timestamp: &RFC3161Timestamp{ SignedRFC3161Timestamp: strfmt.Base64([]byte("signature")), }, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { gotBundle := TimestampToRFC3161Timestamp(tc.timestampRFC3161Entry) if !reflect.DeepEqual(gotBundle, tc.expectedRFC3161Timestamp) { t.Errorf("TimestampToRFC3161Timestamp returned %v, wanted %v", gotBundle, tc.expectedRFC3161Timestamp) } }) } } cosign-2.5.0/pkg/cosign/certextensions.go000066400000000000000000000062031477503325500204630ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "crypto/x509" ) type CertExtensions struct { Cert *x509.Certificate } var ( // Fulcio cert-extensions, documented here: https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md CertExtensionOIDCIssuer = "1.3.6.1.4.1.57264.1.1" CertExtensionGithubWorkflowTrigger = "1.3.6.1.4.1.57264.1.2" CertExtensionGithubWorkflowSha = "1.3.6.1.4.1.57264.1.3" CertExtensionGithubWorkflowName = "1.3.6.1.4.1.57264.1.4" CertExtensionGithubWorkflowRepository = "1.3.6.1.4.1.57264.1.5" CertExtensionGithubWorkflowRef = "1.3.6.1.4.1.57264.1.6" CertExtensionMap = map[string]string{ CertExtensionOIDCIssuer: "oidcIssuer", CertExtensionGithubWorkflowTrigger: "githubWorkflowTrigger", CertExtensionGithubWorkflowSha: "githubWorkflowSha", CertExtensionGithubWorkflowName: "githubWorkflowName", CertExtensionGithubWorkflowRepository: "githubWorkflowRepository", CertExtensionGithubWorkflowRef: "githubWorkflowRef", } ) func (ce *CertExtensions) certExtensions() map[string]string { extensions := map[string]string{} for _, ext := range ce.Cert.Extensions { readableName, ok := CertExtensionMap[ext.Id.String()] if ok { extensions[readableName] = string(ext.Value) } else { extensions[ext.Id.String()] = string(ext.Value) } } return extensions } // GetIssuer returns the issuer for a Certificate func (ce *CertExtensions) GetIssuer() string { return ce.certExtensions()["oidcIssuer"] } // GetCertExtensionGithubWorkflowTrigger returns the GitHub Workflow Trigger for a Certificate func (ce *CertExtensions) GetCertExtensionGithubWorkflowTrigger() string { return ce.certExtensions()["githubWorkflowTrigger"] } // GetExtensionGithubWorkflowSha returns the GitHub Workflow SHA for a Certificate func (ce *CertExtensions) GetExtensionGithubWorkflowSha() string { return ce.certExtensions()["githubWorkflowSha"] } // GetCertExtensionGithubWorkflowName returns the GitHub Workflow Name for a Certificate func (ce *CertExtensions) GetCertExtensionGithubWorkflowName() string { return ce.certExtensions()["githubWorkflowName"] } // GetCertExtensionGithubWorkflowRepository returns the GitHub Workflow Repository for a Certificate func (ce *CertExtensions) GetCertExtensionGithubWorkflowRepository() string { return ce.certExtensions()["githubWorkflowRepository"] } // GetCertExtensionGithubWorkflowRef returns the GitHub Workflow Ref for a Certificate func (ce *CertExtensions) GetCertExtensionGithubWorkflowRef() string { return ce.certExtensions()["githubWorkflowRef"] } cosign-2.5.0/pkg/cosign/certextensions_test.go000066400000000000000000000046621477503325500215310ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "testing" ) func createCert(t *testing.T) *x509.Certificate { t.Helper() return &x509.Certificate{ Extensions: []pkix.Extension{ {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, Value: []byte("myIssuer")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, Value: []byte("myWorkflowTrigger")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}, Value: []byte("myWorkflowSha")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}, Value: []byte("myWorkflowName")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}, Value: []byte("myWorkflowRepository")}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, Value: []byte("myWorkflowRef")}, }, } } func TestCertExtensions(t *testing.T) { t.Parallel() cert := createCert(t) exts := CertExtensions{Cert: cert} if val := exts.GetIssuer(); val != "myIssuer" { t.Fatal("CertExtension does not extract field 'oidcIssuer' correctly") } if val := exts.GetCertExtensionGithubWorkflowTrigger(); val != "myWorkflowTrigger" { t.Fatal("CertExtension does not extract field 'githubWorkflowTrigger' correctly") } if val := exts.GetExtensionGithubWorkflowSha(); val != "myWorkflowSha" { t.Fatal("CertExtension does not extract field 'githubWorkflowSha' correctly") } if val := exts.GetCertExtensionGithubWorkflowName(); val != "myWorkflowName" { t.Fatal("CertExtension does not extract field 'githubWorkflowName' correctly") } if val := exts.GetCertExtensionGithubWorkflowRepository(); val != "myWorkflowRepository" { t.Fatal("CertExtension does not extract field 'githubWorkflowRepository' correctly") } if val := exts.GetCertExtensionGithubWorkflowRef(); val != "myWorkflowRef" { t.Fatal("CertExtension does not extract field 'githubWorkflowRef' correctly") } } cosign-2.5.0/pkg/cosign/common.go000066400000000000000000000031511477503325500166750ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "errors" "fmt" "os" "syscall" "golang.org/x/term" ) // TODO(jason): Move this to an internal package. func GetPassFromTerm(confirm bool) ([]byte, error) { fmt.Fprint(os.Stderr, "Enter password for private key: ") // Unnecessary convert of syscall.Stdin on *nix, but Windows is a uintptr // nolint:unconvert pw1, err := term.ReadPassword(int(syscall.Stdin)) if err != nil { return nil, err } fmt.Fprintln(os.Stderr) if !confirm { return pw1, nil } fmt.Fprint(os.Stderr, "Enter password for private key again: ") // Unnecessary convert of syscall.Stdin on *nix, but Windows is a uintptr // nolint:unconvert confirmpw, err := term.ReadPassword(int(syscall.Stdin)) fmt.Fprintln(os.Stderr) if err != nil { return nil, err } if string(pw1) != string(confirmpw) { return nil, errors.New("passwords do not match") } return pw1, nil } // TODO(jason): Move this to an internal package. func IsTerminal() bool { stat, _ := os.Stdin.Stat() return (stat.Mode() & os.ModeCharDevice) != 0 } cosign-2.5.0/pkg/cosign/ctlog.go000066400000000000000000000042741477503325500165240ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "errors" "fmt" "os" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/sigstore/pkg/tuf" ) // This is the CT log public key target name var ctPublicKeyStr = `ctfe.pub` // GetCTLogPubs retrieves trusted CTLog public keys from the embedded or cached // TUF root. If expired, makes a network call to retrieve the updated targets. // By default the public keys comes from TUF, but you can override this for test // purposes by using an env variable `SIGSTORE_CT_LOG_PUBLIC_KEY_FILE`. If using // an alternate, the file can be PEM, or DER format. func GetCTLogPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) { publicKeys := NewTrustedTransparencyLogPubKeys() altCTLogPub := env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) if altCTLogPub != "" { raw, err := os.ReadFile(altCTLogPub) if err != nil { return nil, fmt.Errorf("error reading alternate CTLog public key file: %w", err) } if err := publicKeys.AddTransparencyLogPubKey(raw, tuf.Active); err != nil { return nil, fmt.Errorf("AddCTLogPubKey: %w", err) } } else { tufClient, err := tuf.NewFromEnv(ctx) if err != nil { return nil, err } targets, err := tufClient.GetTargetsByMeta(tuf.CTFE, []string{ctPublicKeyStr}) if err != nil { return nil, err } for _, t := range targets { if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil { return nil, fmt.Errorf("AddCTLogPubKey: %w", err) } } } if len(publicKeys.Keys) == 0 { return nil, errors.New("none of the CTLog public keys have been found") } return &publicKeys, nil } cosign-2.5.0/pkg/cosign/ctlog_test.go000066400000000000000000000051151477503325500175560ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "os" "testing" ) const ( ctlogPublicKey = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7D2WvgqSzs9jpdJsOJ5Nl6xg8JXm Nmo7M3bN7+dQddw9Ibc2R3SV8tzBZw0rST8FKcn4apJepcKM4qUpYUeNfw== -----END PUBLIC KEY----- ` // This is the LogID constructed from above public key. ctLogID = "0bac0fddd0c15fbc46f8b1bf51c2b57676a9f262294fe13417d85602e73f392a" ) func TestGetCTLogPubKeys(t *testing.T) { keys, err := GetCTLogPubs(context.Background()) if err != nil { t.Fatalf("Unexpected error calling GetCTLogPubs, expected nil: %v", err) } if len(keys.Keys) == 0 { t.Errorf("expected 1 or more keys, got 0") } // check that the mapping of key digest to key is correct for logID, key := range keys.Keys { expectedLogID, err := GetTransparencyLogID(key.PubKey) if err != nil { t.Fatalf("unexpected error generated log ID: %v", err) } if logID != expectedLogID { t.Fatalf("key digests are not equal") } } } func TestGetCTLogPubKeysAlt(t *testing.T) { pkFile, err := os.CreateTemp(t.TempDir(), "cosign_verify_sct_*.key") if err != nil { t.Fatalf("failed to create temp public key file: %v", err) } defer pkFile.Close() if _, err := pkFile.Write([]byte(ctlogPublicKey)); err != nil { t.Fatalf("failed to write public key file: %v", err) } t.Setenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", pkFile.Name()) keys, err := GetCTLogPubs(context.Background()) if err != nil { t.Errorf("Unexpected error calling GetCTLogPubs, expected nil: %v", err) } if len(keys.Keys) == 0 { t.Errorf("expected 1 or more keys, got 0") } // check that the mapping of key digest to key is correct for logID, key := range keys.Keys { expectedLogID, err := GetTransparencyLogID(key.PubKey) if err != nil { t.Fatalf("unexpected error generated log ID: %v", err) } if logID != expectedLogID { t.Fatalf("key digests are not equal") } if expectedLogID != ctLogID { t.Fatalf("key digest computed is different from expected, got: %s want: %s", logID, ctLogID) } } } cosign-2.5.0/pkg/cosign/cue/000077500000000000000000000000001477503325500156325ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/cue/cue.go000066400000000000000000000021271477503325500167370ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cue import ( "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/load" cuejson "cuelang.org/go/encoding/json" ) func ValidateJSON(jsonBody []byte, entrypoints []string) error { ctx := cuecontext.New() bis := load.Instances(entrypoints, nil) for _, bi := range bis { if bi.Err != nil { return bi.Err } value := ctx.BuildInstance(bi) if value.Err() != nil { return value.Err() } err := cuejson.Validate(jsonBody, value) if err != nil { return err } } return nil } cosign-2.5.0/pkg/cosign/cue/cue_test.go000066400000000000000000000070211477503325500177740ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cue import ( "fmt" "os" "testing" ) var cueJSONAttestationsBody = ` { "authorityMatches": { "keyatt": { "signatures": null, "attestations": { "vuln-key": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ] } }, "keysignature": { "signatures": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ], "attestations": null }, "keylessatt": { "signatures": null, "attestations": { "key1": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ], "key2": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ] } }, "keylesssignature": { "signatures": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ], "attestations": null } } } ` var cueJSONSampleBody = `{ "seq": [ 1, 2, 3, { "a": 1, "b": 2 } ], "a": {"b": {"c": 3}}, "b": { "x": 0, "y": 1, "z": 2 } }` func TestValidationJSON(t *testing.T) { cases := []struct { name string jsonBody string policy string pass bool errorMsg string }{ { name: "passing policy", jsonBody: cueJSONSampleBody, policy: ` package test seq: [ 1, 2, 3, { a: 1 b: 2 } ] a: b: c: 3 b: { x: 0 y: 1 z: 2 } `, pass: true, }, { name: "passing result due to matching rules", jsonBody: cueJSONAttestationsBody, policy: ` package test import "struct" import "list" authorityMatches: { keyatt: { attestations: struct.MaxFields(1) & struct.MinFields(1) }, keysignature: { signatures: list.MaxItems(1) & list.MinItems(1) }, keylessatt: { attestations: struct.MinFields(2) & struct.MaxFields(2) }, keylesssignature: { signatures: list.MaxItems(1) & list.MinItems(1) } } `, pass: true, }, { name: "policy query evaluates to false signatures array min items", jsonBody: cueJSONAttestationsBody, policy: ` package test import "list" authorityMatches: { keysignature: { signatures: list.MaxItems(2) & list.MinItems(2) } } `, pass: false, errorMsg: "authorityMatches.keysignature.signatures: invalid value [{subject:\"PLACEHOLDER\",issuer:\"PLACEHOLDER\"}] (does not satisfy list.MinItems(2))", }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { policyFileName := "tmp-policy.cue" if err := os.WriteFile(policyFileName, []byte(tt.policy), 0644); err != nil { t.Fatal(err) } defer os.Remove(policyFileName) if err := ValidateJSON([]byte(tt.jsonBody), []string{policyFileName}); (err == nil) != tt.pass { t.Fatalf("Unexpected result: %v", err) } else if err != nil { if fmt.Sprintf("%s", err) != tt.errorMsg { t.Errorf("Expected error %q, got %q", tt.errorMsg, err) } } }) } } cosign-2.5.0/pkg/cosign/cue/fuzz_test.go000066400000000000000000000014321477503325500202160ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cue import ( "testing" ) func FuzzValidateJSON(f *testing.F) { f.Fuzz(func(_ *testing.T, jsonBody []byte, entrypoint string) { ValidateJSON(jsonBody, []string{entrypoint}) }) } cosign-2.5.0/pkg/cosign/env/000077500000000000000000000000001477503325500156465ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/env/env.go000066400000000000000000000222611477503325500167700ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package env import ( "fmt" "os" "strings" ) // Variable is a type representing an environment variable type Variable string // VariableOpts closely describes a Variable type VariableOpts struct { // Description contains description for the environment variable Description string // Expects describes what value is expected by the environment variable Expects string // Sensitive is used for environment variables with sensitive values // (e.g. passwords, credentials, etc.) Sensitive bool // External is used for environment variables coming from external projects // and dependencies (e.g. GITHUB_TOKEN, SIGSTORE_, TUF_) External bool } func (v Variable) String() string { return string(v) } const ( // Cosign environment variables VariableExperimental Variable = "COSIGN_EXPERIMENTAL" VariableDockerMediaTypes Variable = "COSIGN_DOCKER_MEDIA_TYPES" VariablePassword Variable = "COSIGN_PASSWORD" VariablePKCS11Pin Variable = "COSIGN_PKCS11_PIN" VariablePKCS11ModulePath Variable = "COSIGN_PKCS11_MODULE_PATH" VariablePKCS11IgnoreCertificate Variable = "COSIGN_PKCS11_IGNORE_CERTIFICATE" VariableRepository Variable = "COSIGN_REPOSITORY" VariableMaxAttachmentSize Variable = "COSIGN_MAX_ATTACHMENT_SIZE" // Sigstore environment variables VariableSigstoreCTLogPublicKeyFile Variable = "SIGSTORE_CT_LOG_PUBLIC_KEY_FILE" VariableSigstoreRootFile Variable = "SIGSTORE_ROOT_FILE" VariableSigstoreRekorPublicKey Variable = "SIGSTORE_REKOR_PUBLIC_KEY" VariableSigstoreIDToken Variable = "SIGSTORE_ID_TOKEN" //nolint:gosec VariableSigstoreTSACertificateFile Variable = "SIGSTORE_TSA_CERTIFICATE_FILE" // Other external environment variables VariableGitHubHost Variable = "GITHUB_HOST" VariableGitHubToken Variable = "GITHUB_TOKEN" //nolint:gosec VariableGitHubRequestToken Variable = "ACTIONS_ID_TOKEN_REQUEST_TOKEN" VariableGitHubRequestURL Variable = "ACTIONS_ID_TOKEN_REQUEST_URL" VariableSPIFFEEndpointSocket Variable = "SPIFFE_ENDPOINT_SOCKET" VariableGoogleServiceAccountName Variable = "GOOGLE_SERVICE_ACCOUNT_NAME" VariableGitLabHost Variable = "GITLAB_HOST" VariableGitLabToken Variable = "GITLAB_TOKEN" VariableBuildkiteAgentAccessToken Variable = "BUILDKITE_AGENT_ACCESS_TOKEN" VariableBuildkiteAgentEndpoint Variable = "BUILDKITE_AGENT_ENDPOINT" VariableBuildkiteJobID Variable = "BUILDKITE_JOB_ID" VariableBuildkiteAgentLogLevel Variable = "BUILDKITE_AGENT_LOG_LEVEL" VariableSourceDateEpoch Variable = "SOURCE_DATE_EPOCH" ) var ( // NB: this is intentionally private to avoid anyone changing this from // code. There's a getter function used to get this slice if needed. environmentVariables = map[Variable]VariableOpts{ VariableExperimental: { Description: "enables experimental cosign features", Expects: "1 if experimental features should be enabled (0 by default)", Sensitive: false, }, VariableDockerMediaTypes: { Description: "to be used with registries that do not support OCI media types", Expects: "1 to fallback to legacy OCI media types equivalents (0 by default)", Sensitive: false, }, VariablePassword: { Description: "overrides password inputs with this value", Expects: "string with a password (asks on stdin by default)", Sensitive: true, }, VariablePKCS11Pin: { Description: "to be used if PKCS11 PIN is not provided", Expects: "string with a PIN", Sensitive: true, }, VariablePKCS11ModulePath: { Description: "is PKCS11 module-path", Expects: "string with a module-path", Sensitive: false, }, VariablePKCS11IgnoreCertificate: { Description: "disables loading certificates with PKCS11", Expects: "1 if loading certificates should be disabled (0 by default)", Sensitive: false, }, VariableRepository: { Description: "can be used to store signatures in an alternate location", Expects: "string with a repository", Sensitive: false, }, VariableMaxAttachmentSize: { Description: "maximum attachment size to download (default 128MiB)", Expects: "human-readable unit of memory, e.g. 5120, 20K, 3M, 45MiB, 1GB", Sensitive: false, }, VariableSigstoreCTLogPublicKeyFile: { Description: "overrides what is used to validate the SCT coming back from Fulcio", Expects: "path to the public key file", Sensitive: false, External: true, }, VariableSigstoreRootFile: { Description: "overrides the public good instance root CA", Expects: "path to the root CA", Sensitive: false, External: true, }, VariableSigstoreRekorPublicKey: { Description: "if specified, you can specify an oob Public Key that Rekor uses", Expects: "path to the public key", Sensitive: false, External: true, }, VariableSigstoreTSACertificateFile: { Description: "path to the concatenated PEM-encoded TSA certificate file (leaf, intermediate(s), root) used by Sigstore", Expects: "path to the TSA certificate file", Sensitive: false, External: true, }, VariableGitHubHost: { Description: "is URL of the GitHub Enterprise instance", Expects: "string with the URL of GitHub Enterprise instance", Sensitive: false, External: true, }, VariableGitHubToken: { Description: "is a token used to authenticate with GitHub", Expects: "token generated on GitHub", Sensitive: true, External: true, }, VariableGitHubRequestToken: { Description: "is bearer token for the request to the OIDC provider", Expects: "string with a bearer token", Sensitive: true, External: true, }, VariableGitHubRequestURL: { Description: "is the URL for GitHub's OIDC provider", Expects: "string with the URL for the OIDC provider", Sensitive: false, External: true, }, VariableSPIFFEEndpointSocket: { Description: "allows you to specify non-default SPIFFE socket to use.", Expects: "string with SPIFFE socket path", Sensitive: false, External: true, }, VariableGoogleServiceAccountName: { Description: "is a service account name to be used with the Google provider", Expects: "string with the service account's name", Sensitive: false, External: true, }, VariableGitLabHost: { Description: "is URL of the GitLab instance", Expects: "string with the URL of GitLab instance", Sensitive: false, External: true, }, VariableGitLabToken: { Description: "is a token used to authenticate with GitLab", Expects: "string with a token", Sensitive: true, External: true, }, VariableBuildkiteAgentAccessToken: { Description: "is an access token used to identify the Buildkite agent", Expects: "string with an access token", Sensitive: true, External: true, }, VariableBuildkiteAgentEndpoint: { Description: "the Buildkite agent endpoint", Expects: "string with an endpoint", Sensitive: false, External: true, }, VariableBuildkiteJobID: { Description: "the Buildkite job ID to claim in the OIDC token", Expects: "string with a job ID", Sensitive: false, External: true, }, VariableBuildkiteAgentLogLevel: { Description: "the log level for the Buildkite agent", Expects: "string with log level, either debug, notice, info, error, warn, fatal (default: notice)", Sensitive: false, External: true, }, VariableSigstoreIDToken: { Description: "is a OIDC token used to authenticate to Fulcio", Expects: "string with a OIDC token", Sensitive: true, External: true, }, VariableSourceDateEpoch: { Description: "overrides current time for reproducible builds, see https://reproducible-builds.org/docs/source-date-epoch/", Expects: "number of seconds since unix epoch", Sensitive: false, External: true, }, } ) func EnvironmentVariables() map[Variable]VariableOpts { return environmentVariables } func mustRegisterEnv(name Variable) { opts, ok := environmentVariables[name] if !ok { panic(fmt.Sprintf("environment variable %q is not registered in pkg/cosign/env", name.String())) } if !opts.External && !strings.HasPrefix(name.String(), "COSIGN_") { panic(fmt.Sprintf("cosign environment variable %q must start with COSIGN_ prefix", name.String())) } } func Getenv(name Variable) string { mustRegisterEnv(name) return os.Getenv(name.String()) //nolint:forbidigo } func LookupEnv(name Variable) (string, bool) { mustRegisterEnv(name) return os.LookupEnv(name.String()) //nolint:forbidigo } cosign-2.5.0/pkg/cosign/env/env_test.go000066400000000000000000000053631477503325500200330ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package env import ( "os" "testing" ) func TestMustRegisterEnv(t *testing.T) { //nolint: revive // Calling this should NOT panic mustRegisterEnv(VariableExperimental) } func TestMustRegisterEnvWithNonRegisteredEnv(t *testing.T) { // This test must panic because non-registered variable is not registered with cosign. // We fail if the test doesn't panic. defer func() { if r := recover(); r == nil { t.Errorf("expected to panic, but panic did not happen") } }() mustRegisterEnv(Variable("non-registered")) } func TestMustRegisterEnvWithInvalidCosignEnvVar(t *testing.T) { // This test must panic because registered non-external variable doesn't start with COSIGN_. // We fail if the test doesn't panic. saveEnvs := environmentVariables defer func() { if r := recover(); r == nil { t.Errorf("expected to panic, but panic did not happen") } environmentVariables = saveEnvs }() v := Variable("TEST") environmentVariables = map[Variable]VariableOpts{ v: { External: false, }, } mustRegisterEnv(v) } func TestGetenv(t *testing.T) { os.Setenv("COSIGN_EXPERIMENTAL", "1") if val := Getenv(VariableExperimental); val != "1" { t.Errorf("expected to get \"1\", but got %q", val) } } func TestGetenvUnset(t *testing.T) { os.Unsetenv("COSIGN_EXPERIMENTAL") if val := Getenv(VariableExperimental); val != "" { t.Errorf("expected to get \"\", but got %q", val) } } func TestLookupEnv(t *testing.T) { os.Setenv("COSIGN_EXPERIMENTAL", "1") val, f := LookupEnv(VariableExperimental) if !f { t.Errorf("expected to find %q, but it's not set", "COSIGN_EXPERIMENTAL") } if val != "1" { t.Errorf("expected to get value \"1\", but got %q", val) } } func TestLookupEnvEmpty(t *testing.T) { os.Setenv("COSIGN_EXPERIMENTAL", "") val, f := LookupEnv(VariableExperimental) if !f { t.Errorf("expected to find %q, but it's not set", "COSIGN_EXPERIMENTAL") } if val != "" { t.Errorf("expected to get value \"\", but got %q", val) } } func TestLookupEnvUnset(t *testing.T) { os.Unsetenv("COSIGN_EXPERIMENTAL") val, f := LookupEnv(VariableExperimental) if f { t.Errorf("expected to not find %q, but it's set to %q", "COSIGN_EXPERIMENTAL", val) } } cosign-2.5.0/pkg/cosign/errors.go000066400000000000000000000054301477503325500167230ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import "fmt" // VerificationFailure is the type of Go error that is used by cosign to surface // errors actually related to verification (vs. transient, misconfiguration, // transport, or authentication related issues). type VerificationFailure struct { err error } func (e *VerificationFailure) Error() string { return e.err.Error() } func (e *VerificationFailure) Unwrap() error { return e.err } type ErrNoMatchingSignatures struct { err error } func (e *ErrNoMatchingSignatures) Error() string { return e.err.Error() } func (e *ErrNoMatchingSignatures) Unwrap() error { return e.err } type ErrImageTagNotFound struct { err error } func (e *ErrImageTagNotFound) Error() string { return e.err.Error() } func (e *ErrImageTagNotFound) Unwrap() error { return e.err } type ErrNoSignaturesFound struct { err error } func (e *ErrNoSignaturesFound) Error() string { return e.err.Error() } func (e *ErrNoSignaturesFound) Unwrap() error { return e.err } type ErrNoMatchingAttestations struct { err error } func (e *ErrNoMatchingAttestations) Error() string { return e.err.Error() } func (e *ErrNoMatchingAttestations) Unwrap() error { return e.err } type ErrNoCertificateFoundOnSignature struct { err error } func (e *ErrNoCertificateFoundOnSignature) Error() string { return e.err.Error() } func (e *ErrNoCertificateFoundOnSignature) Unwrap() error { return e.err } // NewVerificationError exists for backwards compatibility. // Deprecated: see [VerificationFailure]. func NewVerificationError(msg string, args ...interface{}) error { return &VerificationError{ message: fmt.Sprintf(msg, args...), } } // VerificationError exists for backwards compatibility. // Deprecated: see [VerificationFailure]. type VerificationError struct { message string } func (e *VerificationError) Error() string { return e.message } var ( // ErrNoMatchingAttestationsMessage exists for backwards compatibility. // Deprecated: see [ErrNoMatchingAttestations]. ErrNoMatchingAttestationsMessage = "no matching attestations" // ErrNoMatchingAttestationsType exists for backwards compatibility. // Deprecated: see [ErrNoMatchingAttestations]. ErrNoMatchingAttestationsType = "NoMatchingAttestations" ) cosign-2.5.0/pkg/cosign/errors_test.go000066400000000000000000000024451477503325500177650ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "errors" "fmt" "testing" ) func TestErrors(t *testing.T) { for _, want := range []error{ &VerificationFailure{fmt.Errorf("not a constant %d", 3)}, &VerificationFailure{fmt.Errorf("not a string %s", "i am a string")}, } { t.Run(want.Error(), func(t *testing.T) { verr := &VerificationFailure{} if !errors.As(want, &verr) { t.Errorf("%v is not a %T", want, &VerificationFailure{}) } // Check that Is sees it as the same error through multiple // levels of wrapping. wrapped := want for i := 0; i < 5; i++ { if !errors.Is(wrapped, want) { t.Errorf("%v is not %v", wrapped, want) } wrapped = fmt.Errorf("wrapper: %w", wrapped) } }) } } cosign-2.5.0/pkg/cosign/fetch.go000066400000000000000000000135601477503325500165030ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "crypto/x509" "encoding/base64" "encoding/json" "errors" "fmt" "os" "runtime" "sync" "github.com/google/go-containerregistry/pkg/name" "github.com/in-toto/in-toto-golang/in_toto" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "golang.org/x/sync/errgroup" ) const maxAllowedSigsOrAtts = 100 type SignedPayload struct { Base64Signature string Payload []byte Cert *x509.Certificate Chain []*x509.Certificate Bundle *bundle.RekorBundle RFC3161Timestamp *bundle.RFC3161Timestamp } type LocalSignedPayload struct { Base64Signature string `json:"base64Signature"` Cert string `json:"cert,omitempty"` Bundle *bundle.RekorBundle `json:"rekorBundle,omitempty"` } type Signatures struct { KeyID string `json:"keyid"` Sig string `json:"sig"` } type AttestationPayload struct { PayloadType string `json:"payloadType"` PayLoad string `json:"payload"` Signatures []Signatures `json:"signatures"` } const ( Signature = "signature" SBOM = "sbom" Attestation = "attestation" Digest = "digest" ) func FetchSignaturesForReference(_ context.Context, ref name.Reference, opts ...ociremote.Option) ([]SignedPayload, error) { simg, err := ociremote.SignedEntity(ref, opts...) if err != nil { return nil, err } sigs, err := FetchSignatures(simg) if err != nil { return nil, fmt.Errorf("%s: %w", ref, err) } return sigs, nil } func FetchSignatures(se oci.SignedEntity) ([]SignedPayload, error) { sigs, err := se.Signatures() if err != nil { return nil, fmt.Errorf("remote image: %w", err) } l, err := sigs.Get() if err != nil { return nil, fmt.Errorf("fetching signatures: %w", err) } if len(l) == 0 { return nil, errors.New("no signatures associated") } if len(l) > maxAllowedSigsOrAtts { return nil, fmt.Errorf("maximum number of signatures on an image is %d, found %d", maxAllowedSigsOrAtts, len(l)) } signatures := make([]SignedPayload, len(l)) var g errgroup.Group g.SetLimit(runtime.NumCPU()) for i, sig := range l { i, sig := i, sig g.Go(func() error { var err error signatures[i].Payload, err = sig.Payload() if err != nil { return err } signatures[i].Base64Signature, err = sig.Base64Signature() if err != nil { return err } signatures[i].Cert, err = sig.Cert() if err != nil { return err } signatures[i].Chain, err = sig.Chain() if err != nil { return err } signatures[i].RFC3161Timestamp, err = sig.RFC3161Timestamp() if err != nil { return err } signatures[i].Bundle, err = sig.Bundle() return err }) } if err := g.Wait(); err != nil { return nil, err } return signatures, nil } func FetchAttestationsForReference(_ context.Context, ref name.Reference, predicateType string, opts ...ociremote.Option) ([]AttestationPayload, error) { se, err := ociremote.SignedEntity(ref, opts...) if err != nil { return nil, err } return FetchAttestations(se, predicateType) } func FetchAttestations(se oci.SignedEntity, predicateType string) ([]AttestationPayload, error) { atts, err := se.Attestations() if err != nil { return nil, fmt.Errorf("remote image: %w", err) } l, err := atts.Get() if err != nil { return nil, fmt.Errorf("fetching attestations: %w", err) } if len(l) == 0 { return nil, errors.New("found no attestations") } if len(l) > maxAllowedSigsOrAtts { errMsg := fmt.Sprintf("maximum number of attestations on an image is %d, found %d", maxAllowedSigsOrAtts, len(l)) return nil, errors.New(errMsg) } attestations := make([]AttestationPayload, 0, len(l)) var attMu sync.Mutex var g errgroup.Group g.SetLimit(runtime.NumCPU()) for _, att := range l { att := att g.Go(func() error { rawPayload, err := att.Payload() if err != nil { return fmt.Errorf("fetching payload: %w", err) } var payload AttestationPayload if err := json.Unmarshal(rawPayload, &payload); err != nil { return fmt.Errorf("unmarshaling payload: %w", err) } if predicateType != "" { var decodedPayload []byte decodedPayload, err = base64.StdEncoding.DecodeString(payload.PayLoad) if err != nil { return fmt.Errorf("decoding payload: %w", err) } var statement in_toto.Statement if err := json.Unmarshal(decodedPayload, &statement); err != nil { return fmt.Errorf("unmarshaling statement: %w", err) } if statement.PredicateType != predicateType { return nil } } attMu.Lock() defer attMu.Unlock() attestations = append(attestations, payload) return nil }) } if err := g.Wait(); err != nil { return nil, err } if len(attestations) == 0 && predicateType != "" { return nil, fmt.Errorf("no attestations with predicate type '%s' found", predicateType) } return attestations, nil } // FetchLocalSignedPayloadFromPath fetches a local signed payload from a path to a file func FetchLocalSignedPayloadFromPath(path string) (*LocalSignedPayload, error) { contents, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("reading %s: %w", path, err) } var b *LocalSignedPayload if err := json.Unmarshal(contents, &b); err != nil { return nil, err } return b, nil } cosign-2.5.0/pkg/cosign/fulcioverifier/000077500000000000000000000000001477503325500200735ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/fulcioverifier/ctutil/000077500000000000000000000000001477503325500213775ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/fulcioverifier/ctutil/ctutil.go000066400000000000000000000221401477503325500232310ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package copied from // https://github.com/google/certificate-transparency-go/blob/master/ctutil/ctutil.go // Package ctutil contains utilities for Certificate Transparency. package ctutil import ( "bytes" "crypto" "crypto/sha256" "encoding/base64" "errors" "fmt" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509" ) var emptyHash = [sha256.Size]byte{} // LeafHashB64 does as LeafHash does, but returns the leaf hash base64-encoded. // The base64-encoded leaf hash returned by B64LeafHash can be used with the // get-proof-by-hash API endpoint of Certificate Transparency Logs. func LeafHashB64(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (string, error) { hash, err := LeafHash(chain, sct, embedded) if err != nil { return "", err } return base64.StdEncoding.EncodeToString(hash[:]), nil } // LeafHash calculates the leaf hash of the certificate or precertificate at // chain[0] that sct was issued for. // // sct is required because the SCT timestamp is used to calculate the leaf hash. // Leaf hashes are unique to (pre)certificate-SCT pairs. // // This function can be used with three different types of leaf certificate: // - X.509 Certificate: // If using this function to calculate the leaf hash for a normal X.509 // certificate then it is enough to just provide the end entity // certificate in chain. This case assumes that the SCT being provided is // not embedded within the leaf certificate provided, i.e. the certificate // is what was submitted to the Certificate Transparency Log in order to // obtain the SCT. For this case, set embedded to false. // - Precertificate: // If using this function to calculate the leaf hash for a precertificate // then the issuing certificate must also be provided in chain. The // precertificate should be at chain[0], and its issuer at chain[1]. For // this case, set embedded to false. // - X.509 Certificate containing the SCT embedded within it: // If using this function to calculate the leaf hash for a certificate // where the SCT provided is embedded within the certificate you // are providing at chain[0], set embedded to true. LeafHash will // calculate the leaf hash by building the corresponding precertificate. // LeafHash will return an error if the provided SCT cannot be found // embedded within chain[0]. As with the precertificate case, the issuing // certificate must also be provided in chain. The certificate containing // the embedded SCT should be at chain[0], and its issuer at chain[1]. // // Note: LeafHash doesn't check that the provided SCT verifies for the given // chain. It simply calculates what the leaf hash would be for the given // (pre)certificate-SCT pair. func LeafHash(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) ([sha256.Size]byte, error) { leaf, err := createLeaf(chain, sct, embedded) if err != nil { return emptyHash, err } return ct.LeafHashForLeaf(leaf) } // VerifySCT takes the public key of a Certificate Transparency Log, a // certificate chain, and an SCT and verifies whether the SCT is a valid SCT for // the certificate at chain[0], signed by the Log that the public key belongs // to. If the SCT does not verify, an error will be returned. // // This function can be used with three different types of leaf certificate: // - X.509 Certificate: // If using this function to verify an SCT for a normal X.509 certificate // then it is enough to just provide the end entity certificate in chain. // This case assumes that the SCT being provided is not embedded within // the leaf certificate provided, i.e. the certificate is what was // submitted to the Certificate Transparency Log in order to obtain the // SCT. For this case, set embedded to false. // - Precertificate: // If using this function to verify an SCT for a precertificate then the // issuing certificate must also be provided in chain. The precertificate // should be at chain[0], and its issuer at chain[1]. For this case, set // embedded to false. // - X.509 Certificate containing the SCT embedded within it: // If the SCT you wish to verify is embedded within the certificate you // are providing at chain[0], set embedded to true. VerifySCT will // verify the provided SCT by building the corresponding precertificate. // VerifySCT will return an error if the provided SCT cannot be found // embedded within chain[0]. As with the precertificate case, the issuing // certificate must also be provided in chain. The certificate containing // the embedded SCT should be at chain[0], and its issuer at chain[1]. func VerifySCT(pubKey crypto.PublicKey, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error { s, err := ct.NewSignatureVerifier(pubKey) if err != nil { return fmt.Errorf("error creating signature verifier: %w", err) } return VerifySCTWithVerifier(s, chain, sct, embedded) } // VerifySCTWithVerifier takes a ct.SignatureVerifier, a certificate chain, and // an SCT and verifies whether the SCT is a valid SCT for the certificate at // chain[0], signed by the Log whose public key was used to set up the // ct.SignatureVerifier. If the SCT does not verify, an error will be returned. // // This function can be used with three different types of leaf certificate: // - X.509 Certificate: // If using this function to verify an SCT for a normal X.509 certificate // then it is enough to just provide the end entity certificate in chain. // This case assumes that the SCT being provided is not embedded within // the leaf certificate provided, i.e. the certificate is what was // submitted to the Certificate Transparency Log in order to obtain the // SCT. For this case, set embedded to false. // - Precertificate: // If using this function to verify an SCT for a precertificate then the // issuing certificate must also be provided in chain. The precertificate // should be at chain[0], and its issuer at chain[1]. For this case, set // embedded to false. // - X.509 Certificate containing the SCT embedded within it: // If the SCT you wish to verify is embedded within the certificate you // are providing at chain[0], set embedded to true. VerifySCT will // verify the provided SCT by building the corresponding precertificate. // VerifySCT will return an error if the provided SCT cannot be found // embedded within chain[0]. As with the precertificate case, the issuing // certificate must also be provided in chain. The certificate containing // the embedded SCT should be at chain[0], and its issuer at chain[1]. func VerifySCTWithVerifier(sv *ct.SignatureVerifier, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error { if sv == nil { return errors.New("ct.SignatureVerifier is nil") } leaf, err := createLeaf(chain, sct, embedded) if err != nil { return err } return sv.VerifySCTSignature(*sct, ct.LogEntry{Leaf: *leaf}) } func createLeaf(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (*ct.MerkleTreeLeaf, error) { if len(chain) == 0 { return nil, errors.New("chain is empty") } if sct == nil { return nil, errors.New("sct is nil") } if embedded { sctPresent, err := ContainsSCT(chain[0], sct) if err != nil { return nil, fmt.Errorf("error checking for SCT in leaf certificate: %w", err) } if !sctPresent { return nil, errors.New("SCT provided is not embedded within leaf certificate") } } certType := ct.X509LogEntryType if chain[0].IsPrecertificate() || embedded { certType = ct.PrecertLogEntryType } var leaf *ct.MerkleTreeLeaf var err error if embedded { leaf, err = ct.MerkleTreeLeafForEmbeddedSCT(chain, sct.Timestamp) } else { leaf, err = ct.MerkleTreeLeafFromChain(chain, certType, sct.Timestamp) } if err != nil { return nil, fmt.Errorf("error creating MerkleTreeLeaf: %w", err) } return leaf, nil } // ContainsSCT checks to see whether the given SCT is embedded within the given // certificate. func ContainsSCT(cert *x509.Certificate, sct *ct.SignedCertificateTimestamp) (bool, error) { if cert == nil || sct == nil { return false, nil } sctBytes, err := tls.Marshal(*sct) if err != nil { return false, fmt.Errorf("error tls.Marshalling SCT: %w", err) } for _, s := range cert.SCTList.SCTList { if bytes.Equal(sctBytes, s.Val) { return true, nil } } return false, nil } cosign-2.5.0/pkg/cosign/fulcioverifier/ctutil/ctutil_test.go000066400000000000000000000223121477503325500242710ustar00rootroot00000000000000// Copyright 2018 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ctutil import ( "encoding/base64" "testing" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" "github.com/google/certificate-transparency-go/x509util" ) func TestLeafHash(t *testing.T) { tests := []struct { desc string chainPEM string sct []byte embedded bool want string }{ { desc: "cert", chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, want: testdata.TestCertB64LeafHash, }, { desc: "precert", chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, want: testdata.TestPreCertB64LeafHash, }, { desc: "cert with embedded SCT", chainPEM: testdata.TestEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, embedded: true, want: testdata.TestPreCertB64LeafHash, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse chain chain, err := x509util.CertificatesFromPEM([]byte(test.chainPEM)) if err != nil { t.Fatalf("error parsing certificate chain: %s", err) } // Parse SCT var sct ct.SignedCertificateTimestamp if _, err = tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } // Test LeafHash() wantSl, err := base64.StdEncoding.DecodeString(test.want) if err != nil { t.Fatalf("error base64-decoding leaf hash %q: %s", test.want, err) } var want [32]byte copy(want[:], wantSl) got, err := LeafHash(chain, &sct, test.embedded) if got != want || err != nil { t.Errorf("LeafHash(_,_) = %v, %v, want %v, nil", got, err, want) } // Test LeafHashB64() gotB64, err := LeafHashB64(chain, &sct, test.embedded) if gotB64 != test.want || err != nil { t.Errorf("LeafHashB64(_,_) = %v, %v, want %v, nil", gotB64, err, test.want) } }) } } func TestLeafHashErrors(t *testing.T) { tests := []struct { desc string chainPEM string sct []byte embedded bool }{ { desc: "empty chain", chainPEM: "", sct: testdata.TestCertProof, }, { desc: "nil SCT", chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: nil, }, { desc: "no SCTs embedded in cert, embedded true", chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, }, { desc: "cert contains embedded SCTs, but not the SCT provided", chainPEM: testdata.TestEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse chain chain, err := x509util.CertificatesFromPEM([]byte(test.chainPEM)) if err != nil { t.Fatalf("error parsing certificate chain: %s", err) } // Parse SCT var sct *ct.SignedCertificateTimestamp if test.sct != nil { sct = &ct.SignedCertificateTimestamp{} if _, err = tls.Unmarshal(test.sct, sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } } // Test LeafHash() got, err := LeafHash(chain, sct, test.embedded) if got != emptyHash || err == nil { t.Errorf("LeafHash(_,_) = %s, %v, want %v, error", got, err, emptyHash) } // Test LeafHashB64() gotB64, err := LeafHashB64(chain, sct, test.embedded) if gotB64 != "" || err == nil { t.Errorf("LeafHashB64(_,_) = %s, %v, want \"\", error", gotB64, err) } }) } } func TestVerifySCT(t *testing.T) { tests := []struct { desc string chainPEM string sct []byte embedded bool wantErr bool }{ { desc: "cert", chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, }, { desc: "precert", chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, }, { desc: "invalid SCT", chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, wantErr: true, }, { desc: "cert with embedded SCT", chainPEM: testdata.TestEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, embedded: true, }, { desc: "cert with invalid embedded SCT", chainPEM: testdata.TestInvalidEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, wantErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse chain chain, err := x509util.CertificatesFromPEM([]byte(test.chainPEM)) if err != nil { t.Fatalf("error parsing certificate chain: %s", err) } // Parse SCT var sct ct.SignedCertificateTimestamp if _, err = tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } // Test VerifySCT() pk, err := ct.PublicKeyFromB64(testdata.LogPublicKeyB64) if err != nil { t.Errorf("error parsing public key: %s", err) } err = VerifySCT(pk, chain, &sct, test.embedded) if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("VerifySCT(_,_,_, %t) = %v, want error? %t", test.embedded, err, test.wantErr) } }) } } func TestVerifySCTWithVerifier(t *testing.T) { // Parse public key pk, err := ct.PublicKeyFromB64(testdata.LogPublicKeyB64) if err != nil { t.Errorf("error parsing public key: %s", err) } // Create signature verifier sv, err := ct.NewSignatureVerifier(pk) if err != nil { t.Errorf("couldn't create signature verifier: %s", err) } tests := []struct { desc string sv *ct.SignatureVerifier chainPEM string sct []byte embedded bool wantErr bool }{ { desc: "nil signature verifier", sv: nil, chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, wantErr: true, }, { desc: "cert", sv: sv, chainPEM: testdata.TestCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, }, { desc: "precert", sv: sv, chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, }, { desc: "invalid SCT", sv: sv, chainPEM: testdata.TestPreCertPEM + testdata.CACertPEM, sct: testdata.TestCertProof, wantErr: true, }, { desc: "cert with embedded SCT", sv: sv, chainPEM: testdata.TestEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestPreCertProof, embedded: true, }, { desc: "cert with invalid embedded SCT", sv: sv, chainPEM: testdata.TestInvalidEmbeddedCertPEM + testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, wantErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse chain chain, err := x509util.CertificatesFromPEM([]byte(test.chainPEM)) if err != nil { t.Fatalf("error parsing certificate chain: %s", err) } // Parse SCT var sct ct.SignedCertificateTimestamp if _, err = tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } // Test VerifySCTWithVerifier() err = VerifySCTWithVerifier(test.sv, chain, &sct, test.embedded) if gotErr := err != nil; gotErr != test.wantErr { t.Errorf("VerifySCT(_,_,_, %t) = %v, want error? %t", test.embedded, err, test.wantErr) } }) } } func TestContainsSCT(t *testing.T) { tests := []struct { desc string certPEM string sct []byte want bool }{ { desc: "cert doesn't contain any SCTs", certPEM: testdata.TestCertPEM, sct: testdata.TestPreCertProof, want: false, }, { desc: "cert contains SCT but not specified SCT", certPEM: testdata.TestEmbeddedCertPEM, sct: testdata.TestInvalidProof, want: false, }, { desc: "cert contains SCT", certPEM: testdata.TestEmbeddedCertPEM, sct: testdata.TestPreCertProof, want: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // Parse cert cert, err := x509util.CertificateFromPEM([]byte(test.certPEM)) if err != nil { t.Fatalf("error parsing certificate: %s", err) } // Parse SCT var sct ct.SignedCertificateTimestamp if _, err = tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } // Test ContainsSCT() got, err := ContainsSCT(cert, &sct) if err != nil { t.Fatalf("ContainsSCT(_,_) = false, %s, want no error", err) } if got != test.want { t.Errorf("ContainsSCT(_,_) = %t, nil, want %t, nil", got, test.want) } }) } } cosign-2.5.0/pkg/cosign/fuzz_test.go000066400000000000000000000065401477503325500174470ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "os" "path/filepath" "testing" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ) var ( defaultFuzzRef = "fuzz/test" ) func fuzzPass(s string) PassFunc { return func(_ bool) ([]byte, error) { return []byte(s), nil } } func FuzzImportKeyPairLoadPrivateKey(f *testing.F) { f.Add([]byte(validrsa), []byte("password")) f.Add([]byte(validrsapkcs1), []byte("password")) f.Add([]byte(validrsapkcs8), []byte("password")) f.Add([]byte(validecp256), []byte("password")) f.Add([]byte(validecp384), []byte("password")) f.Add([]byte(validecp521), []byte("password")) f.Add([]byte(validecpkcs8), []byte("password")) f.Add([]byte(ed25519key), []byte("password")) f.Add([]byte(pemcosignkey), []byte("password")) f.Add([]byte(pemcosigneckey), []byte("password")) f.Add([]byte(pemsigstorekey), []byte("password")) f.Fuzz(func(t *testing.T, pemData, password []byte) { path := t.TempDir() keyFilePath := filepath.Join(path, "fuzzKey") err := os.WriteFile(keyFilePath, pemData, 0x755) if err != nil { return } keyBytes, err := ImportKeyPair(keyFilePath, fuzzPass(string(password))) if err != nil { return } // Loading the private key should also work. _, err = LoadPrivateKey(keyBytes.PrivateBytes, password) if err != nil { t.Fatal(err) } }) } func FuzzSigVerify(f *testing.F) { f.Fuzz(func(t *testing.T, sigData, payloadData []byte, verificationTest int) { path := t.TempDir() sigPath := filepath.Join(path, "sigFile") err := os.WriteFile(sigPath, sigData, 0x755) if err != nil { return } payloadPath := filepath.Join(path, "payloadFile") err = os.WriteFile(payloadPath, payloadData, 0x755) if err != nil { return } ref, err := name.ParseReference(defaultFuzzRef) if err != nil { panic(err) } sigs, err := loadSignatureFromFile(context.Background(), sigPath, ref, &CheckOpts{PayloadRef: payloadPath}) if err != nil { return } switch verificationTest % 5 { case 0: VerifyImageAttestation(context.Background(), sigs, v1.Hash{}, &CheckOpts{IgnoreTlog: true}) case 1: verifySignatures(context.Background(), sigs, v1.Hash{}, &CheckOpts{IgnoreTlog: true}) case 2: sl, err := sigs.Get() if err != nil { t.Fatal(err) } for _, sig := range sl { VerifyBlobSignature(context.Background(), sig, &CheckOpts{IgnoreTlog: true}) } case 3: sl, err := sigs.Get() if err != nil { t.Fatal(err) } for _, sig := range sl { VerifyImageSignature(context.Background(), sig, v1.Hash{}, &CheckOpts{IgnoreTlog: true}) } case 4: sl, err := sigs.Get() if err != nil { t.Fatal(err) } for _, sig := range sl { mutate.Signature(sig) } } }) } cosign-2.5.0/pkg/cosign/git/000077500000000000000000000000001477503325500156415ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/git/git.go000066400000000000000000000021721477503325500167550ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package git import ( "context" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/git/github" "github.com/sigstore/cosign/v2/pkg/cosign/git/gitlab" ) var providerMap = map[string]Git{ github.ReferenceScheme: github.New(), gitlab.ReferenceScheme: gitlab.New(), } type Git interface { PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error GetSecret(ctx context.Context, ref string, key string) (string, error) } func GetProvider(provider string) Git { return providerMap[provider] } cosign-2.5.0/pkg/cosign/git/github/000077500000000000000000000000001477503325500171235ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/git/github/github.go000066400000000000000000000141701477503325500207370ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package github import ( "context" "encoding/base64" "errors" "fmt" "io" "net/http" "os" "strings" "github.com/google/go-github/v55/github" "golang.org/x/crypto/nacl/box" "golang.org/x/oauth2" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) const ( ReferenceScheme = "github" ) type Gh struct{} func New() *Gh { return &Gh{} } func (g *Gh) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error { var httpClient *http.Client if token, ok := env.LookupEnv(env.VariableGitHubToken); ok { ts := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: token}, ) httpClient = oauth2.NewClient(ctx, ts) } else { return fmt.Errorf("could not find %q environment variable", env.VariableGitHubToken.String()) } var client *github.Client if host, ok := env.LookupEnv(env.VariableGitHubHost); ok { var err error client, err = github.NewClient(httpClient).WithEnterpriseURLs(host, host) if err != nil { return fmt.Errorf("could not create github enterprise client: %w", err) } } else { client = github.NewClient(httpClient) } keys, err := cosign.GenerateKeyPair(pf) if err != nil { return fmt.Errorf("generating key pair: %w", err) } var owner, repo string split := strings.Split(ref, "/") switch len(split) { case 2: owner, repo = split[0], split[1] case 1: owner = split[0] default: return errors.New("could not parse scheme, use github:// or github:/// format") } key, getPubKeyResp, err := getPublicKey(ctx, client, owner, repo) if err != nil { return fmt.Errorf("could not get repository public key: %w", err) } if getPubKeyResp.StatusCode < 200 && getPubKeyResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(getPubKeyResp.Body) return fmt.Errorf("%s", bodyBytes) } encryptedCosignPasswd, err := encryptSecretWithPublicKey(key, "COSIGN_PASSWORD", keys.Password()) if err != nil { return fmt.Errorf("could not encrypt the secret: %w", err) } passwordSecretEnvResp, err := createOrUpdateOrgSecret(ctx, client, owner, repo, encryptedCosignPasswd) if err != nil { return fmt.Errorf("could not create \"COSIGN_PASSWORD\" github actions secret: %w", err) } if passwordSecretEnvResp.StatusCode < 200 && passwordSecretEnvResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(passwordSecretEnvResp.Body) return fmt.Errorf("%s", bodyBytes) } fmt.Fprintln(os.Stderr, "Password written to COSIGN_PASSWORD github actions secret") encryptedCosignPrivKey, err := encryptSecretWithPublicKey(key, "COSIGN_PRIVATE_KEY", keys.PrivateBytes) if err != nil { return fmt.Errorf("could not encrypt the secret: %w", err) } privateKeySecretEnvResp, err := createOrUpdateOrgSecret(ctx, client, owner, repo, encryptedCosignPrivKey) if err != nil { return fmt.Errorf("could not create \"COSIGN_PRIVATE_KEY\" github actions secret: %w", err) } if privateKeySecretEnvResp.StatusCode < 200 && privateKeySecretEnvResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(privateKeySecretEnvResp.Body) return fmt.Errorf("%s", bodyBytes) } fmt.Fprintln(os.Stderr, "Private key written to COSIGN_PRIVATE_KEY github actions secret") encryptedCosignPubKey, err := encryptSecretWithPublicKey(key, "COSIGN_PUBLIC_KEY", keys.PublicBytes) if err != nil { return fmt.Errorf("could not encrypt the secret: %w", err) } publicKeySecretEnvResp, err := createOrUpdateOrgSecret(ctx, client, owner, repo, encryptedCosignPubKey) if err != nil { return fmt.Errorf("could not create \"COSIGN_PUBLIC_KEY\" github actions secret: %w", err) } if publicKeySecretEnvResp.StatusCode < 200 && publicKeySecretEnvResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(publicKeySecretEnvResp.Body) return fmt.Errorf("%s", bodyBytes) } fmt.Fprintln(os.Stderr, "Public key written to COSIGN_PUBLIC_KEY github actions secret") if err := os.WriteFile("cosign.pub", keys.PublicBytes, 0o600); err != nil { return err } fmt.Fprintln(os.Stderr, "Public key also written to cosign.pub") return nil } // NOTE: GetSecret is not implemented for GitHub func (g *Gh) GetSecret(ctx context.Context, ref string, key string) (string, error) { //nolint: revive return "", nil } func createOrUpdateOrgSecret(ctx context.Context, client *github.Client, owner string, repo string, encryptedCosignPasswd *github.EncryptedSecret) (*github.Response, error) { if len(repo) > 0 { return client.Actions.CreateOrUpdateRepoSecret(ctx, owner, repo, encryptedCosignPasswd) } return client.Actions.CreateOrUpdateOrgSecret(ctx, owner, encryptedCosignPasswd) } func getPublicKey(ctx context.Context, client *github.Client, owner string, repo string) (*github.PublicKey, *github.Response, error) { if len(repo) > 0 { return client.Actions.GetRepoPublicKey(ctx, owner, repo) } return client.Actions.GetOrgPublicKey(ctx, owner) } func encryptSecretWithPublicKey(publicKey *github.PublicKey, secretName string, secretValue []byte) (*github.EncryptedSecret, error) { decodedPubKey, err := base64.StdEncoding.DecodeString(publicKey.GetKey()) if err != nil { return nil, fmt.Errorf("failed to decode public key: %w", err) } var peersPubKey [32]byte copy(peersPubKey[:], decodedPubKey[0:32]) var rand io.Reader eBody, err := box.SealAnonymous(nil, secretValue, &peersPubKey, rand) if err != nil { return nil, fmt.Errorf("failed to encrypt body: %w", err) } encryptedString := base64.StdEncoding.EncodeToString(eBody) keyID := publicKey.GetKeyID() encryptedSecret := &github.EncryptedSecret{ Name: secretName, KeyID: keyID, EncryptedValue: encryptedString, } return encryptedSecret, nil } cosign-2.5.0/pkg/cosign/git/gitlab/000077500000000000000000000000001477503325500171035ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/git/gitlab/gitlab.go000066400000000000000000000121221477503325500206720ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gitlab import ( "context" "fmt" "io" "os" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/env" gitlab "gitlab.com/gitlab-org/api/client-go" ) const ( ReferenceScheme = "gitlab" ) type Gl struct{} func New() *Gl { return &Gl{} } func (g *Gl) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error { keys, err := cosign.GenerateKeyPair(pf) if err != nil { return fmt.Errorf("generating key pair: %w", err) } token, tokenExists := env.LookupEnv(env.VariableGitLabToken) if !tokenExists { return fmt.Errorf("could not find %q", env.VariableGitLabToken.String()) } var client *gitlab.Client if url, baseURLExists := env.LookupEnv(env.VariableGitLabHost); baseURLExists { client, err = gitlab.NewClient(token, gitlab.WithBaseURL(url)) if err != nil { return fmt.Errorf("could not create GitLab client: %w", err) } } else { client, err = gitlab.NewClient(token) if err != nil { return fmt.Errorf("could not create GitLab client: %w", err) } } _, passwordResp, err := client.ProjectVariables.CreateVariable(ref, &gitlab.CreateProjectVariableOptions{ Key: gitlab.Ptr("COSIGN_PASSWORD"), Value: gitlab.Ptr(string(keys.Password())), VariableType: gitlab.Ptr(gitlab.EnvVariableType), Protected: gitlab.Ptr(false), Masked: gitlab.Ptr(false), EnvironmentScope: gitlab.Ptr("*"), }) if err != nil { ui.Warnf(ctx, "If you are using a self-hosted gitlab please set the \"GITLAB_HOST\" your server name.") return fmt.Errorf("could not create \"COSIGN_PASSWORD\" variable: %w", err) } if passwordResp.StatusCode < 200 && passwordResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(passwordResp.Body) return fmt.Errorf("%s", bodyBytes) } ui.Infof(ctx, "Password written to \"COSIGN_PASSWORD\" variable") _, privateKeyResp, err := client.ProjectVariables.CreateVariable(ref, &gitlab.CreateProjectVariableOptions{ Key: gitlab.Ptr("COSIGN_PRIVATE_KEY"), Value: gitlab.Ptr(string(keys.PrivateBytes)), VariableType: gitlab.Ptr(gitlab.EnvVariableType), Protected: gitlab.Ptr(false), Masked: gitlab.Ptr(false), }) if err != nil { return fmt.Errorf("could not create \"COSIGN_PRIVATE_KEY\" variable: %w", err) } if privateKeyResp.StatusCode < 200 && privateKeyResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(privateKeyResp.Body) return fmt.Errorf("%s", bodyBytes) } ui.Infof(ctx, "Private key written to \"COSIGN_PRIVATE_KEY\" variable") _, publicKeyResp, err := client.ProjectVariables.CreateVariable(ref, &gitlab.CreateProjectVariableOptions{ Key: gitlab.Ptr("COSIGN_PUBLIC_KEY"), Value: gitlab.Ptr(string(keys.PublicBytes)), VariableType: gitlab.Ptr(gitlab.EnvVariableType), Protected: gitlab.Ptr(false), Masked: gitlab.Ptr(false), }) if err != nil { return fmt.Errorf("could not create \"COSIGN_PUBLIC_KEY\" variable: %w", err) } if publicKeyResp.StatusCode < 200 && publicKeyResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(publicKeyResp.Body) return fmt.Errorf("%s", bodyBytes) } ui.Infof(ctx, "Public key written to \"COSIGN_PUBLIC_KEY\" variable") if err := os.WriteFile("cosign.pub", keys.PublicBytes, 0o600); err != nil { return err } ui.Infof(ctx, "Public key also written to cosign.pub") return nil } func (g *Gl) GetSecret(_ context.Context, ref string, key string) (string, error) { token, tokenExists := env.LookupEnv(env.VariableGitLabToken) var varPubKeyValue string if !tokenExists { return varPubKeyValue, fmt.Errorf("could not find %q", env.VariableGitLabToken.String()) } var client *gitlab.Client var err error if url, baseURLExists := env.LookupEnv(env.VariableGitLabHost); baseURLExists { client, err = gitlab.NewClient(token, gitlab.WithBaseURL(url)) if err != nil { return varPubKeyValue, fmt.Errorf("could not create GitLab client): %w", err) } } else { client, err = gitlab.NewClient(token) if err != nil { return varPubKeyValue, fmt.Errorf("could not create GitLab client: %w", err) } } varPubKey, pubKeyResp, err := client.ProjectVariables.GetVariable(ref, key, nil) if err != nil { return varPubKeyValue, fmt.Errorf("could not retrieve \"COSIGN_PUBLIC_KEY\" variable: %w", err) } varPubKeyValue = varPubKey.Value if pubKeyResp.StatusCode < 200 && pubKeyResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(pubKeyResp.Body) return varPubKeyValue, fmt.Errorf("%s", bodyBytes) } return varPubKeyValue, nil } cosign-2.5.0/pkg/cosign/keys.go000066400000000000000000000154141477503325500163650ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" _ "crypto/sha256" // for `crypto.SHA256` "crypto/x509" "encoding/pem" "errors" "fmt" "os" "path/filepath" "github.com/secure-systems-lab/go-securesystemslib/encrypted" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" ) const ( CosignPrivateKeyPemType = "ENCRYPTED COSIGN PRIVATE KEY" SigstorePrivateKeyPemType = "ENCRYPTED SIGSTORE PRIVATE KEY" // PEM-encoded PKCS #1 RSA private key RSAPrivateKeyPemType = "RSA PRIVATE KEY" // PEM-encoded ECDSA private key ECPrivateKeyPemType = "EC PRIVATE KEY" // PEM-encoded PKCS #8 RSA, ECDSA or ED25519 private key PrivateKeyPemType = "PRIVATE KEY" BundleKey = static.BundleAnnotationKey RFC3161TimestampKey = static.RFC3161TimestampAnnotationKey ) // PassFunc is the function to be called to retrieve the signer password. If // nil, then it assumes that no password is provided. type PassFunc func(bool) ([]byte, error) type Keys struct { private crypto.PrivateKey public crypto.PublicKey } type KeysBytes struct { PrivateBytes []byte PublicBytes []byte password []byte } func (k *KeysBytes) Password() []byte { return k.password } // GeneratePrivateKey generates an ECDSA private key with the P-256 curve. func GeneratePrivateKey() (*ecdsa.PrivateKey, error) { return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) } // ImportKeyPair imports a key pair from a file containing a PEM-encoded // private key encoded with a password provided by the 'pf' function. // The private key can be in one of the following formats: // - RSA private key (PKCS #1) // - ECDSA private key // - PKCS #8 private key (RSA, ECDSA or ED25519). func ImportKeyPair(keyPath string, pf PassFunc) (*KeysBytes, error) { kb, err := os.ReadFile(filepath.Clean(keyPath)) if err != nil { return nil, err } p, _ := pem.Decode(kb) if p == nil { return nil, fmt.Errorf("invalid pem block") } var pk crypto.Signer switch p.Type { case RSAPrivateKeyPemType: rsaPk, err := x509.ParsePKCS1PrivateKey(p.Bytes) if err != nil { return nil, fmt.Errorf("error parsing rsa private key: %w", err) } if err = cryptoutils.ValidatePubKey(rsaPk.Public()); err != nil { return nil, fmt.Errorf("error validating rsa key: %w", err) } pk = rsaPk case ECPrivateKeyPemType: ecdsaPk, err := x509.ParseECPrivateKey(p.Bytes) if err != nil { return nil, fmt.Errorf("error parsing ecdsa private key") } if err = cryptoutils.ValidatePubKey(ecdsaPk.Public()); err != nil { return nil, fmt.Errorf("error validating ecdsa key: %w", err) } pk = ecdsaPk case PrivateKeyPemType: pkcs8Pk, err := x509.ParsePKCS8PrivateKey(p.Bytes) if err != nil { return nil, fmt.Errorf("error parsing pkcs #8 private key") } switch k := pkcs8Pk.(type) { case *rsa.PrivateKey: if err = cryptoutils.ValidatePubKey(k.Public()); err != nil { return nil, fmt.Errorf("error validating rsa key: %w", err) } pk = k case *ecdsa.PrivateKey: if err = cryptoutils.ValidatePubKey(k.Public()); err != nil { return nil, fmt.Errorf("error validating ecdsa key: %w", err) } pk = k case ed25519.PrivateKey: if err = cryptoutils.ValidatePubKey(k.Public()); err != nil { return nil, fmt.Errorf("error validating ed25519 key: %w", err) } pk = k default: return nil, fmt.Errorf("unexpected private key") } default: return nil, fmt.Errorf("unsupported private key") } return marshalKeyPair(p.Type, Keys{pk, pk.Public()}, pf) } func marshalKeyPair(ptype string, keypair Keys, pf PassFunc) (key *KeysBytes, err error) { x509Encoded, err := x509.MarshalPKCS8PrivateKey(keypair.private) if err != nil { return nil, fmt.Errorf("x509 encoding private key: %w", err) } password := []byte{} if pf != nil { password, err = pf(true) if err != nil { return nil, err } } encBytes, err := encrypted.Encrypt(x509Encoded, password) if err != nil { return nil, err } // default to SIGSTORE, but keep support of COSIGN if ptype != CosignPrivateKeyPemType { ptype = SigstorePrivateKeyPemType } // store in PEM format privBytes := pem.EncodeToMemory(&pem.Block{ Bytes: encBytes, Type: ptype, }) // Now do the public key pubBytes, err := cryptoutils.MarshalPublicKeyToPEM(keypair.public) if err != nil { return nil, err } return &KeysBytes{ PrivateBytes: privBytes, PublicBytes: pubBytes, password: password, }, nil } func GenerateKeyPair(pf PassFunc) (*KeysBytes, error) { priv, err := GeneratePrivateKey() if err != nil { return nil, err } // Emit SIGSTORE keys by default return marshalKeyPair(SigstorePrivateKeyPemType, Keys{priv, priv.Public()}, pf) } // PemToECDSAKey marshals and returns the PEM-encoded ECDSA public key. func PemToECDSAKey(pemBytes []byte) (*ecdsa.PublicKey, error) { pub, err := cryptoutils.UnmarshalPEMToPublicKey(pemBytes) if err != nil { return nil, err } ecdsaPub, ok := pub.(*ecdsa.PublicKey) if !ok { return nil, fmt.Errorf("invalid public key: was %T, require *ecdsa.PublicKey", pub) } return ecdsaPub, nil } // LoadPrivateKey loads a cosign PEM private key encrypted with the given passphrase, // and returns a SignerVerifier instance. The private key must be in the PKCS #8 format. func LoadPrivateKey(key []byte, pass []byte) (signature.SignerVerifier, error) { // Decrypt first p, _ := pem.Decode(key) if p == nil { return nil, errors.New("invalid pem block") } if p.Type != CosignPrivateKeyPemType && p.Type != SigstorePrivateKeyPemType { return nil, fmt.Errorf("unsupported pem type: %s", p.Type) } x509Encoded, err := encrypted.Decrypt(p.Bytes, pass) if err != nil { return nil, fmt.Errorf("decrypt: %w", err) } pk, err := x509.ParsePKCS8PrivateKey(x509Encoded) if err != nil { return nil, fmt.Errorf("parsing private key: %w", err) } switch pk := pk.(type) { case *rsa.PrivateKey: return signature.LoadRSAPKCS1v15SignerVerifier(pk, crypto.SHA256) case *ecdsa.PrivateKey: return signature.LoadECDSASignerVerifier(pk, crypto.SHA256) case ed25519.PrivateKey: return signature.LoadED25519SignerVerifier(pk) default: return nil, errors.New("unsupported key type") } } cosign-2.5.0/pkg/cosign/keys_test.go000066400000000000000000000521661477503325500174310ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "crypto/rand" "errors" "os" "path/filepath" "testing" "github.com/stretchr/testify/require" ) const validrsa = `-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAx5piWVlE62NnZ0UzJ8Z6oKiKOC4dbOZ1HsNhIRtqkM+Oq4G+ 25yq6P+0JU/Qvr9veOGEb3R/J9u8JBo+hv2i5X8OtgvP2V2pi6f1s6vK7L0+6uRb 4YTT/UdMshaVf97MgEqbq41Jf/cuvh+3AV0tZ1BpixZg4aXMKpY6HUP69lbsu27o SUN1myMv7TSgZiV4CYs3l/gkEfpysBptWlcHRuw5RsB+C0RbjRtbJ/5VxmE/vd3M lafd5t1WSpMb8yf0a84u5NFaXwZ7CweMfXeOddS0yb19ShSuW3PPRadruBM1mq15 js9GfagPxDS75Imcs+fA62lWvHxEujTGjYHxawIDAQABAoIBAH+sgLwmHa9zJfEo klAe5NFe/QpydN/ziXbkAnzqzH9URC3wD+TpkWj4JoK3Sw635NWtasjf+3XDV9S/ 9L7j/g5N91r6sziWcJykEsWaXXKQmm4lI6BdFjwsHyLKz1W7bZOiJXDWLu1rbrqu DqEQuLoc9WXCKrYrFy0maoXNtfla/1p05kKN0bMigcnnyAQ+xBTwoyco4tkIz5se IYxorz7qzXrkHQI+knz5BawmNe3ekoSaXUPoLoOR7TRTGsLteL5yukvWAi8S/0rE gftC+PZCQpoQhSUYq7wXe7RowJ1f+kXb7HsSedOTfTSW1D/pUb/uW+CcRKig42ZI I9H9TAECgYEA5XGBML6fJyWVqx64sHbUAjQsmQ0RwU6Zo7sqHIEPf6tYVYp7KtzK KOfi8seOOL5FSy4pjCo11Dzyrh9bn45RNmtjSYTgOnVPSoCfuRNfOcpG+/wCHjYf EjDvdrCpbg59kVUeaMeBDiyWAlM48HJAn8O7ez2U/iKQCyJmOIwFhSkCgYEA3rSz Fi1NzqYWxWos4NBmg8iKcQ9SMkmPdgRLAs/WNnZJ8fdgJZwihevkXGytRGJEmav2 GMKRx1g6ey8fjXTQH9WM8X/kJC5fv8wLHnUCH/K3Mcp9CYwn7PFvSnBr4kQoc/el bURhcF1+/opEC8vNX/Wk3zAG7Xs1PREXlH2SIHMCgYBV/3kgwBH/JkM25EjtO1yz hsLAivmAruk/SUO7c1RP0fVF+qW3pxHOyztxLALOmeJ3D1JbSubqKf377Zz17O3b q9yHDdrNjnKtxhAX2n7ytjJs+EQC9t4mf1kB761RpvTBqFnBhCWHHocLUA4jcW9v cnmu86IIrwO2aKpPv4vCIQKBgHU9gY3qOazRSOmSlJ+hdmZn+2G7pBTvHsQNTIPl cCrpqNHl3crO4GnKHkT9vVVjuiOAIKU2QNJFwzu4Og8Y8LvhizpTjoHxm9x3iV72 UDELcJ+YrqyJCTe2flUcy96o7Pbn50GXnwgtYD6WAW6IUszyn2ITgYIhu4wzZEt6 s6O7AoGAPTKbRA87L34LMlXyUBJma+etMARIP1zu8bXJ7hSJeMcog8zaLczN7ruT pGAaLxggvtvuncMuTrG+cdmsR9SafSFKRS92NCxhOUonQ+NP6mLskIGzJZoQ5JvQ qGzRVIDGbNkrVHM0IsAtHRpC0rYrtZY+9OwiraGcsqUMLwwQdCA= -----END RSA PRIVATE KEY-----` // RSA 2048 key encoded with PCKS#1 const validrsapkcs1 = `-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqvVkqzBrMzl4TC5VsBgXnQiCo861QcB1TqdwgGzYkrNdF6fr UisPWgjMJixolmpwHv+088rsbiSlD9hc9DmzgCJPto7wvGeTILa9cNHCGKm12q7K TFnVUTH9z6D4E3F/IsI22Pg8+cSyeDn+LKxMadTohlcXJ8jqcH75KezoGngMp5OJ vqs93lkLep+UJMspV029z7PzF9uoT8gI02Adfq5Zkfu8VmIy8gkpYgTBCpNnD01u vo6HoAYG/mHqgPYivWBwi221GPjJWmCPB1rIJHlpAYUETA5jUY2UwP1oQx54ybcj eNjMRP5J0cGyOI8MT0j9ul7/DJde3Ds5A7BA9QIDAQABAoIBAQCJ3Q5rhsZMLsI2 HP943FTei+heFOnStlNjNF/jEOOtmfsugnmgb50XrBSFjDZjZj44oVjZaQE06VQ6 7O44/PcmE4VY4Ph91sCtFvC6NE1j+ifuzBnTbHY73iah81tawqIV86yrV7REbzzE +29fsyqEBe/ltgG0Ua/NPHfOOYALJwZVx8ozkz7xOyU23kNxSzp3T0FBnYYIuzrI a4h7FVxGLbIJQ3xWBU5xkd4m7EqgFYkWCfSXAVoLT2z7eJSYAmITuiQXl8uDz1XY lWKgOwkRJrMVVD8hDME7Hoc/RlKmYX64IZ3lv70NuyKDPTuhmoIQRJ49mVaqdPtH v0Z9L0tBAoGBAMPFcJFaR+VdmsZ2DXQlsPQNAB064SYbIXx/pxNVoDYkHyMfZsf3 vjf4gMKNsHTM4u812UpsE5762OqdVKmWXQc60mkuEk7N55iXuBJiJxSpuj6IbiLw ogV+B40UC9luOISQpDYdY1Km1ho4HRngNkXMlJ48tFuwIP3lwwz3FtFZAoGBAN+N wVssBvNhHzGfcUMxxCwJKfHCx1ANWuTe+AsDtpZRTExMcX1PH1euxUV9aII9Klg7 A7FN1It78pDrQBNQJoeMON+5N53//geY6stDfhPkOoT8Zqg2VEz4WRihUgAUHESk pUVYSvEXG7J7AG5iGgn0B3P9PMvvReIHnTeQ1rz9AoGAWAR31NHrSyMniBzhdZvQ kBkcOQgU3AYMqyXVXyr7KfxZh3gBxNwMyKtQcKg1cn3/dZ8XP4+RzsNnLSxpOQni b3Kx0RomnwmSG5fy6Uj52x9oHd9G7SyVG7UK/hHKNgqJHIjPW4kg87MQxZ7+7nhQ zlbpZq9SQ3rPind3l2er+ZkCgYASFs9ZiEN7uBUlF8i7bjB4e7lYJbGpCZucP2qE waUpnqR03A6m3BsmJi8yQ0aMm1Rs1UGkPC8BpmLnVRHXPjoP58nGWJ9meotcpAQD tI9kHqiZkC7iV5sUq1fSRWN0PCxZZZU1+kH+JieIlqlfRTLkMUnVGd2shsz50DHp iB/IJQKBgAO3kRRVszif2jdo7gzDsiSQ+fSyD6yEE9eP+uNwLZw9bQhyW5wlXF+t dR5olNrc0bP542MHL5vigRnezq9hT1hbLkQg/MA2k5FrMHIZshfWITnI5B5I2sw6 wu/XEVtNr8RincoHXjov4DiqgbLPWubM7FHLN5CW6nRLXhGkb4+7 -----END RSA PRIVATE KEY-----` // RSA 2048 key encoded with PKCS#8 const validrsapkcs8 = `-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwDtRl4McMhk4Q UD723TzrAlYt59w73Qy6SZJLdaRKLjl0T8NY4NqnscIl/ZFTPh7SwqIF7D33riYB sp60j6cV7ADh3fiIFtl6hYMAEpgqboJgTSlyeVX2jeyZCg25stv9AtvcT1B5ZVqa +CY4feeUzp4LbVb4vY0nk1NHP95AJkJdcQaod44NXkc7AVb8h/atWx53AujXQsJY 0t84Z1+WCUhA+2D1ECdGJlkinwy/cBjABKtMe+4jN4z9vRXnIqHn8gpTsoBqSWRP Ua2aNQzho/JnbDSWDu8Jzs+75llwzGxKFEIsAVpB5v63ir5lVejgf2Bs2JSCfn0W BSqdxaLXAgMBAAECggEAUwEK3mVVMvB3CXXr2ZOAzwOxAb+Ys5iKEaHyGSWDqX2V lOKuJM8OB5XlBOhBhc951L/yh3xT0twGCzLdZB9+FPXJjLOMIw0yx3L+yh/6Ibcs PJ7kdZYDE1TiQVzeD7jlwqmAYqP6OuGwD/QCgQvLDPtEw/pu0KL9U7U/xA22iOM4 MpbCZgQUBYvIikUgUBnhxtq9CXf9+NZOKGrLUV7zNn2Abqyw5047hDpBRejfiOIw a6oO3UykJyEKv4P6DmCSIZyBUbgeo+jvJ/4FsGYvCSqIYCdtvOaHqHI5w5cwgUy5 hwLuqXNG4X/sqqmvqICVB2efK+vrGmGiUFizkWDjsQKBgQDi4mbKNsa1sDMS/F4W fx0kNlriPGAlS5+I1RgGXOh3sQ3D2hi2hDKym3KlPVXMmDBH+FqHt83uPXXSEpTo d6jCyprw0kBgmfZCnWrWEMY1KGHPsPDO+Vx7nsrpgPA9EpftXibgTu1wQqNR+NOE RwweniZhqOjGkjMZagMvS701UwKBgQDGpq3c9SRPJT6TD77akfrWs2nKjXsD9xzP 1mjXVN+ft6Om5olSANrH8nRsbND4BfYMfDbm/LscjM/qZ8ueC/KRZZEBoB9qy7R9 76JbqvCctFLorTy6RNIYno0JS5tivU93SOKDv1V/eXuZb3BVyixyhIaCjjjjnE7u AP195YIH7QKBgElHVGm1XWKrQSO9rOnZLmFWyO3PEEKbdTBtmu/bLB4Ualy6YUb5 1aIIQPQLpl2JPfbQyPSSsgljgl1SMRQQKcqYQ4jKb46Dy5ziWPJAwrPCkizRekVv Fqa6t9DJG06uZbF9ulKyS0/5xeQg2LgddlWhQMZEFsKjz6tCqTqqXLcPAoGBAIBY FCCb6XeREpqlI6PHiQ7KH+GUAxSOxXiqiFYHKevhE8SzUak/kBp61SlwLJryDwQG BNq8Eo/hkjtaED3ubivuOP+Z2nJ/Zf+voXAkQwybnK1jr8aQzETHu0t0I9JpiTwC RQblyXFwpaB+VU+4LXtXkCgthyfXR0+SKDT84UQJAoGAPxwl93a6voIoTmuT1WJn RdInSOS8J/13xjhlTHwpWBbUVVZDZyM+vGit0e3cTBSoDnF4/axKVF9veN3GF0RY doAvTv0rhEQ8VmRQY9cZsaPMVg0q9UcSLLel9lJRRcEXa7v2aUpTNXVtar9csphX 5ybCksQRTS2adHuIdOa111g= -----END PRIVATE KEY-----` const invalidrsawithpubkey = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx5piWVlE62NnZ0UzJ8Z6 oKiKOC4dbOZ1HsNhIRtqkM+Oq4G+25yq6P+0JU/Qvr9veOGEb3R/J9u8JBo+hv2i 5X8OtgvP2V2pi6f1s6vK7L0+6uRb4YTT/UdMshaVf97MgEqbq41Jf/cuvh+3AV0t Z1BpixZg4aXMKpY6HUP69lbsu27oSUN1myMv7TSgZiV4CYs3l/gkEfpysBptWlcH Ruw5RsB+C0RbjRtbJ/5VxmE/vd3Mlafd5t1WSpMb8yf0a84u5NFaXwZ7CweMfXeO ddS0yb19ShSuW3PPRadruBM1mq15js9GfagPxDS75Imcs+fA62lWvHxEujTGjYHx awIDAQAB -----END PUBLIC KEY-----` // RSA key with size 1024 const invalidrsasmallkey = `-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCYm7UwrM1fEsB+pOagKMRy1G9sX3A9MbC+a/G/jI+AERiNhCxa dQUnicqs1ct78mTSH15+MJa39tYhwwh2yePUMAzP4yeiFluCAjxl8MQOqg2Q0i2+ juF72HekB1RoXj5To7j9iYL71F3+A7C+ewvMRDQZ6wsJladiM3xbx3swhwIDAQAB AoGAKstvUgkDRmfxxxHjAoKsJC9iV5ej1+U5VQzcLAT0sMsagYTRE0TBf0bqqPED MOzWTP4y91wUx93WSn1wwC75TjaRQ8YS9u8lFN/7ANo7zEucCSMlomEJgdvURmpS JwsEyx03W+VCPTIz1WNFG04ICnLQvimOihU6nanDE8t4UoECQQDKFpLsipARIgVs Zr+V7CgN33O7sduaZvakq9ymgHqd5L1B+USglCVXBCYUPTombT9Li8ecDZk69e6I aWm1Pb9hAkEAwVHyTzI3Lu4LCx/T6mTOaJR1ads6XlFqTKUNBLd+PeL89cJhnigO Ad0faD4hW61IF0lHjPemLo3c6nGeNPOA5wJAIHLNdpOtHEMlMcmxu4XmzItzjtC5 HSqpMbmyvT1l8tJWnTBEF7CR6k3tO1S1cJQcFKpGC8WXNANnIJokcgiPIQJAJBNs yoaucZ2OhgbsfwNM2YtK1fRJUiyTT7ZFVaoAbwAbAKnDmcYTxxlCsStXAkq191J/ fbkBVBK5NS76vRrr5QJAGdcfJrnB9UIJQrTrXZWYSGCqkKSHhSrI4bLxrG2I+KlR yAA/xDHlrlrCK260XFCi47rpAowtLwB1JbUwGr6x0Q== -----END RSA PRIVATE KEY-----` // RSA key with size 5120 const invalidrsalargekey = `-----BEGIN RSA PRIVATE KEY----- MIILaQIBAAKCAoEA1Lu6U/a2p7AmuXPxaAfkrLaRkiWfnfddkl0kWfbO8a+sAwpi E4p6e33xcH65PLJh22a2KoZotobFq41uK0I0Jr0tCU/ZrtJIynLpq/jS/uLH2foY fsymXzO6f45Sfyo0CRVGEs+TQbU/XM7wEJu9JkJsocBYnWqDmjQAMkPLGjK5xAqC k3jEhwzlkgMSAAVU5aO3lol5N3j2A72JgX3uPkLXvkQoPdBSOeTPfpevjlFbnWEl SSyIAlR17yrlQfi/MccK5/4DaMWrzWqJLtK9wtRl7sx1Vn20xNbgavmTbBxZzwwH bMND6TGBeIbnZ+dN4u2sPcs36Y04qQ0yA6kitHvPEk/exS5NvY6I8KSTxbeECecM 1cXfVe/7FoyxE3G7+7VbdxWoy8yZav1If15ATictn2fp9u3DM3Z+Qjd9YhHyZoPt dZQqlLcQTMtaBubMeKcbY8JMDNc29ljtO1ClMPNxd50MYO0sXl/QnXnIfPjecDV/ I0JHLtiwrXk1LxFrErZZGFskvGsS4APtI0Ds5r+j0d5QPuffqyKpadhf6+yashsI HV123q7iMg1v6bBd/+tQobSBYRtTSGO37Ct428MKqG6kz5P/txmrBFr/JhjOGBBZ WhXmTm965DLsfDejpEc+ocP8hjiKcn6qfKXjSfulMUGgEKue4iSvB62qMRNFkpH/ 9Ftg9v+2bySURYiJWSu/A0RmXkRlKoTFGmmI0BOBGKpLDwnLhc2kZab6iqjDHBx1 qJAM58AAwWVQtzcY42RVMxzFVMWtdV2uVbPIOPX9ZXKOyex9b3+JD9XjCkBE1Wjv a3PEZ0CTjSrAqtgQ3DN88P9izduuPlJSVb8+gwIDAQABAoICgEU+ZcP2xjWG7NPo nWdTSme9dVywymfMoLSHhNGTuICKwd6rfokFxiB0OiZ32SuclKWppRnqbiMbczQH 8Rg7kGYbpZEmYKC66d6b0NudPnCguJSHB3oeevj6CXaDiO7DefSK7CgrUK9Oo7U9 1n5RcxwE+v8bcLyscvG6g2XZEz8Py8+37BC8epvK4t7ICQ/grGWjCJsDXGVmBg3p n9x6dRXnA/p2jPKx4FHf3HpEPWyBpuRvPoe26v53J3wV5lG2+eTl+PLSh6GO1gEi 8ExBZGsKX7N+8aKZgEGh/6JSYl4KTGFMdQ498NjyuEXXA3Oaoot++VWT1Ds9MHg2 R1VRtG4y7o/zV3uvOra8sm5B46ezuFLQ1iivI6cBWiVY3jqKBrpDqeX1MuBDVJyy nOp4b04BCqScWld5xNP3edlr3nAQ9xRod6BcMB9195uFnqJDcvbjKnJH6vDACOgS loXApGW4zgmJ6hSk0Rwn1+3/JKpe149kkH0sRk2y2LE2C3hBOSbJ+z5z6wcyLEZi QIRIV2IW+XFBLdJeUo+nx0AMV9wxUNlgs9q6ILFS78bxMWCi3Uy6ozTBaz7LPAGU 2zf//nbIXpOlJbMrK1xvGgMa5VUldCvC3oFsDkrLcNWb/cX5GDxPl6vNedPjiTw7 xcT7boZPNRpj1i3txR6eAoH5kU9NQOMtvU2zJG2UbADX6r/SHmfSH4ghYDuG5UKL B1lY6H6GDUCFHWhhjY7e2yKfvMWGHfnvv8/cr3FwqXd0ksDELLzGEw08f329m9EV ryA6rLGjPhyomqXtctD/zpmQSraBpE95e0bVfNZQ2PX2P/r+NzyShUhnQmFqnFkq uoHDNoECggFBAPsyz6nQ8A6onswNzDFNEI26Bdf4ucQxqIO1fMuTfAv5+LGHQioO tr3h++moFHt9xD56WfxTUzAYoVrubzkhg7SNFnanqgtMgeUBtItRXMU/ql2MPA08 D6kwdPy262kizgKfkxpdLlXacBo9g/B4WQgixEJqZ2rBg1NsP4y1sqKbV0iVEN61 yuMoJicgYpg44QxXAQqXTu+oPAPCAa5e9OgLiEL2uYlo0IT2axH6iMvYs6+uZSRB t+J3eTpgR6wnNvlis3vzO5wszmRdNlh/i47tfIQGo8oNbzM0QMGOeZ0P5D75vc1h cqctwbTC5y2LgQbGcAnmsJ5J8ZtzSF0gnKYn/8f9WfzzWbZgOTajxqym2hDnNuT6 DzQhgD+exmA6cdt+DDRB0vH1xHzQ8Kvm+AvjM15K1b8Uo8S7o2Q2hjkRAoIBQQDY zLH1rSPKj7ZBcaxT+mA9LjYaykp43wjzMStB3Iw7b8CrRPwhdl9C1umW6l27e0ya aciA0sHIGSFquQ3Exh3lqSg8laoGcEfMp9gn5dmlcHmzz+YmKq9ydY/gvHutkxoe BDUb1ydcBNNimSz+ATQHq/+Mwlu6sfL6wwZ+S7LkF1goFPQmrdEAbvOhPerDkmki 2YE9VQ9CaWiPpobW4l/kYeFzDxC3706dRd56+jPmH13/3FPcOanSoy4CpZ8TlNix 8rrX9+b0o+BFxB5iqLIprimMwcPK3ioBzDYqkYzqp0nayZgUGf5fhsvsi7in08Ya hBJbup5M5I+3iGmJrXcyKBX5CHORraMmYZfdPxTX1bK0SGOcMl2Km32WbAiYYSe2 Faww6QHKw+K6WAGO6vieQsKOv73jLOTMOzfKZt7eUwKCAUEA88/7k33CotezWadC u89q88TMizVVSUJRp5TtzcIWsqEra1Q3Og8R+/dtxPpo9vu5EFM9KBXQNmyRoGqw 9ai75vDSDtTpzRGzOg2PqXGNM755o1bLqqTTJopr4iXBFEi93/n2k65BnP7ps+5l M2/8KlNkXnpcalftGXmFrRNmkUFpVH+q4h9dD2IWtf9O8ySx+oIv9pGqAh8uMQ+L Bi4QU3FuDmDe8KoVShjLD6Y2RHTO4wPIE4rd6ifAOJLevg9J4oCUaQhKoWkz4mI+ r2MMl+uV4ad4LlMfzXk4KSYakAGurhlEyiV9XRqiWsqaC7DNyT+t205XuytWIGWi pRFUOkm0j+4t+8BPIR8AKTKJUWaZXbKtq02ymAy0KAv5y8iuXjZXrhj9n+/FiMhb 8N27f/5EC49jK5Xi5r6g9lGdsaECggFAOEotipRBzr4xnBxfmg5QHpJ5CcusOmXu dPY3PQp+fpAtfkqTDD0nzrruO3jujVceNJlyrcALAGFGA+e4Y3btHEwnXlOdqb5N Zh3OSc2sDQB/GOjJ4O8ETrund7p4gkDHbzO4dloOph26pMcQn4LAd5145JsyJe8+ H02zyebts7s78GxAWCqZMXudVig1ZEIHejzvCXWkWKH1vBaIvBJaw3mGh9FJjfhc eQlDErsT7pQGXABg5bUzGrWzpIxMGVF0Uf+r85cyKCLEgFjDaupSF/BYaWuF4o58 aasUBUl1RRfaXSwqiE2XdkYRfIFqmGir7waLnbV+lIhjqEuK22xmnmc6DUbcet6S lcyRGajfSIr7s0N4WX3aO7rTiNLUCHxxSx2lb62QAY2KuMdQ4EKx+qVqzpWKQAnP /hcCDVNYWnECggFBALjWC1wKR8l2LusiaY+GPHM+8CIUb2++QBUYLDbnP4Tr97nu s+H6/d6763J/hmoD52JrCiCgs2w7NfXh5rPb3sxWoDHb3VWgjbH2q3WMXr7VECcQ vItnEiZSUbqns6buM+vSojhZOtP0mbC9sAJneF6bq93VnZNFDCiIjsHSgJAKYXLq vxm201n4DKlyUJMd4MBV8jkw28bJq38SWpx0gx7QgRIngMKQBkOyVYyZxmtbkq4Q KF5RR2WCFUZQ/4nfZRctlRkWTz2smRoO4aXTys5daAGx7EMVOuLvvdefmS+/P8VC jBytpQdBlp8J7TaieCtFIf2fzziAVOWFtV1UpZ81j44Xs8Fo7SuzcCHZ1uenm+oc mScruPO4CZ5cr0xP7Va3EDrZpoAtoPxmNVSztHGSK6z1E8j8xttn8mHZ+r5T -----END RSA PRIVATE KEY-----` const invalidkey = `-----BEGIN PGP PRIVATE KEY BLOCK----- Version: BCPG C# v1.6.1.0 lQOsBGGTOVkBCACbhVqCN55SElw1rZxI9LQDf91sU5FmrSybGh5r1xGV8rOhpKe+ eGirYVY3KeI6XUdZoJEIRtXtd6IJWn3msFRgO/MwkUQ4CibORSXPjCwHnseJmh5D axgZbXpzjP90fW03R+sBqm2AvrUANaWIKIXk8bWWdK5yUhB7TubIxpOZKg/nLlIE 1j6+XdCWIfo56z0mpJWRASzZRGuncfvkHRz73YfA00FpflQykiUDi6+vDV7KTh49 7nkivRwyx5JcsAT3W1MCXNjCEXsdmdtNah3mN7SMbzSh3RF+IMaonxT4KM5nmEj/ wGKJ4xUPtKy7kgIPYP+LMOj7j1qCsndYWILzABEBAAH/AwMC5uUvFLMg8b9gVFGU B1Ak38tCEBPtON9gSIxg9HX80WyMI8/MdfaisEsnFvy4X3UolhTlFJ9v3aqK1Zc8 JSkEw7cgY0NmFWDr6k8y8LhLN1ATjnKr9J9jzr8G9XvQfgaFbtcuFOF35ylQdeoL IKKa8GqrXL75rolg+p/OSw52n/7fb17fDXLNyeGQ0g8wjIVTv+7vuvr9Z0kxfIgG Y9oGIV/SeJvXjoWZWG3GbpTXx+ktmtwCY+tAlxJUt23OwWRfsnC9rS2DAsnJLlG2 r3Exfl80MUza1sQ/7u1svcHbFuZZOrJ1S9OjRQAWMsfQHFcav34Yrbb3aFweXLjs iT9BJOMR4W/nyXvKAnMt/6vHKfO6kbxCtDFstH5qZAKbSceWX1Y6UaGimHXCnTYi tlUMRNWlf6fFLdYBrRCh+MpLs5tSLc6NAYaQXTe3dJrjTRyzkxzYxeE/Y6Mii8KR gF3Fu5OwkJ39jKdWZf17i/LUofgQHzW4ymuDMWcrqX1kZXPjD6WN8c8NmNCGvlsT n1V6jPGb8tORIn8+CX+mCyJcxLpbG3ke90DIPnMol7WJ+3xV7J9peJqp0fY4jkmF I96EUhY1HTZcy4SnhiPwKb8NDpdqwFx1qwytf7eM+65Cf+rj9Nh6ShVOjIfOT9gh zEp0W0SFTU7p5af9ULnONCJABvRB8Gneosc6iwVclgHhTJcUzILRqNjcrJQu1j1v oK9Ls+VANww4zEOqx8g+T/P4pHmGTIYTDErzyDmBw8aFD7fDl+kPUtanqC1oTvnJ qZvoJ3JJ9Z2edW7Ulc1+BhnB8Cfs/jEJQHCngciUjW8yLUcVKdmFKkd9cajhoeQz bJp6/t9dRUVXo2ulZzvdN93TWV66rTxHQAI4OBZKqbQLYm9iQGJvYi5jb22JARwE EAECAAYFAmGTOVkACgkQSL3lExF3kQq7swf+Ndhd9iogkofT9ihMuMksoSDituN+ 7hZY5zCj0UzCS8o2vHU+XJCRCpRlOc7gxl+44S60YmwqKtTjT5eqXCXrqa1XTyZz xYpRfRjwnS6coQbdREQUvIgKapHII+b5gmnNhVX8niO11KyUHc29RWRiLFvMMcYO urG06WshDewpqrBdq0MYBSSWO7myQLR5xEW6ld6CKkU0153LHgVdlGVIzrLM7sRo NoHsidPbBIYv+aQxSVHxdKpFEpCHi9vckLSew+8LG5sDA/X3G4l9P3c1KusXP248 hfOiWo/4tMCN8XJpe0L+99ubcnHjQR7C8htFB4DnIA8KhMBSDdF/Vgp97g== =8+cN -----END PGP PRIVATE KEY BLOCK-----` // Valid NIST P-256 key const validecp256 = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIIGhcmCI5F7BPMH4r3pWCpQdAsveErdU5DjvVQerErJuoAoGCCqGSM49 AwEHoUQDQgAE+9E3Qe+h25ofmz3Uo2T004Dfy49iX06MMbxf9rsGmLkOPrS0KYDl 1QMfFuSbrtf8wTWNT9HNxrW/Foz39mDhHw== -----END EC PRIVATE KEY-----` // Valid NIST P-384 key const validecp384 = `-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDDWIebxSdyJVber6J/l5MKnD0+VU0b7fcjY/RbIxuIsHLVIvwJrSohY r3gJ/iJmIKGgBwYFK4EEACKhZANiAAQeoKZkkc1GafmIgp1tbNbMBjr2EdvX2dtT lGhzHJgE0YB6TavazcYH1iOBNmX7/pInCopiyWbjF/5olrRKJMG6DQz80td++fYf O4tr+Z3nyUgRGmf/fqYA4PSN30CuOMU= -----END EC PRIVATE KEY-----` // Valid NIST P-521 key const validecp521 = `-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIA3JhHw1bhJyNnK80SPnvuIY9h++IaEhZcHR7SDHzMnFf72O5fF/RI c+Dbd9lTfrP3Oy9BOC1opvXHvr5tqzV0MregBwYFK4EEACOhgYkDgYYABAALA20e WqTf2chokKaCmxfnhs1lW70/J8slBzKBG6x3OjJX+yrpkDqe8zpkN08E+RZA8RSE 9TqbcXVHGtR6tj0IMgEFmybW+efJr4nz/iBvchGeIg1HtOX5V97z0bmwsCgQCD8L 4cNcYkeNJazrzzXte5Y5DK4Vm8QE4Jkux6FCw19QhQ== -----END EC PRIVATE KEY-----` // Valid NIST P-256 key encoded with PKCS#8 const validecpkcs8 = `-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgmKnLL81dCEYc6Spq rFukFeTnU5JjtXfeNHAXJj3hYTahRANCAATjp4/Z4tUbZwUdId07GQeUdwLll4Br YLP25Fk+mBY2G0lyKqjqLG5hxhimbEJH6j+lzg5tcYpQgzXtOJ66Zi0N -----END PRIVATE KEY-----` // We consider NIST P-224 below our standard. const invalidecp224 = `-----BEGIN EC PRIVATE KEY----- MGgCAQEEHNpr8qVw+OcDrk/2aSqCGkWFKIjJSH0smeuloomgBwYFK4EEACGhPAM6 AARL+L9osuD8BYNr/aCkJiDHfDhxosXNOkcml4XPsnN88EjUqI2J7lMqmea5guwr eu5jUfVoZzti6A== -----END EC PRIVATE KEY-----` // Go crypto library does not support non-NIST curves. const invalidecunsupported = `-----BEGIN EC PRIVATE KEY----- MHQCAQEEIJ6iubjORxWrzL3Z0i30s80TuLD+N6YTYVk49nzl+O1eoAcGBSuBBAAK oUQDQgAEBadoFlV8pUuQ+WvRapCRJRGYk34h2nYkXW0BPdaCiPiEHawiVq9XXwG9 BuLB48bb77i0pTkVEefOuiNrbdBibw== -----END EC PRIVATE KEY-----` // PKCS#8 ED25519 key const ed25519key = `-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIALEbo1EFnWFqBK/wC+hhypG/8hXEerwdNetAoFoFVdv -----END PRIVATE KEY-----` // COSIGN labeled RSA key const pemcosignkey = `-----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 OCwicCI6MX0sInNhbHQiOiJ4WWdoc09JTUxUWGNOT0RsclNIOUNKc1FlOVFnZmN1 cmUrMXlLdHh1TlkwPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 Iiwibm9uY2UiOiI0cS9PSlVmaXJkSUkrUjZ0ajZBMmcyQ0JqL25xdFNicCJ9LCJj aXBoZXJ0ZXh0IjoiKzB4Q3NzcFN0WStBczdKanJpOWtsbHBWd2JhcUI4ZWJNdWto eS9aVE1MSXRsL3B1YS9jWVJvbytLRGxMWWdmOW1kSjk4K1FnQW9oTktoYnJPMTcw MHdBY1JTMjFDOE4zQUNJRUVZaWpOMllBNnMraGJSbkhjUnd4eGhDMDFtb2FvL0dO Y1pmbEJheXZMV3pXblo4d2NDZ2ZpT1o1VXlRTEFJMHh0dnR6dEh3cTdDV1Vhd3V4 RlhlNDZzck9TUE9SNHN6bytabWErUGovSFE9PSJ9 -----END ENCRYPTED COSIGN PRIVATE KEY-----` // COSIGN labeled EC key const pemcosigneckey = `-----BEGIN ENCRYPTED COSIGN PRIVATE KEY----- eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjo2NTUzNiwiciI6 OCwicCI6MX0sInNhbHQiOiJHK3F5WTYrNzhNS0JzMXNGTGs1ajYwcS9kS3Z1czBW VkhlSHZybC9POTF3PSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 Iiwibm9uY2UiOiJRc2JGdG13WDRDK2ttV3ZCcVRaMEFGOUFYdk1jRmg1SCJ9LCJj aXBoZXJ0ZXh0IjoiREM5T28zeldiYVQzSXYwdFVnWEdycjUxYW1samwwNlQ5MTNP VkxPbWpuMWhnK2o2WXRUbWg3SGhZSlY1N2J5eGE0Q281bE9YYmRqbTJ3aklubEd1 Um5aZCt5OExnekpSNzFSeEhKVzgrWmRlcFJmYWJMTjdHbDgrSFZEcERVQ3NxQnRh VngyblpGbFEwWUl1anZwbFphblNGaUVvdERLVGkxZ3VhUXIwUHNzYU01NXZxbTRY WS9rPSJ9 -----END ENCRYPTED COSIGN PRIVATE KEY-----` // SIGSTORE labeled key const pemsigstorekey = `-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6 OCwicCI6MX0sInNhbHQiOiI3T3VGd2VsbWZZNXVId2NoaURSc210anNwZ2ZlZjFG Mk5lOGFDTjVLYVpZPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 Iiwibm9uY2UiOiJQNHk4OGhCb3ZTa09MbXN0bFVBaGJwdDJ0K2xTNUxQSCJ9LCJj aXBoZXJ0ZXh0IjoiMnB1QzdyZldJOWh3bnJlQ2s4aUZDRlVwQlRrSzRJNlIvbFBF cnBDekpXUGpJWXl4eGVIL1A2VW52cFJHdVhla1NNb3JMdGhLamdoQ1JlNy82NDVH QWtoVm1LRC92eEF0S2EvbE1abENSQ3FlekJGUFd1dzNpeFRtZ2xhb2J1ZFVSbUVs bmNGOGlZbzBTMVl6Y1ZOMVFwY2J2c0dNcUlYRzVlbmdteGp5dCtBcXlyZTF0Q0Y0 V01tU1BlaEljNlBqd2h1Q2xHaVpJUWRvTGc9PSJ9 -----END ENCRYPTED SIGSTORE PRIVATE KEY-----` func pass(s string) PassFunc { return func(_ bool) ([]byte, error) { return []byte(s), nil } } func TestLoadECDSAPrivateKey(t *testing.T) { // Generate a valid keypair keys, err := GenerateKeyPair(pass("hello")) if err != nil { t.Fatal(err) } // Load the private key with the right password if _, err := LoadPrivateKey(keys.PrivateBytes, []byte("hello")); err != nil { t.Errorf("unexpected error decrypting key: %s", err) } // Try it with the wrong one if _, err := LoadPrivateKey(keys.PrivateBytes, []byte("wrong")); err == nil { t.Error("expected error decrypting key!") } // Try to decrypt garbage buf := [100]byte{} if _, err := rand.Read(buf[:]); err != nil { t.Fatal(err) } if _, err := LoadPrivateKey(buf[:], []byte("wrong")); err == nil { t.Error("expected error decrypting key!") } } func TestReadingPrivatePemTypes(t *testing.T) { pemECErrMsg := "parsing private key: x509: failed to parse private key (use ParseECPrivateKey instead for this key format)" testCases := []struct { pemType string pemData []byte expected error }{ { pemType: "COSIGN PEM RSA Type", pemData: []byte(pemcosignkey), expected: nil, }, { pemType: "COSIGN PEM EC Type", pemData: []byte(pemcosigneckey), expected: errors.New(pemECErrMsg), }, { pemType: "SISTORE PEM Type", pemData: []byte(pemsigstorekey), expected: nil, }, } for _, tc := range testCases { t.Run(tc.pemType, func(t *testing.T) { _, err := LoadPrivateKey(tc.pemData, []byte("hello")) if tc.expected == nil { require.NoError(t, err) } else { require.ErrorContains(t, err, tc.expected.Error()) } }) } } func TestWritingPrivatePemTypes(t *testing.T) { keys, err := GenerateKeyPair(pass("hello")) if err != nil { t.Fatal(err) } require.Contains(t, string(keys.PrivateBytes), SigstorePrivateKeyPemType) } func TestImportPrivateKey(t *testing.T) { testCases := []struct { fileName string pemData string expected error }{ // RSA tests { fileName: "validrsa.key", pemData: validrsa, expected: nil, }, { fileName: "validrsapkcs1.key", pemData: validrsapkcs1, expected: nil, }, { fileName: "validrsapkcs8.key", pemData: validrsapkcs8, expected: nil, }, { fileName: "invalidrsawithpubkey.key", pemData: invalidrsawithpubkey, expected: errors.New("unsupported private key"), }, { fileName: "invalidrsasmallkey.key", pemData: invalidrsasmallkey, expected: errors.New("error validating rsa key: key size not supported: 1024"), }, { fileName: "invalidrsalargekey.key", pemData: invalidrsalargekey, expected: errors.New("error validating rsa key: key size not supported: 5120"), }, // EC tests { fileName: "validecp256.key", pemData: validecp256, expected: nil, }, { fileName: "validecp384.key", pemData: validecp384, expected: nil, }, { fileName: "validecp521.key", pemData: validecp521, expected: nil, }, { fileName: "validecpkcs8.key", pemData: validecpkcs8, expected: nil, }, { fileName: "invalidecp224.key", pemData: invalidecp224, expected: errors.New("error validating ecdsa key: ECDSA curve P-224 not allowed"), }, { fileName: "invalidecunsupported.key", pemData: invalidecunsupported, expected: errors.New("error parsing ecdsa private key"), }, // ED25519 tests { fileName: "ed25519.key", pemData: ed25519key, expected: nil, }, // Additional tests { fileName: "invalidkey.key", pemData: invalidkey, expected: errors.New("invalid pem block"), }, } td := t.TempDir() for _, tc := range testCases { t.Run(tc.fileName, func(t *testing.T) { f := filepath.Join(td, tc.fileName) err := os.WriteFile(f, []byte(tc.pemData), 0600) if err != nil { t.Fatal(err) } keyBytes, err := ImportKeyPair(f, pass("hello")) if err == nil || tc.expected == nil { require.Equal(t, tc.expected, err) // Loading the private key should also work. _, err = LoadPrivateKey(keyBytes.PrivateBytes, []byte("hello")) require.Equal(t, tc.expected, err) } else { require.Equal(t, tc.expected.Error(), err.Error()) } }) } } cosign-2.5.0/pkg/cosign/kubernetes/000077500000000000000000000000001477503325500172255ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/kubernetes/client.go000066400000000000000000000033651477503325500210410ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "fmt" utilversion "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/kubernetes" // Initialize all known client auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" ) func client() (kubernetes.Interface, error) { cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( clientcmd.NewDefaultClientConfigLoadingRules(), nil).ClientConfig() if clientcmd.IsEmptyConfig(err) { cfg, err = rest.InClusterConfig() if err != nil { return nil, fmt.Errorf("error creating REST client config in-cluster: %w", err) } } else if err != nil { return nil, fmt.Errorf("error creating REST client config: %w", err) } return kubernetes.NewForConfig(cfg) } func checkImmutableSecretSupported(client kubernetes.Interface) (bool, error) { k8sVer, err := client.Discovery().ServerVersion() if err != nil { return false, err } semVer, err := utilversion.ParseSemantic(k8sVer.String()) if err != nil { return false, err } // https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable return semVer.Major() >= 1 && semVer.Minor() >= 21, nil } cosign-2.5.0/pkg/cosign/kubernetes/secret.go000066400000000000000000000075501477503325500210500ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "context" "errors" "fmt" "os" "strings" "github.com/sigstore/cosign/v2/pkg/cosign" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" ) const ( KeyReference = "k8s://" ) func GetKeyPairSecret(ctx context.Context, k8sRef string) (*v1.Secret, error) { namespace, name, err := parseRef(k8sRef) if err != nil { return nil, err } client, err := client() if err != nil { return nil, fmt.Errorf("new for config: %w", err) } var s *v1.Secret if s, err = client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}); err != nil { return nil, fmt.Errorf("checking if secret exists: %w", err) } return s, nil } func KeyPairSecret(ctx context.Context, k8sRef string, pf cosign.PassFunc) error { namespace, name, err := parseRef(k8sRef) if err != nil { return err } // now, generate the key in memory keys, err := cosign.GenerateKeyPair(pf) if err != nil { return fmt.Errorf("generating key pair: %w", err) } // create the k8s client client, err := client() if err != nil { return fmt.Errorf("new for config: %w", err) } immutable, err := checkImmutableSecretSupported(client) if err != nil { return fmt.Errorf("check immutable: %w", err) } var s *v1.Secret if s, err = client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}); err != nil { if k8serrors.IsNotFound(err) { s, err = client.CoreV1().Secrets(namespace).Create(ctx, secret(keys, namespace, name, nil, immutable), metav1.CreateOptions{}) if err != nil { return fmt.Errorf("creating secret %s in ns %s: %w", name, namespace, err) } } else { return fmt.Errorf("checking if secret exists: %w", err) } } else { // Update the existing secret s, err = client.CoreV1().Secrets(namespace).Update(ctx, secret(keys, namespace, name, s.Data, immutable), metav1.UpdateOptions{}) if err != nil { return fmt.Errorf("updating secret %s in ns %s: %w", name, namespace, err) } } fmt.Fprintf(os.Stderr, "Successfully created secret %s in namespace %s\n", s.Name, s.Namespace) if err := os.WriteFile("cosign.pub", keys.PublicBytes, 0600); err != nil { return err } fmt.Fprintln(os.Stderr, "Public key written to cosign.pub") return nil } // creates a secret with the following data: // * cosign.key // * cosign.pub // * cosign.password func secret(keys *cosign.KeysBytes, namespace, name string, data map[string][]byte, immutable bool) *v1.Secret { if data == nil { data = map[string][]byte{} } data["cosign.key"] = keys.PrivateBytes data["cosign.pub"] = keys.PublicBytes data["cosign.password"] = keys.Password() obj := metav1.ObjectMeta{ Name: name, Namespace: namespace, } // For Kubernetes >= 1.21, set Immutable by default if immutable { return &v1.Secret{ ObjectMeta: obj, Data: data, Immutable: ptr.To[bool](true), } } return &v1.Secret{ ObjectMeta: obj, Data: data, } } // the reference should be formatted as / func parseRef(k8sRef string) (string, string, error) { s := strings.Split(strings.TrimPrefix(k8sRef, KeyReference), "/") if len(s) != 2 { return "", "", errors.New("kubernetes specification should be in the format k8s:///") } return s[0], s[1], nil } cosign-2.5.0/pkg/cosign/kubernetes/secret_test.go000066400000000000000000000060671477503325500221110ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubernetes import ( "reflect" "testing" "github.com/sigstore/cosign/v2/pkg/cosign" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" ) func TestSecret(t *testing.T) { keys := &cosign.KeysBytes{ PrivateBytes: []byte("private"), PublicBytes: []byte("public"), } name := "secret" namespace := "default" expect := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "secret", Namespace: "default", }, Data: map[string][]byte{ "cosign.key": []byte("private"), "cosign.pub": []byte("public"), "cosign.password": nil, }, Immutable: ptr.To[bool](true), } actual := secret(keys, namespace, name, nil, true) if !reflect.DeepEqual(actual, expect) { t.Errorf("secret: %v, want %v", expect, actual) } } func TestSecretUpdate(t *testing.T) { keys := &cosign.KeysBytes{ PrivateBytes: []byte("private"), PublicBytes: []byte("public"), } name := "secret" namespace := "default" existing := map[string][]byte{ "foobar": []byte("hi"), "cosign.key": []byte("myoldkey"), } // Make sure old keys are preserved but the cosign ones are updated expect := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "secret", Namespace: "default", }, Data: map[string][]byte{ "foobar": []byte("hi"), "cosign.key": []byte("private"), "cosign.pub": []byte("public"), "cosign.password": nil, }, } actual := secret(keys, namespace, name, existing, false) if !reflect.DeepEqual(actual, expect) { t.Errorf("secret: %v, want %v", expect, actual) } } func TestParseRef(t *testing.T) { tests := []struct { desc string ref string name string namespace string shouldErr bool }{ { desc: "valid", ref: "k8s://default/cosign-secret", name: "cosign-secret", namespace: "default", }, { desc: "invalid, 1 field", ref: "k8s://something", shouldErr: true, }, { desc: "invalid, more than 2 fields", ref: "k8s://yet/another/arg", shouldErr: true, }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { ns, name, err := parseRef(test.ref) if (err == nil) == test.shouldErr { t.Fatal("unexpected error") } if test.shouldErr { return } if name != test.name { t.Fatalf("unexpected name: got %v expected %v", name, test.name) } if ns != test.namespace { t.Fatalf("unexpected name: got %v expected %v", ns, test.namespace) } }) } } cosign-2.5.0/pkg/cosign/obsolete.go000066400000000000000000000025011477503325500172170ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/sigstore/pkg/signature/payload" ) // ObsoletePayload returns the implied payload that some commands expect to match // the signature if no payload is provided by the user. // DO NOT ADD ANY NEW CALLERS OF THIS. func ObsoletePayload(ctx context.Context, digestedImage name.Digest) ([]byte, error) { blob, err := (&payload.Cosign{Image: digestedImage}).MarshalJSON() if err != nil { return nil, err } ui.Warnf(ctx, "using obsolete implied signature payload data (with digested reference %s); specify it explicitly with --payload instead", digestedImage.Name()) return blob, nil } cosign-2.5.0/pkg/cosign/obsolete_test.go000066400000000000000000000032231477503325500202600ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "testing" "github.com/google/go-containerregistry/pkg/name" "github.com/sigstore/cosign/v2/internal/ui" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestObsoletePayload(t *testing.T) { // This looks like a smoke test, but the property of generating _exactly_ the same string as previous versions is // essential. digestedImg, err := name.NewDigest("docker.io/namespace/image@sha256:4aa3054270f7a70b4528f2064ee90961788e1e1518703592ae4463de3b889dec") require.NoError(t, err) var res []byte stderr := ui.RunWithTestCtx(func(ctx context.Context, _ ui.WriteFunc) { r, err := ObsoletePayload(ctx, digestedImg) require.NoError(t, err) res = r }) assert.Contains(t, stderr, "obsolete implied signature payload") assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"index.docker.io/namespace/image"},"image":{"docker-manifest-digest":"sha256:4aa3054270f7a70b4528f2064ee90961788e1e1518703592ae4463de3b889dec"},"type":"cosign container image signature"},"optional":null}`), res) } cosign-2.5.0/pkg/cosign/pivkey/000077500000000000000000000000001477503325500163655ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/pivkey/disabled.go000066400000000000000000000045471477503325500204750ustar00rootroot00000000000000//go:build !pivkey || !cgo // +build !pivkey !cgo // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pivkey import ( "crypto/x509" "errors" "github.com/sigstore/sigstore/pkg/signature" ) // The empty struct is used so this file never imports piv-go which is // dependent on cgo and will fail to build if imported. type empty struct{} //nolint type Key struct{} func GetKey() (*Key, error) { return nil, errors.New("unimplemented") } func GetKeyWithSlot(slot string) (*Key, error) { //nolint: revive return nil, errors.New("unimplemented") } func (k *Key) Close() {} func (k *Key) Authenticate(pin string) {} //nolint: revive func (k *Key) SetSlot(slot string) {} //nolint: revive func (k *Key) Attest() (*x509.Certificate, error) { return nil, errors.New("unimplemented") } func (k *Key) GetAttestationCertificate() (*x509.Certificate, error) { return nil, errors.New("unimplemented") } func (k *Key) SetManagementKey(old, new [24]byte) error { //nolint: revive return errors.New("unimplemented") } func (k *Key) SetPIN(old, new string) error { //nolint: revive return errors.New("unimplemented") } func (k *Key) SetPUK(old, new string) error { //nolint: revive return errors.New("unimplemented") } func (k *Key) Reset() error { return errors.New("unimplemented") } func (k *Key) Unblock(puk, newPIN string) error { //nolint: revive return errors.New("unimplemented") } func (k *Key) GenerateKey(mgmtKey [24]byte, slot *empty, opts *empty) (*empty, error) { //nolint return nil, errors.New("unimplemented") } func (k *Key) Verifier() (signature.Verifier, error) { return nil, errors.New("unimplemented") } func (k *Key) Certificate() (*x509.Certificate, error) { return nil, errors.New("unimplemented") } func (k *Key) SignerVerifier() (signature.SignerVerifier, error) { return nil, errors.New("unimplemented") } cosign-2.5.0/pkg/cosign/pivkey/pivkey.go000066400000000000000000000134461477503325500202330ustar00rootroot00000000000000//go:build pivkey && cgo // +build pivkey,cgo // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pivkey import ( "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "errors" "fmt" "io" "os" "syscall" "github.com/go-piv/piv-go/v2/piv" "github.com/sigstore/sigstore/pkg/signature" "golang.org/x/term" ) var ( KeyNotInitialized error = errors.New("key not initialized") SlotNotSet error = errors.New("slot not set") ) type Key struct { Pub crypto.PublicKey Priv crypto.PrivateKey card *piv.YubiKey slot *piv.Slot pin string } func GetKey() (*Key, error) { cards, err := piv.Cards() if err != nil { return nil, err } if len(cards) == 0 { return nil, errors.New("no cards found") } if len(cards) > 1 { return nil, fmt.Errorf("found %d cards, please attach only one", len(cards)) } yk, err := piv.Open(cards[0]) if err != nil { return nil, err } return &Key{card: yk}, nil } func GetKeyWithSlot(slot string) (*Key, error) { card, err := GetKey() if err != nil { return nil, fmt.Errorf("open key: %w", err) } card.slot = SlotForName(slot) return card, nil } func (k *Key) Close() { k.Pub = nil k.Priv = nil k.slot = nil k.pin = "" k.card.Close() } func (k *Key) Authenticate(pin string) { k.pin = pin } func (k *Key) SetSlot(slot string) { k.slot = SlotForName(slot) } func (k *Key) Attest() (*x509.Certificate, error) { if k.card == nil { return nil, KeyNotInitialized } return k.card.Attest(*k.slot) } func (k *Key) GetAttestationCertificate() (*x509.Certificate, error) { if k.card == nil { return nil, KeyNotInitialized } return k.card.AttestationCertificate() } func (k *Key) SetManagementKey(old, new []byte) error { if k.card == nil { return KeyNotInitialized } return k.card.SetManagementKey(old, new) } func (k *Key) SetPIN(old, new string) error { if k.card == nil { return KeyNotInitialized } return k.card.SetPIN(old, new) } func (k *Key) SetPUK(old, new string) error { if k.card == nil { return KeyNotInitialized } return k.card.SetPUK(old, new) } func (k *Key) Reset() error { if k.card == nil { return KeyNotInitialized } return k.card.Reset() } func (k *Key) Unblock(puk, newPIN string) error { if k.card == nil { return KeyNotInitialized } return k.card.Unblock(puk, newPIN) } func (k *Key) GenerateKey(mgmtKey []byte, slot piv.Slot, opts piv.Key) (crypto.PublicKey, error) { if k.card == nil { return nil, KeyNotInitialized } return k.card.GenerateKey(mgmtKey, slot, opts) } func (k *Key) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) { return k.Pub, nil } func (k *Key) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error { sig, err := io.ReadAll(signature) if err != nil { return fmt.Errorf("read signature: %w", err) } msg, err := io.ReadAll(message) if err != nil { return fmt.Errorf("read message: %w", err) } digest := sha256.Sum256(msg) att, err := k.Attest() if err != nil { return fmt.Errorf("get attestation: %w", err) } switch kt := att.PublicKey.(type) { case *ecdsa.PublicKey: if ecdsa.VerifyASN1(kt, digest[:], sig) { return nil } return errors.New("invalid ecdsa signature") case *rsa.PublicKey: return rsa.VerifyPKCS1v15(kt, crypto.SHA256, digest[:], sig) } return fmt.Errorf("unsupported key type: %T", att.PublicKey) } func getPin() (string, error) { fmt.Fprint(os.Stderr, "Enter PIN for security key: ") // Unnecessary convert of syscall.Stdin on *nix, but Windows is a uintptr // nolint:unconvert b, err := term.ReadPassword(int(syscall.Stdin)) if err != nil { return "", err } fmt.Fprintln(os.Stderr, "\nPlease tap security key...") return string(b), err } func (k *Key) Verifier() (signature.Verifier, error) { if k.card == nil { return nil, KeyNotInitialized } if k.slot == nil { return nil, SlotNotSet } cert, err := k.card.Attest(*k.slot) if err != nil { return nil, err } k.Pub = cert.PublicKey return k, nil } func (k *Key) Certificate() (*x509.Certificate, error) { if k.card == nil { return nil, KeyNotInitialized } if k.slot == nil { return nil, SlotNotSet } return k.card.Certificate(*k.slot) } func (k *Key) SignerVerifier() (signature.SignerVerifier, error) { if k.card == nil { return nil, KeyNotInitialized } if k.slot == nil { return nil, SlotNotSet } cert, err := k.card.Attest(*k.slot) if err != nil { return nil, err } k.Pub = cert.PublicKey var auth piv.KeyAuth if k.pin == "" { auth.PINPrompt = getPin } else { auth.PIN = k.pin } privKey, err := k.card.PrivateKey(*k.slot, cert.PublicKey, auth) if err != nil { return nil, err } k.Priv = privKey return k, nil } func (k *Key) Sign(ctx context.Context, rawPayload []byte) ([]byte, []byte, error) { signer := k.Priv.(crypto.Signer) h := sha256.Sum256(rawPayload) sig, err := signer.Sign(rand.Reader, h[:], crypto.SHA256) if err != nil { return nil, nil, err } return sig, h[:], err } func (k *Key) SignMessage(message io.Reader, opts ...signature.SignOption) ([]byte, error) { signer := k.Priv.(crypto.Signer) h := sha256.New() if _, err := io.Copy(h, message); err != nil { return nil, err } sig, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256) if err != nil { return nil, err } return sig, err } cosign-2.5.0/pkg/cosign/pivkey/util.go000066400000000000000000000051421477503325500176730ustar00rootroot00000000000000//go:build pivkey && cgo // +build pivkey,cgo // Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pivkey import ( "github.com/go-piv/piv-go/v2/piv" ) func SlotForName(slotName string) *piv.Slot { switch slotName { case "": return &piv.SlotSignature case "authentication": return &piv.SlotAuthentication case "signature": return &piv.SlotSignature case "card-authentication": return &piv.SlotCardAuthentication case "key-management": return &piv.SlotKeyManagement default: return nil } } func PINPolicyForName(policyName string, slot piv.Slot) piv.PINPolicy { switch policyName { case "": return defaultPINPolicyForSlot(slot) case "never": return piv.PINPolicyNever case "once": return piv.PINPolicyOnce case "always": return piv.PINPolicyAlways default: return -1 } } func TouchPolicyForName(policyName string, slot piv.Slot) piv.TouchPolicy { switch policyName { case "": return defaultTouchPolicyForSlot(slot) case "never": return piv.TouchPolicyNever case "cached": return piv.TouchPolicyCached case "always": return piv.TouchPolicyAlways default: return -1 } } func defaultPINPolicyForSlot(slot piv.Slot) piv.PINPolicy { // // Defaults from https://developers.yubico.com/PIV/Introduction/Certificate_slots.html // switch slot { case piv.SlotAuthentication: return piv.PINPolicyOnce case piv.SlotSignature: return piv.PINPolicyAlways case piv.SlotKeyManagement: return piv.PINPolicyOnce case piv.SlotCardAuthentication: return piv.PINPolicyNever default: // This should never happen panic("invalid value for slot") } } func defaultTouchPolicyForSlot(slot piv.Slot) piv.TouchPolicy { // // Defaults from https://developers.yubico.com/PIV/Introduction/Certificate_slots.html // switch slot { case piv.SlotAuthentication: return piv.TouchPolicyCached case piv.SlotSignature: return piv.TouchPolicyAlways case piv.SlotKeyManagement: return piv.TouchPolicyCached case piv.SlotCardAuthentication: return piv.TouchPolicyNever default: // This should never happen panic("invalid value for slot") } } cosign-2.5.0/pkg/cosign/pkcs11key/000077500000000000000000000000001477503325500166715ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/pkcs11key/disabled.go000066400000000000000000000040171477503325500207710ustar00rootroot00000000000000//go:build !pkcs11key // +build !pkcs11key // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pkcs11key import ( "context" "crypto" "crypto/x509" "errors" "io" "github.com/sigstore/sigstore/pkg/signature" ) // The empty struct is used so this file never imports piv-go which is // dependent on cgo and will fail to build if imported. type empty struct{} //nolint type Key struct{} func GetKeyWithURIConfig(config *Pkcs11UriConfig, askForPinIfNeeded bool) (*Key, error) { //nolint: revive return nil, errors.New("unimplemented") } func (k *Key) Certificate() (*x509.Certificate, error) { return nil, errors.New("unimplemented") } func (k *Key) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) { //nolint: revive return nil, errors.New("unimplemented") } func (k *Key) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error { //nolint: revive return errors.New("unimplemented") } func (k *Key) Verifier() (signature.Verifier, error) { return nil, errors.New("unimplemented") } func (k *Key) Sign(ctx context.Context, rawPayload []byte) ([]byte, []byte, error) { //nolint: revive return nil, nil, errors.New("unimplemented") } func (k *Key) SignMessage(message io.Reader, opts ...signature.SignOption) ([]byte, error) { //nolint: revive return nil, errors.New("unimplemented") } func (k *Key) SignerVerifier() (signature.SignerVerifier, error) { //nolint: revive return nil, errors.New("unimplemented") } func (k *Key) Close() { } cosign-2.5.0/pkg/cosign/pkcs11key/pkcs11key.go000066400000000000000000000162401477503325500210360ustar00rootroot00000000000000//go:build pkcs11key // +build pkcs11key // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pkcs11key import ( "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "errors" "fmt" "io" "os" "path/filepath" "syscall" "github.com/ThalesIgnite/crypto11" "github.com/miekg/pkcs11" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/sigstore/pkg/signature" "golang.org/x/term" ) var ( ContextNotInitialized error = errors.New("context not initialized") SignerNotSet error = errors.New("signer not set") CertNotSet error = errors.New("certificate not set") ) type Key struct { ctx *crypto11.Context signer crypto.Signer cert *x509.Certificate } func GetKeyWithURIConfig(config *Pkcs11UriConfig, askForPinIfNeeded bool) (*Key, error) { conf := &crypto11.Config{ Path: config.ModulePath, Pin: config.Pin, } // At least one of object and id must be specified. if len(config.KeyLabel) == 0 && len(config.KeyID) == 0 { return nil, errors.New("one of keyLabel and keyID must be set") } // At least one of token and slot-id must be specified. if config.TokenLabel == "" && config.SlotID == nil { return nil, errors.New("one of token and slot id must be set") } // modulePath must be specified and must point to the absolute path of the PKCS11 module. if !filepath.IsAbs(config.ModulePath) { return nil, errors.New("modulePath does not point to an absolute path") } info, err := os.Stat(config.ModulePath) if err != nil { return nil, fmt.Errorf("access modulePath: %w", err) } if !info.Mode().IsRegular() { return nil, errors.New("modulePath does not point to a regular file") } // If no PIN was specified, and if askForPinIfNeeded is true, check to see if COSIGN_PKCS11_PIN env var is set. if conf.Pin == "" && askForPinIfNeeded { conf.Pin = env.Getenv(env.VariablePKCS11Pin) // If COSIGN_PKCS11_PIN not set, check to see if CKF_LOGIN_REQUIRED is set in Token Info. // If it is, and if askForPinIfNeeded is true, ask the user for the PIN, otherwise, do not. if conf.Pin == "" { askForPinIfNeededFunc := func() error { p := pkcs11.New(config.ModulePath) if p == nil { return errors.New("failed to load PKCS11 module") } err := p.Initialize() if err != nil { return fmt.Errorf("initialize PKCS11 module: %w", err) } defer p.Destroy() defer p.Finalize() var tokenInfo pkcs11.TokenInfo bTokenFound := false if config.SlotID != nil { tokenInfo, err = p.GetTokenInfo(uint(*config.SlotID)) if err != nil { return fmt.Errorf("get token info: %w", err) } } else { slots, err := p.GetSlotList(true) if err != nil { return fmt.Errorf("get slot list of PKCS11 module: %w", err) } for _, slot := range slots { currentTokenInfo, err := p.GetTokenInfo(slot) if err != nil { return fmt.Errorf("get token info: %w", err) } if currentTokenInfo.Label == config.TokenLabel { tokenInfo = currentTokenInfo bTokenFound = true break } } if !bTokenFound { return fmt.Errorf("could not find a slot for the token '%s'", config.TokenLabel) } } if tokenInfo.Flags&pkcs11.CKF_LOGIN_REQUIRED == pkcs11.CKF_LOGIN_REQUIRED { fmt.Fprintf(os.Stderr, "Enter PIN for key '%s' in PKCS11 token '%s': ", config.KeyLabel, config.TokenLabel) // Unnecessary convert of syscall.Stdin on *nix, but Windows is a uintptr // nolint:unconvert b, err := term.ReadPassword(int(syscall.Stdin)) if err != nil { return fmt.Errorf("get pin: %w", err) } conf.Pin = string(b) } return nil }() err := askForPinIfNeededFunc if err != nil { return nil, err } } } // We must set one SlotID or tokenLabel, never both. // SlotID has priority over tokenLabel. if config.SlotID != nil { conf.SlotNumber = config.SlotID } else if config.TokenLabel != "" { conf.TokenLabel = config.TokenLabel } ctx, err := crypto11.Configure(conf) if err != nil { return nil, err } // If both keyID and keyLabel are set, keyID has priority. var signer crypto11.Signer if len(config.KeyID) != 0 { signer, err = ctx.FindKeyPair(config.KeyID, nil) } else if len(config.KeyLabel) != 0 { signer, err = ctx.FindKeyPair(nil, config.KeyLabel) } if err != nil { return nil, err } // Key's corresponding cert might not exist, // therefore, we do not fail if it is the case. var cert *x509.Certificate ignoreCert := env.Getenv(env.VariablePKCS11IgnoreCertificate) == "1" if !ignoreCert { if len(config.KeyID) != 0 { cert, _ = ctx.FindCertificate(config.KeyID, nil, nil) } else if len(config.KeyLabel) != 0 { cert, _ = ctx.FindCertificate(nil, config.KeyLabel, nil) } } return &Key{ctx: ctx, signer: signer, cert: cert}, nil } func (k *Key) Certificate() (*x509.Certificate, error) { return k.cert, nil } func (k *Key) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) { return k.signer.Public(), nil } func (k *Key) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error { sig, err := io.ReadAll(signature) if err != nil { return fmt.Errorf("read signature: %w", err) } msg, err := io.ReadAll(message) if err != nil { return fmt.Errorf("read message: %w", err) } digest := sha256.Sum256(msg) switch kt := k.signer.Public().(type) { case *ecdsa.PublicKey: if ecdsa.VerifyASN1(kt, digest[:], sig) { return nil } return errors.New("invalid ecdsa signature") case *rsa.PublicKey: return rsa.VerifyPKCS1v15(kt, crypto.SHA256, digest[:], sig) } return fmt.Errorf("unsupported key type: %T", k.PublicKey) } func (k *Key) Verifier() (signature.Verifier, error) { if k.ctx == nil { return nil, ContextNotInitialized } if k.signer == nil { return nil, SignerNotSet } return k, nil } func (k *Key) Sign(ctx context.Context, rawPayload []byte) ([]byte, []byte, error) { h := sha256.Sum256(rawPayload) sig, err := k.signer.Sign(rand.Reader, h[:], crypto.SHA256) if err != nil { return nil, nil, err } return sig, h[:], err } func (k *Key) SignMessage(message io.Reader, opts ...signature.SignOption) ([]byte, error) { h := sha256.New() if _, err := io.Copy(h, message); err != nil { return nil, err } sig, err := k.signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256) if err != nil { return nil, err } return sig, err } func (k *Key) SignerVerifier() (signature.SignerVerifier, error) { if k.ctx == nil { return nil, ContextNotInitialized } if k.signer == nil { return nil, SignerNotSet } return k, nil } func (k *Key) Close() { k.ctx.Close() k.signer = nil k.cert = nil k.ctx = nil } cosign-2.5.0/pkg/cosign/pkcs11key/util.go000066400000000000000000000160531477503325500202020ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pkcs11key import ( "errors" "fmt" "net/url" "strconv" "strings" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) const ( ReferenceScheme = "pkcs11:" ) var pathAttrValueChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:[]@!$'()*+,=&" var queryAttrValueChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:[]@!$'()*+,=/?|" func percentEncode(input []byte) string { if len(input) == 0 { return "" } var stringBuilder strings.Builder for i := 0; i < len(input); i++ { stringBuilder.WriteByte('%') stringBuilder.WriteString(fmt.Sprintf("%.2x", input[i])) } return stringBuilder.String() } func EncodeURIComponent(uriString string, isForPath bool, usePercentEncoding bool) (string, error) { var stringBuilder strings.Builder var allowedChars string if isForPath { allowedChars = pathAttrValueChars } else { allowedChars = queryAttrValueChars } for i := 0; i < len(uriString); i++ { allowedChar := false for j := 0; j < len(allowedChars); j++ { if uriString[i] == allowedChars[j] { allowedChar = true break } } if allowedChar { stringBuilder.WriteByte(uriString[i]) } else { if usePercentEncoding { stringBuilder.WriteString(percentEncode([]byte{uriString[i]})) } else { return "", errors.New("string contains an invalid character") } } } return stringBuilder.String(), nil } type Pkcs11UriConfig struct { uriPathAttributes url.Values uriQueryAttributes url.Values ModulePath string SlotID *int TokenLabel string KeyLabel []byte KeyID []byte Pin string } func NewPkcs11UriConfig() *Pkcs11UriConfig { return &Pkcs11UriConfig{ uriPathAttributes: make(url.Values), uriQueryAttributes: make(url.Values), } } func NewPkcs11UriConfigFromInput(modulePath string, slotID *int, tokenLabel string, keyLabel []byte, keyID []byte, pin string) *Pkcs11UriConfig { return &Pkcs11UriConfig{ uriPathAttributes: make(url.Values), uriQueryAttributes: make(url.Values), ModulePath: modulePath, SlotID: slotID, TokenLabel: tokenLabel, KeyLabel: keyLabel, KeyID: keyID, Pin: pin, } } func (conf *Pkcs11UriConfig) Parse(uriString string) error { var slotID *int var pin string uri, err := url.Parse(uriString) if err != nil { return fmt.Errorf("parse uri: %w", err) } if uri.Scheme != "pkcs11" { return errors.New("invalid uri: not a PKCS11 uri") } // Semicolons are no longer valid separators, therefore, // we need to replace all occurrences of ";" with "&" // in uri.Opaque and uri.RawQuery before passing them to url.ParseQuery(). uri.Opaque = strings.ReplaceAll(uri.Opaque, ";", "&") uriPathAttributes, err := url.ParseQuery(uri.Opaque) if err != nil { return fmt.Errorf("parse uri path: %w", err) } uri.RawQuery = strings.ReplaceAll(uri.RawQuery, ";", "&") uriQueryAttributes, err := url.ParseQuery(uri.RawQuery) if err != nil { return fmt.Errorf("parse uri query: %w", err) } modulePath := uriQueryAttributes.Get("module-path") pinValue := uriQueryAttributes.Get("pin-value") tokenLabel := uriPathAttributes.Get("token") slotIDStr := uriPathAttributes.Get("slot-id") keyLabel := uriPathAttributes.Get("object") keyID := uriPathAttributes.Get("id") // At least one of token and slot-id must be specified. if tokenLabel == "" && slotIDStr == "" { return errors.New("invalid uri: one of token and slot-id must be set") } // slot-id, if specified, should be a number. if slotIDStr != "" { slot, err := strconv.Atoi(slotIDStr) if err != nil { return fmt.Errorf("invalid uri: slot-id '%s' is not a valid number", slotIDStr) } slotID = &slot } // If pin-value is specified, take it as it is. if pinValue != "" { pin = pinValue } // module-path should be specified and should point to the absolute path of the PKCS11 module. // If it is not, COSIGN_PKCS11_MODULE_PATH environment variable must be set. if modulePath == "" { modulePath = env.Getenv(env.VariablePKCS11ModulePath) if modulePath == "" { return errors.New("invalid uri: module-path or COSIGN_PKCS11_MODULE_PATH must be set to the absolute path of the PKCS11 module") } } // At least one of object and id must be specified. if keyLabel == "" && keyID == "" { return errors.New("invalid uri: one of object and id must be set") } conf.uriPathAttributes = uriPathAttributes conf.uriQueryAttributes = uriQueryAttributes conf.ModulePath = modulePath conf.TokenLabel = tokenLabel conf.SlotID = slotID conf.KeyLabel = []byte(keyLabel) conf.KeyID = []byte(keyID) // url.ParseQuery() already calls url.QueryUnescape() on the id, so we only need to cast the result into byte array conf.Pin = pin return nil } func (conf *Pkcs11UriConfig) Construct() (string, error) { var modulePath, pinValue, tokenLabel, slotID, keyID, keyLabel string var err error uriString := "pkcs11:" // module-path should be specified and should point to the absolute path of the PKCS11 module. if conf.ModulePath == "" { return "", errors.New("module path must be set to the absolute path of the PKCS11 module") } // At least one of keyLabel and keyID must be specified. if len(conf.KeyLabel) == 0 && len(conf.KeyID) == 0 { return "", errors.New("one of keyLabel and keyID must be set") } // At least one of tokenLabel and slotID must be specified. if conf.TokenLabel == "" && conf.SlotID == nil { return "", errors.New("one of tokenLabel and slotID must be set") } // Construct the URI. if conf.TokenLabel != "" { tokenLabel, err = EncodeURIComponent(conf.TokenLabel, true, true) if err != nil { return "", fmt.Errorf("encode token label: %w", err) } uriString += "token=" + tokenLabel } if conf.SlotID != nil { slotID = fmt.Sprintf("%d", *conf.SlotID) uriString += ";slot-id=" + slotID } if len(conf.KeyID) != 0 { keyID = percentEncode(conf.KeyID) uriString += ";id=" + keyID } if len(conf.KeyLabel) != 0 { keyLabel, err = EncodeURIComponent(string(conf.KeyLabel), true, true) if err != nil { return "", fmt.Errorf("encode key label: %w", err) } uriString += ";object=" + keyLabel } modulePath, err = EncodeURIComponent(conf.ModulePath, false, true) if err != nil { return "", fmt.Errorf("encode module path: %w", err) } uriString += "?module-path=" + modulePath if conf.Pin != "" { pinValue, err = EncodeURIComponent(conf.Pin, false, true) if err != nil { return "", fmt.Errorf("encode pin: %w", err) } uriString += "&pin-value=" + pinValue } return uriString, nil } cosign-2.5.0/pkg/cosign/rego/000077500000000000000000000000001477503325500160125ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/rego/fuzz_test.go000066400000000000000000000014331477503325500203770ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rego import ( "testing" ) func FuzzValidateJSON(f *testing.F) { f.Fuzz(func(_ *testing.T, jsonBody []byte, entrypoint string) { ValidateJSON(jsonBody, []string{entrypoint}) }) } cosign-2.5.0/pkg/cosign/rego/rego.go000066400000000000000000000114321477503325500172760ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rego import ( "bytes" "context" "encoding/json" "errors" "fmt" "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/rego" ) // The query below should meet the following requirements: // * Provides no Bindings. Do not use a query that sets a variable, e.g. x := data.signature.allow // * Queries for a single value. const QUERY = "data.signature.allow" // CosignRegoPackageName defines the expected package name of a provided rego module const CosignRegoPackageName = "sigstore" // CosignEvaluationRule defines the expected evaluation role of a provided rego module const CosignEvaluationRule = "isCompliant" // CosignRuleResult defines a expected result object when wrapping the custom messages of the result of our cosign rego rule type CosignRuleResult struct { Warning string `json:"warning,omitempty"` Error string `json:"error,omitempty"` Result bool `json:"result,omitempty"` } func ValidateJSON(jsonBody []byte, entrypoints []string) []error { ctx := context.Background() r := rego.New( rego.Query(QUERY), rego.Load(entrypoints, nil), rego.SetRegoVersion(ast.RegoV0), ) query, err := r.PrepareForEval(ctx) if err != nil { return []error{err} } var input interface{} dec := json.NewDecoder(bytes.NewBuffer(jsonBody)) dec.UseNumber() if err := dec.Decode(&input); err != nil { return []error{err} } rs, err := query.Eval(ctx, rego.EvalInput(input)) if err != nil { return []error{err} } // Ensure the resultset contains a single result where the Expression contains a single value // which is true and there are no Bindings. if rs.Allowed() { return nil } var errs []error for _, result := range rs { for _, expression := range result.Expressions { errs = append(errs, fmt.Errorf("expression value, %v, is not true", expression)) } } // When rs.Allowed() is not true and len(rs) is 0, the result is undefined. This is a policy // check failure. if len(errs) == 0 { errs = append(errs, fmt.Errorf("result is undefined for query '%s'", QUERY)) } return errs } // ValidateJSONWithModuleInput takes the body of the results to evaluate and the defined module // in a policy to validate against the input data func ValidateJSONWithModuleInput(jsonBody []byte, moduleInput string) (warnings error, errors error) { ctx := context.Background() query := fmt.Sprintf("%s = data.%s.%s", CosignEvaluationRule, CosignRegoPackageName, CosignEvaluationRule) module := fmt.Sprintf("%s.rego", CosignRegoPackageName) r := rego.New( rego.Query(query), rego.Module(module, moduleInput), rego.SetRegoVersion(ast.RegoV0), ) evalQuery, err := r.PrepareForEval(ctx) if err != nil { return nil, err } var input interface{} dec := json.NewDecoder(bytes.NewBuffer(jsonBody)) dec.UseNumber() if err := dec.Decode(&input); err != nil { return nil, err } rs, err := evalQuery.Eval(ctx, rego.EvalInput(input)) if err != nil { return nil, err } for _, result := range rs { switch response := result.Bindings[CosignEvaluationRule].(type) { case []interface{}: return evaluateRegoEvalMapResult(query, response) case bool: if response { return nil, nil } } } return nil, fmt.Errorf("policy is not compliant for query '%s'", query) } func evaluateRegoEvalMapResult(query string, response []interface{}) (warning error, retErr error) { retErr = fmt.Errorf("policy is not compliant for query %q", query) //nolint: revive for _, r := range response { rMap := r.(map[string]interface{}) mapBytes, err := json.Marshal(rMap) if err != nil { return nil, fmt.Errorf("policy is not compliant for query '%s' due to parsing errors: %w", query, err) } var resultObject CosignRuleResult err = json.Unmarshal(mapBytes, &resultObject) if err != nil { return nil, fmt.Errorf("policy is not compliant for query '%s' due to parsing errors: %w", query, err) } // Check if it is complaint if resultObject.Result { if resultObject.Warning == "" { return nil, nil } return fmt.Errorf("warning: %s", resultObject.Warning), nil } warning = errors.New(resultObject.Warning) retErr = fmt.Errorf("policy is not compliant for query '%s' with errors: %s", query, resultObject.Error) //nolint: revive } return warning, retErr } cosign-2.5.0/pkg/cosign/rego/rego_test.go000066400000000000000000000136211477503325500203370ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package rego import ( "fmt" "os" "testing" ) const simpleJSONBody = `{ "_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://slsa.dev/provenance/v0.2" }` func TestValidationJSON(t *testing.T) { cases := []struct { name string jsonBody string policy string pass bool errors []string }{ { name: "passing policy", jsonBody: simpleJSONBody, policy: ` package signature allow { input.predicateType == "https://slsa.dev/provenance/v0.2" } `, pass: true, }, { name: "undefined result due to no matching rules", jsonBody: simpleJSONBody, policy: ` package signature allow { input.predicateType == "https://slsa.dev/provenance/v99.9" } `, pass: false, errors: []string{"result is undefined for query 'data.signature.allow'"}, }, { name: "policy query evaluates to false", jsonBody: simpleJSONBody, policy: ` package signature default allow = false `, pass: false, errors: []string{"expression value, false, is not true"}, }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { // Do not use t.TempDir() because rego has issues loading policy files // from an absolute path on Windows. Use a relative path instead. See: // https://github.com/open-policy-agent/opa/issues/4521 policyFileName := "tmp-policy.rego" if err := os.WriteFile(policyFileName, []byte(tt.policy), 0644); err != nil { t.Fatal(err) } defer os.Remove(policyFileName) if errs := ValidateJSON([]byte(tt.jsonBody), []string{policyFileName}); (errs == nil) != tt.pass { t.Fatalf("Unexpected result: %v", errs) } else if errs != nil { if len(errs) != len(tt.errors) { t.Fatalf("Expected %d errors, got %d errors: %v", len(tt.errors), len(errs), errs) } for i, err := range errs { if fmt.Sprintf("%s", err) != tt.errors[i] { t.Errorf("Expected error %q, got %q", tt.errors[i], err) } } } }) } } const attestationsJSONBody = `{ "authorityMatches": { "keyatt": { "signatures": null, "attestations": { "vuln-key": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ] } }, "keysignature": { "signatures": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ], "attestations": null }, "keylessatt": { "signatures": null, "attestations": { "custom-keyless": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ] } }, "keylesssignature": { "signatures": [ { "subject": "PLACEHOLDER", "issuer": "PLACEHOLDER" } ], "attestations": null } } }` func TestValidateJSONWithModuleInput(t *testing.T) { cases := []struct { name string jsonBody string policy string pass bool errorMsg string warnMsg string }{ { name: "passing policy attestations", jsonBody: attestationsJSONBody, policy: ` package sigstore default isCompliant = false isCompliant { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations count(attestationsKeylessATT) == 1 attestationsKeyATT := input.authorityMatches.keyatt.attestations count(attestationsKeyATT) == 1 keylessSignature := input.authorityMatches.keylesssignature.signatures count(keylessSignature) == 1 keySignature := input.authorityMatches.keysignature.signatures count(keySignature) == 1 } `, pass: true, }, { name: "not passing policy attestations", jsonBody: attestationsJSONBody, policy: ` package sigstore default isCompliant = false isCompliant { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations count(attestationsKeylessATT) == 0 attestationsKeyATT := input.authorityMatches.keyatt.attestations count(attestationsKeyATT) == 1 keylessSignature := input.authorityMatches.keylesssignature.signatures count(keylessSignature) == 1 keySignature := input.authorityMatches.keysignature.signatures count(keySignature) == 1 } `, pass: false, errorMsg: "policy is not compliant for query 'isCompliant = data.sigstore.isCompliant'", }, { name: "not passing policy attestations", jsonBody: attestationsJSONBody, policy: ` package sigstore isCompliant[response] { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations result = (count(attestationsKeylessATT) == 1000) errorMsg := "attestationsKeylessATT is not equal to 1000" warnMsg := "" response :={ "result" : result, "error" : errorMsg, "warning": warnMsg, } } `, pass: false, errorMsg: "policy is not compliant for query 'isCompliant = data.sigstore.isCompliant' with errors: attestationsKeylessATT is not equal to 1000", }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { warn, err := ValidateJSONWithModuleInput([]byte(tt.jsonBody), tt.policy) if (err == nil) != tt.pass { t.Fatalf("Unexpected result: %v", err) } else if err != nil { if fmt.Sprintf("%s", err) != tt.errorMsg { t.Errorf("Expected error %q, got %q", tt.errorMsg, err) } } if warn != nil && tt.warnMsg != "" && tt.warnMsg != warn.Error() { t.Errorf("Expected warning %q, got %q", tt.warnMsg, warn) } }) } } cosign-2.5.0/pkg/cosign/rekor_factory.go000066400000000000000000000023551477503325500202630ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "github.com/sigstore/rekor/pkg/generated/client" ) // key is used for associating the Rekor client client inside the // context.Context. type key struct{} // TODO(jason): Rename this to something better than pkg/cosign.Set. func Set(ctx context.Context, rekorClient *client.Rekor) context.Context { return context.WithValue(ctx, key{}, rekorClient) } // Get extracts the Rekor client from the context. // TODO(jason): Rename this to something better than pkg/cosign.Get. func Get(ctx context.Context) *client.Rekor { untyped := ctx.Value(key{}) if untyped == nil { return nil } return untyped.(*client.Rekor) } cosign-2.5.0/pkg/cosign/remote/000077500000000000000000000000001477503325500163515ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/remote/index.go000066400000000000000000000075161477503325500200200ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "fmt" "net/http" "os" "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/oci/static" ) type File interface { Contents() ([]byte, error) Platform() *v1.Platform String() string Path() string } func FilesFromFlagList(sl []string) []File { files := make([]File, len(sl)) for i, s := range sl { files[i] = FileFromFlag(s) } return files } func FileFromFlag(s string) File { split := strings.Split(s, ":") f := file{ path: split[0], } if len(split) > 1 { split = strings.Split(split[1], "/") f.platform = &v1.Platform{ OS: split[0], } if len(split) > 1 { f.platform.Architecture = split[1] } } return &f } type file struct { path string platform *v1.Platform } func (f *file) Path() string { return f.path } func (f *file) Contents() ([]byte, error) { return os.ReadFile(f.path) } func (f *file) Platform() *v1.Platform { return f.platform } func (f *file) String() string { r := f.path if f.platform == nil { return r } r += ":" + f.platform.OS if f.platform.Architecture == "" { return r } r += "/" + f.platform.Architecture return r } type MediaTypeGetter func(b []byte) types.MediaType func DefaultMediaTypeGetter(b []byte) types.MediaType { return types.MediaType(strings.Split(http.DetectContentType(b), ";")[0]) } func UploadFiles(ref name.Reference, files []File, annotations map[string]string, getMt MediaTypeGetter, remoteOpts ...remote.Option) (name.Digest, error) { var lastHash v1.Hash var idx v1.ImageIndex = empty.Index for _, f := range files { b, err := f.Contents() if err != nil { return name.Digest{}, err } mt := getMt(b) fmt.Fprintf(os.Stderr, "Uploading file from [%s] to [%s] with media type [%s]\n", f.Path(), ref.Name(), mt) img, err := static.NewFile(b, static.WithLayerMediaType(mt), static.WithAnnotations(annotations)) if err != nil { return name.Digest{}, err } lastHash, err = img.Digest() if err != nil { return name.Digest{}, err } if err := remote.Write(ref, img, remoteOpts...); err != nil { return name.Digest{}, err } l, err := img.Layers() if err != nil { return name.Digest{}, err } layerHash, err := l[0].Digest() if err != nil { return name.Digest{}, err } blobURL := ref.Context().RegistryStr() + "/v2/" + ref.Context().RepositoryStr() + "/blobs/" + layerHash.String() fmt.Fprintf(os.Stderr, "File [%s] is available directly at [%s]\n", f.Path(), blobURL) if f.Platform() != nil { idx = mutate.AppendManifests(idx, mutate.IndexAddendum{ Add: img, Descriptor: v1.Descriptor{ Platform: f.Platform(), }, }) } } if len(files) > 1 { if annotations != nil { idx = mutate.Annotations(idx, annotations).(v1.ImageIndex) } err := remote.WriteIndex(ref, idx, remoteOpts...) if err != nil { return name.Digest{}, err } lastHash, err = idx.Digest() if err != nil { return name.Digest{}, err } } return ref.Context().Digest(lastHash.String()), nil } cosign-2.5.0/pkg/cosign/remote/index_test.go000066400000000000000000000156251477503325500210570ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "fmt" "io" "log" "net/http/httptest" "net/url" "reflect" "testing" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/registry" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" ) func TestFilesFromFlagList(t *testing.T) { tests := []struct { name string sl []string want []File }{{ name: "empty", sl: []string{}, want: []File{}, }, { name: "nil", sl: nil, want: []File{}, }, { name: "plain", sl: []string{"foo"}, want: []File{&file{path: "foo"}}, }, { name: "three", sl: []string{"foo", "foo:darwin", "foo:darwin/amd64"}, want: []File{ &file{path: "foo"}, &file{path: "foo", platform: &v1.Platform{ OS: "darwin", }}, &file{path: "foo", platform: &v1.Platform{ OS: "darwin", Architecture: "amd64", }}, }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := FilesFromFlagList(tt.sl); !reflect.DeepEqual(got, tt.want) { t.Errorf("fileFromFlag() = %v, want %v", got, tt.want) } }) } } func TestFileFromFlag(t *testing.T) { tests := []struct { name string s string want File }{{ name: "plain", s: "foo", want: &file{path: "foo"}, }, { name: "os", s: "foo:darwin", want: &file{path: "foo", platform: &v1.Platform{ OS: "darwin", }}, }, { name: "os amd64", s: "foo:darwin/amd64", want: &file{path: "foo", platform: &v1.Platform{ OS: "darwin", Architecture: "amd64", }}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := FileFromFlag(tt.s); !reflect.DeepEqual(got, tt.want) { t.Errorf("fileFromFlag() = %v, want %v", got, tt.want) } }) } } func TestUploadFiles(t *testing.T) { var ( expectedRepo = "foo" mt = DefaultMediaTypeGetter err error ) // Set up a fake registry (with NOP logger to avoid spamming test logs). nopLog := log.New(io.Discard, "", 0) s := httptest.NewServer(registry.New(registry.Logger(nopLog))) defer s.Close() u, err := url.Parse(s.URL) if err != nil { t.Fatal(err) } tests := []struct { name string image string fs []File annotations map[string]string wantErr bool }{{ name: "one file", image: "one", fs: []File{ &file{path: "testdata/foo"}, }, wantErr: false, }, { name: "missing file", image: "one-missing", fs: []File{ &file{path: "testdata/missing"}, }, wantErr: true, }, { name: "two files with platform", image: "two-platform", fs: []File{ &file{path: "testdata/foo", platform: &v1.Platform{ OS: "darwin", Architecture: "amd64", }}, &file{path: "testdata/bar", platform: &v1.Platform{ OS: "linux", Architecture: "amd64", }}, }, wantErr: false, }, { name: "one file with annotations", image: "one-annotations", fs: []File{ &file{path: "testdata/foo"}, }, annotations: map[string]string{ "foo": "bar", }, wantErr: false, }, { name: "two files with annotations", image: "two-annotations", fs: []File{ &file{path: "testdata/foo", platform: &v1.Platform{ OS: "darwin", Architecture: "amd64", }}, &file{path: "testdata/bar", platform: &v1.Platform{ OS: "linux", Architecture: "amd64", }}, }, annotations: map[string]string{ "foo": "bar", "bar": "baz", }, wantErr: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ref, err := name.ParseReference(fmt.Sprintf("%s/%s/%s:latest", u.Host, expectedRepo, tt.image)) if err != nil { t.Fatalf("ParseRef() = %v", err) } _, err = UploadFiles(ref, tt.fs, tt.annotations, mt) if (err != nil) != tt.wantErr { t.Errorf("UploadFiles() error = %v, wantErr %v", err, tt.wantErr) } if len(tt.fs) > 1 { if !checkIndex(t, ref) { t.Errorf("UploadFiles() index = %v", checkIndex(t, ref)) } } for _, f := range tt.fs { if f.Platform() != nil { if !checkPlatform(t, ref, f.Platform()) { t.Errorf("UploadFiles() platform = %v was not found", checkPlatform(t, ref, f.Platform())) } } } if tt.annotations != nil { if !checkAnnotations(t, ref, tt.annotations) { t.Errorf("UploadFiles() annotations = %v was not found", checkAnnotations(t, ref, tt.annotations)) } } }) } } func checkPlatform(t *testing.T, ref name.Reference, p *v1.Platform) bool { t.Helper() d, err := remote.Get(ref) if err != nil { t.Fatalf("Get() = %v", err) } if d.MediaType.IsIndex() { // if it is an index recurse into the index idx, err := d.ImageIndex() if err != nil { t.Fatalf("ImageIndex() = %v", err) } manifest, err := idx.IndexManifest() if err != nil { t.Fatalf("IndexManifest() = %v", err) } for _, m := range manifest.Manifests { if !m.MediaType.IsImage() { return false } if m.Platform != nil { if m.Platform.OS == p.OS && m.Platform.Architecture == p.Architecture { return true } } } return false } else if d.MediaType.IsImage() { // if it is an image check the platform if d.Platform != nil { if d.Platform.OS == p.OS && d.Platform.Architecture == p.Architecture { return true } } } return false } func checkIndex(t *testing.T, ref name.Reference) bool { t.Helper() d, err := remote.Get(ref) if err != nil { t.Fatalf("Get() = %v", err) } if d.MediaType.IsIndex() { return true } return false } func checkAnnotations(t *testing.T, ref name.Reference, annotations map[string]string) bool { t.Helper() var found bool d, err := remote.Get(ref) if err != nil { t.Fatalf("Get() = %v", err) } if d.MediaType.IsIndex() { idx, err := remote.Index(ref) if err != nil { t.Fatalf("Index() = %v", err) } m, err := idx.IndexManifest() if err != nil { t.Fatalf("IndexManifest() = %v", err) } for annotationsKey := range annotations { _, ok := m.Annotations[annotationsKey] if ok { found = true } } return found } if d.MediaType.IsImage() { i, err := remote.Image(ref) if err != nil { t.Fatalf("Image() = %v", err) } m, _ := i.Manifest() for annotationsKey := range annotations { _, ok := m.Annotations[annotationsKey] if ok { found = true } } return found } return false } cosign-2.5.0/pkg/cosign/remote/remote.go000066400000000000000000000111301477503325500201670ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "bytes" "encoding/base64" "encoding/json" "fmt" "os" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/mutate" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/sigstore/pkg/signature" ) // NewDupeDetector creates a new DupeDetector that looks for matching signatures that // can verify the provided signature's payload. func NewDupeDetector(v signature.Verifier) mutate.DupeDetector { return &dd{verifier: v} } func NewReplaceOp(predicateURI string) mutate.ReplaceOp { return &ro{predicateURI: predicateURI} } type dd struct { verifier signature.Verifier } type ro struct { predicateURI string } var _ mutate.DupeDetector = (*dd)(nil) var _ mutate.ReplaceOp = (*ro)(nil) func (dd *dd) Find(sigImage oci.Signatures, newSig oci.Signature) (oci.Signature, error) { newDigest, err := newSig.Digest() if err != nil { return nil, err } newMediaType, err := newSig.MediaType() if err != nil { return nil, err } newAnnotations, err := newSig.Annotations() if err != nil { return nil, err } sigs, err := sigImage.Get() if err != nil { return nil, err } LayerLoop: for _, sig := range sigs { existingAnnotations, err := sig.Annotations() if err != nil { continue LayerLoop } // if there are any new annotations, then this isn't a duplicate for a, value := range newAnnotations { if a == static.SignatureAnnotationKey { continue // Ignore the signature key, we check it with custom logic below. } if val, ok := existingAnnotations[a]; !ok || val != value { continue LayerLoop } } if existingDigest, err := sig.Digest(); err != nil || existingDigest != newDigest { continue LayerLoop } if existingMediaType, err := sig.MediaType(); err != nil || existingMediaType != newMediaType { continue LayerLoop } existingSignature, err := sig.Base64Signature() if err != nil || existingSignature == "" { continue LayerLoop } uploadedSig, err := base64.StdEncoding.DecodeString(existingSignature) if err != nil { continue LayerLoop } r, err := newSig.Uncompressed() if err != nil { return nil, err } if err := dd.verifier.VerifySignature(bytes.NewReader(uploadedSig), r); err == nil { return sig, nil } } return nil, nil } func (r *ro) Replace(signatures oci.Signatures, o oci.Signature) (oci.Signatures, error) { sigs, err := signatures.Get() if err != nil { return nil, err } ros := &replaceOCISignatures{Signatures: signatures} sigsCopy := make([]oci.Signature, 0, len(sigs)) sigsCopy = append(sigsCopy, o) if len(sigs) == 0 { ros.attestations = append(ros.attestations, sigsCopy...) return ros, nil } for _, s := range sigs { var signaturePayload map[string]interface{} p, err := s.Payload() if err != nil { return nil, fmt.Errorf("could not get payload: %w", err) } err = json.Unmarshal(p, &signaturePayload) if err != nil { return nil, fmt.Errorf("unmarshal payload data: %w", err) } val, ok := signaturePayload["payload"] if !ok { return nil, fmt.Errorf("could not find 'payload' in payload data") } decodedPayload, err := base64.StdEncoding.DecodeString(val.(string)) if err != nil { return nil, fmt.Errorf("could not decode 'payload': %w", err) } var payloadData map[string]interface{} if err := json.Unmarshal(decodedPayload, &payloadData); err != nil { return nil, fmt.Errorf("unmarshal payloadData: %w", err) } val, ok = payloadData["predicateType"] if !ok { return nil, fmt.Errorf("could not find 'predicateType' in payload data") } if r.predicateURI == val { fmt.Fprintln(os.Stderr, "Replacing attestation predicate:", r.predicateURI) continue } fmt.Fprintln(os.Stderr, "Not replacing attestation predicate:", val) sigsCopy = append(sigsCopy, s) } ros.attestations = append(ros.attestations, sigsCopy...) return ros, nil } type replaceOCISignatures struct { oci.Signatures attestations []oci.Signature } func (r *replaceOCISignatures) Get() ([]oci.Signature, error) { return r.attestations, nil } cosign-2.5.0/pkg/cosign/remote/testdata/000077500000000000000000000000001477503325500201625ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/remote/testdata/bar000066400000000000000000000000041477503325500206430ustar00rootroot00000000000000bar cosign-2.5.0/pkg/cosign/remote/testdata/foo000066400000000000000000000000041477503325500206620ustar00rootroot00000000000000foo cosign-2.5.0/pkg/cosign/testdata/000077500000000000000000000000001477503325500166675ustar00rootroot00000000000000cosign-2.5.0/pkg/cosign/testdata/garbage-there-are-limits000066400000000000000000000000451477503325500233520ustar00rootroot00000000000000yeah so we don't just parse whatever cosign-2.5.0/pkg/cosign/testdata/google000066400000000000000000000014401477503325500200650ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsu0BHGnQ++W2CTdyZyxv HHRALOZPlnu/VMVgo2m+JZ8MNbAOH2cgXb8mvOj8flsX/qPMuKIaauO+PwROMjiq fUpcFm80Kl7i97ZQyBDYKm3MkEYYpGN+skAR2OebX9G2DfDqFY8+jUpOOWtBNr3L rmVcwx+FcFdMjGDlrZ5JRmoJ/SeGKiORkbbu9eY1Wd0uVhz/xI5bQb0OgII7hEj+ i/IPbJqOHgB8xQ5zWAJJ0DmG+FM6o7gk403v6W3S8qRYiR84c50KppGwe4YqSMkF bLDleGQWLoaDSpEWtESisb4JiLaY4H+Kk0EyAhPSb+49JfUozYl+lf7iFN3qRq/S IXXTh6z0S7Qa8EYDhKGCrpI03/+qprwy+my6fpWHi6aUIk4holUCmWvFxZDfixox K0RlqbFDl2JXMBquwlQpm8u5wrsic1ksIv9z8x9zh4PJqNpCah0ciemI3YGRQqSe /mRRXBiSn9YQBUPcaeqCYan+snGADFwHuXCd9xIAdFBolw9R9HTedHGUfVXPJDiF 4VusfX6BRR/qaadB+bqEArF/TzuDUr6FvOR4o8lUUxgLuZ/7HO+bHnaPFKYHHSm+ +z1lVDhhYuSZ8ax3T0C3FZpb7HMjZtpEorSV5ElKJEJwrhrBCMOD8L01EoSPrGlS 1w22i9uGHMn/uGQKo28u7AsCAwEAAQ== -----END PUBLIC KEY----- cosign-2.5.0/pkg/cosign/testdata/letsencrypt-testflume-2021000066400000000000000000000001331477503325500235530ustar00rootroot000000000000000Y0*H=*H=Bt"$uAZC;2~`C:7Y?ڭ%tӀx+7UAB@?Me cosign-2.5.0/pkg/cosign/testdata/oci-attestation.sigstore.json000066400000000000000000000127161477503325500245360ustar00rootroot00000000000000{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIICzzCCAlWgAwIBAgIUF2e+Ci0YsLnPaFUknbNDtUcqXl0wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwMjI4MTkxODExWhcNMjUwMjI4MTkyODExWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk/CiYRmA9zsa7ams/+fI+wGeXOFxng4J0wSj1jub7kxArHhGwvgcVE3ubrGXNQB9X3ksG0F+MiXHG8gRzxrr0KOCAXQwggFwMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUcPdotYluBHI4fkYy3yij5SY4728wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHgYDVR0RAQH/BBQwEoEQY29keUBzb3lsYW5kLmNvbTAsBgorBgEEAYO/MAEBBB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwLgYKKwYBBAGDvzABCAQgDB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwgYoGCisGAQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAZVN/q3JAAAEAwBHMEUCIQCdLrw1b9rE6rFd+NAJVPaw4DmH0YNOH2AYIOc4g26WmgIgG7Gub5hz7gzFpvhdb9HQsObWfTNcUiSOmrnPlmaQaKUwCgYIKoZIzj0EAwMDaAAwZQIxAMwB66cCKVhOYq2JMx20FPfJXD6K6oaUV8aIlCiW/YLLZdkWjTWkXt4qIbPgUeAl6wIwdCmnnSMw9fG7FuKz14RYFSbE1VLhNiWhGYpfCj00FtNRoXF+NFQ7DOKjjq7xPjza"},"tlogEntries":[{"logIndex":"175508996","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"dsse","version":"0.0.1"},"integratedTime":"1740770291","inclusionPromise":{"signedEntryTimestamp":"MEUCIQDIgcWm5sXPh52rA/3BLyvfymD4RLEze5cx0WrDCF7tXAIgDtDq9wgYo8Z6RQ/LIaMRY/clocjCbX1qZTI9EVrGbzQ="},"inclusionProof":{"logIndex":"53604734","rootHash":"nd/V1IEweXlu0reIQGaGK1d51xW4Rj8fUyTrmcUkrMY=","treeSize":"53604735","hashes":["p7adBuLwiHyxFA7sjjx9Jt+YJ9NJ6pPRsqrlFv/Prl4=","V8kTVhZDHU0oMb8SoiG+IJzfi1+zs8k07t7msfdk3DU=","Y9K/VuEpG5hexjkO0eGf5aWgfD1no2I/HmjLL79pGOw=","49xFd2wqEUENt7vh3yabkMXtEu6nNJxA6xJDF35HkvQ=","fldHlNczrXBcOY4UnkC1im8c3AZGOojtqHIrnWG81FA=","9JKlhk57UmiGCygvf555Z0loJkdvt8BLIwGeMAuJM1o=","JuKjSUhL4pgP2+mTbT/X01oQa16n9Nh2ruwSgN9Ft3o=","6g/GERajYnzZjZ0EXQIVHpGIKry1LMG0+RJhvtcbSwI=","62MSvX4iwxK6VPg9s7NR+Y1jiYkd/SsZpVoaUYjD8wg=","K7s/kEEUl/59VblorlWa2aJO8TuH5uy8kn/7qSY+T94=","GlND1TZKB9H1XkH8gmyEmNId6gWGYnXjA0ykMIJaI44=","315PrxqKtUW8bpqBdn6SMyj+smOwK7jCa7Px7i4AIOQ=","lL6jxdDTg23iUvuwRop1833jkmWSvr7sLBM3hXZ8tTU=","eExzddanJoxYTKoErFFSTDFUR3UvwaaXxcddWwQJd7I=","ebCKJ53lKWPqIx8mXXgznF9DGoQv70J7JTlFAav6s5E=","vemyaMj0Na1LMjbB/9Dmkq8T+jAb3o+yCESgAayUABU="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n53604735\nnd/V1IEweXlu0reIQGaGK1d51xW4Rj8fUyTrmcUkrMY=\n\n— rekor.sigstore.dev wNI9ajBFAiEAuh6liXP/rBKWjuO/W8jooM66eVHSXo3Cf8kEAIryZLACIAfCuhLOHpSnU3WA68gZci57o2JuwX7S5H6ANbGePH58\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiYzZhZGFhZjE0OTA4NGYxOTc3YzE0YjViMTQxMmY4ODI1OGU1ODMwZDg3YTVjNjQ0MDNjNDAzYjliNzNmZjEzNiJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6IjE1NjkyNjE2ZTljNDM1OTA3YzQxNDExNDY2ZTQ5M2YxOTVhMzljYTY4Yzk3M2E0ZjUzOTBkZjNkMzAxMzk4YmQifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lEb21pdVlWZ05LeWZZei9sZXF2dEVZMnh1aWZ0ZWlnanErTGZTaHU5MkJCQWlCeGl4c0daTllCVlJoR2pYNk9aaVBRQklvVWYraThWZ1BoLy9BVC96NFZnZz09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VONmVrTkRRV3hYWjBGM1NVSkJaMGxWUmpKbEswTnBNRmx6VEc1UVlVWlZhMjVpVGtSMFZXTnhXR3d3ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwVmQwMXFTVFJOVkd0NFQwUkZlRmRvWTA1TmFsVjNUV3BKTkUxVWEzbFBSRVY0VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVnJMME5wV1ZKdFFUbDZjMkUzWVcxekx5dG1TU3QzUjJWWVQwWjRibWMwU2pCM1Uyb0tNV3AxWWpkcmVFRnlTR2hIZDNablkxWkZNM1ZpY2tkWVRsRkNPVmd6YTNOSE1FWXJUV2xZU0VjNFoxSjZlSEp5TUV0UFEwRllVWGRuWjBaM1RVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVmpVR1J2Q25SWmJIVkNTRWswWm10WmVUTjVhV28xVTFrME56STRkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMGhuV1VSV1VqQlNRVkZJTDBKQ1VYZEZiMFZSV1RJNWEyVlZRbnBpTTJ4eldWYzFhMHh0VG5aaVZFRnpRbWR2Y2tKblJVVkJXVTh2VFVGRlFncENRalZ2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJZa2M1Ym1GWE5IWmlNa1l4WkVkbmQweG5XVXRMZDFsQ1FrRkhSSFo2UVVKRFFWRm5Da1JDTlc5a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFppUnpsdVlWYzBkbUl5UmpGa1IyZDNaMWx2UjBOcGMwZEJVVkZDTVc1clEwSkJTVVVLWmtGU05rRklaMEZrWjBSa1VGUkNjWGh6WTFKTmJVMWFTR2g1V2xwNlkwTnZhM0JsZFU0ME9ISm1LMGhwYmt0QlRIbHVkV3BuUVVGQldsWk9MM0V6U2dwQlFVRkZRWGRDU0UxRlZVTkpVVU5rVEhKM01XSTVja1UyY2taa0swNUJTbFpRWVhjMFJHMUlNRmxPVDBneVFWbEpUMk0wWnpJMlYyMW5TV2RITjBkMUNtSTFhSG8zWjNwR2NIWm9aR0k1U0ZGelQySlhabFJPWTFWcFUwOXRjbTVRYkcxaFVXRkxWWGREWjFsSlMyOWFTWHBxTUVWQmQwMUVZVUZCZDFwUlNYZ0tRVTEzUWpZMlkwTkxWbWhQV1hFeVNrMTRNakJHVUdaS1dFUTJTelp2WVZWV09HRkpiRU5wVnk5WlRFeGFaR3RYYWxSWGExaDBOSEZKWWxCblZXVkJiQW8yZDBsM1pFTnRibTVUVFhjNVprYzNSblZMZWpFMFVsbEdVMkpGTVZaTWFFNXBWMmhIV1hCbVEyb3dNRVowVGxKdldFWXJUa1pSTjBSUFMycHFjVGQ0Q2xCcWVtRUtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifV19fQ=="}]},"dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2Nvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiMTI3LjAuMC4xOjYwOTQzL3JlcG8iLCJkaWdlc3QiOnsic2hhMjU2IjoiNzMyMTEyMjcwZDdlNTk0MThhOGMwODBiMTM0YjI0Y2FiZDY3ZDI1MGQwZDAxNDdhOTdlZDk1YmE1YzI4MGFhNCJ9fV0sInByZWRpY2F0ZSI6eyJEYXRhIjoie1wiZm9vXCI6IFwiYmFyXCJ9XG4iLCJUaW1lc3RhbXAiOiIyMDI1LTAyLTI4VDE5OjE4OjExWiJ9fQ==","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MEQCIDomiuYVgNKyfYz/leqvtEY2xuifteigjq+LfShu92BBAiBxixsGZNYBVRhGjX6OZiPQBIoUf+i8VgPh//AT/z4Vgg=="}]}} cosign-2.5.0/pkg/cosign/testdata/rsa000066400000000000000000000014071477503325500174010ustar00rootroot00000000000000-----BEGIN RSA PUBLIC KEY----- MIICCgKCAgEAsxbYrZKRAi25BJrhgUTaLcy0eN22zAKC2iLP99BobGRKLxIsvNJj mV+TjFDe7KH+VwEeByJI7IpKu/j73JkDAWG2T16sjveLzVZHi+xgInG9HTensmo9 nYKpOGAfuAeCyWbuoNpMoxNMVBn4WyM/gPLg5RKD4BSMS/QwJzbg6pS+2O5ckBZW TSMouK5lO71PsJE9mFt8TJfOTFMXZsfwFPkJqhVDL+2Xm5+4TZdhXiA0196xYd1A VJLG3gQ1nWv0wd9l3pGxaLQfTClm6iQX6VhWwqH+27Udqh212FYVIe5WRjOUMNj4 2EohgjkXSjlIii2jeNbRv8yJwASIiY+cshEEF8U0DcUgCTer2e8jKe9OrsjbrYDD v8EgqkBUNpMZrveZtOH1+SHI1BeWw+SMjR0VtoCGcb7ZGJ4URKJSpe0stBStfJFa 7+zfdjz1dKqJHLkgpaWsdSh1/pnniASOQ5WzpB3vJA673yY3ZuZW6Cr3XL3S4uzn c0wSlc/NiseNOCmCACPWG3oju3hyc1L4PigeqocGqXintJR0YXRBLcO46vAyBkiR EXK0ZZ+oYZk2Qlx5nVz9tGA9+9LcDhx+yzbDNJQwf4KHNBfg7J9j1Igi0CcCVWOR iO/3lv6wd+tqwbAlxFgAXEn0+PH3Q1BtT3Ydu+9zoEAJtfy4AKcHwVkCAwEAAQ== -----END RSA PUBLIC KEY----- cosign-2.5.0/pkg/cosign/testdata/trusted_root_pgi.json000066400000000000000000000106711477503325500231630ustar00rootroot00000000000000{ "mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1", "tlogs": [ { "baseUrl": "https://rekor.sigstore.dev", "hashAlgorithm": "SHA2_256", "publicKey": { "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==", "keyDetails": "PKIX_ECDSA_P256_SHA_256", "validFor": { "start": "2021-01-12T11:53:27.000Z" } }, "logId": { "keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0=" } } ], "certificateAuthorities": [ { "subject": { "organization": "sigstore.dev", "commonName": "sigstore" }, "uri": "https://fulcio.sigstore.dev", "certChain": { "certificates": [ { "rawBytes": "MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==" } ] }, "validFor": { "start": "2021-03-07T03:20:29.000Z", "end": "2022-12-31T23:59:59.999Z" } }, { "subject": { "organization": "sigstore.dev", "commonName": "sigstore" }, "uri": "https://fulcio.sigstore.dev", "certChain": { "certificates": [ { "rawBytes": "MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow=" }, { "rawBytes": "MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ" } ] }, "validFor": { "start": "2022-04-13T20:06:15.000Z" } } ], "ctlogs": [ { "baseUrl": "https://ctfe.sigstore.dev/test", "hashAlgorithm": "SHA2_256", "publicKey": { "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==", "keyDetails": "PKIX_ECDSA_P256_SHA_256", "validFor": { "start": "2021-03-14T00:00:00.000Z", "end": "2022-10-31T23:59:59.999Z" } }, "logId": { "keyId": "CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I=" } }, { "baseUrl": "https://ctfe.sigstore.dev/2022", "hashAlgorithm": "SHA2_256", "publicKey": { "rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==", "keyDetails": "PKIX_ECDSA_P256_SHA_256", "validFor": { "start": "2022-10-20T00:00:00.000Z" } }, "logId": { "keyId": "3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4=" } } ] } cosign-2.5.0/pkg/cosign/tlog.go000066400000000000000000000420221477503325500163520ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" "errors" "fmt" "hash" "os" "strconv" "strings" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/rekor/pkg/types" "github.com/sigstore/rekor/pkg/types/dsse" dsse_v001 "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "github.com/sigstore/rekor/pkg/types/intoto" intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/tuf" "github.com/transparency-dev/merkle/proof" "github.com/transparency-dev/merkle/rfc6962" ) // This is the rekor transparency log public key target name var rekorTargetStr = `rekor.pub` // TransparencyLogPubKey contains the ECDSA verification key and the current status // of the key according to TUF metadata, whether it's active or expired. type TransparencyLogPubKey struct { PubKey crypto.PublicKey Status tuf.StatusKind } // This is a map of TransparencyLog public keys indexed by log ID that's used // in verification. type TrustedTransparencyLogPubKeys struct { // A map of keys indexed by log ID Keys map[string]TransparencyLogPubKey } const treeIDHexStringLen = 16 const uuidHexStringLen = 64 const entryIDHexStringLen = treeIDHexStringLen + uuidHexStringLen // GetTransparencyLogID generates a SHA256 hash of a DER-encoded public key. // (see RFC 6962 S3.2) // In CT V1 the log id is a hash of the public key. func GetTransparencyLogID(pub crypto.PublicKey) (string, error) { pubBytes, err := x509.MarshalPKIXPublicKey(pub) if err != nil { return "", err } digest := sha256.Sum256(pubBytes) return hex.EncodeToString(digest[:]), nil } func dsseEntry(ctx context.Context, signature, pubKey []byte) (models.ProposedEntry, error) { var pubKeyBytes [][]byte if len(pubKey) == 0 { return nil, errors.New("public key provided has 0 length") } pubKeyBytes = append(pubKeyBytes, pubKey) return types.NewProposedEntry(ctx, dsse.KIND, dsse_v001.APIVERSION, types.ArtifactProperties{ ArtifactBytes: signature, PublicKeyBytes: pubKeyBytes, }) } func intotoEntry(ctx context.Context, signature, pubKey []byte) (models.ProposedEntry, error) { var pubKeyBytes [][]byte if len(pubKey) == 0 { return nil, errors.New("none of the Rekor public keys have been found") } pubKeyBytes = append(pubKeyBytes, pubKey) return types.NewProposedEntry(ctx, intoto.KIND, intoto_v001.APIVERSION, types.ArtifactProperties{ ArtifactBytes: signature, PublicKeyBytes: pubKeyBytes, }) } // GetRekorPubs retrieves trusted Rekor public keys from the embedded or cached // TUF root. If expired, makes a network call to retrieve the updated targets. // There are two Env variable that can be used to override this behaviour: // SIGSTORE_REKOR_PUBLIC_KEY - If specified, location of the file that contains // the Rekor Public Key on local filesystem func GetRekorPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) { publicKeys := NewTrustedTransparencyLogPubKeys() altRekorPub := env.Getenv(env.VariableSigstoreRekorPublicKey) if altRekorPub != "" { raw, err := os.ReadFile(altRekorPub) if err != nil { return nil, fmt.Errorf("error reading alternate Rekor public key file: %w", err) } if err := publicKeys.AddTransparencyLogPubKey(raw, tuf.Active); err != nil { return nil, fmt.Errorf("AddRekorPubKey: %w", err) } } else { tufClient, err := tuf.NewFromEnv(ctx) if err != nil { return nil, err } targets, err := tufClient.GetTargetsByMeta(tuf.Rekor, []string{rekorTargetStr}) if err != nil { return nil, err } for _, t := range targets { if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil { return nil, fmt.Errorf("AddRekorPubKey: %w", err) } } } if len(publicKeys.Keys) == 0 { return nil, errors.New("none of the Rekor public keys have been found") } return &publicKeys, nil } // rekorPubsFromClient returns a RekorPubKey keyed by the log ID from the Rekor client. // NOTE: This **must not** be used in the verification path, but may be used in the // sign path to validate return responses are consistent from Rekor. func rekorPubsFromClient(rekorClient *client.Rekor) (*TrustedTransparencyLogPubKeys, error) { publicKeys := NewTrustedTransparencyLogPubKeys() pubOK, err := rekorClient.Pubkey.GetPublicKey(nil) if err != nil { return nil, fmt.Errorf("unable to fetch rekor public key from rekor: %w", err) } if err := publicKeys.AddTransparencyLogPubKey([]byte(pubOK.Payload), tuf.Active); err != nil { return nil, fmt.Errorf("constructRekorPubKey: %w", err) } return &publicKeys, nil } // TLogUpload will upload the signature, public key and payload to the transparency log. func TLogUpload(ctx context.Context, rekorClient *client.Rekor, signature []byte, sha256CheckSum hash.Hash, pemBytes []byte) (*models.LogEntryAnon, error) { re := rekorEntry(sha256CheckSum, signature, pemBytes) returnVal := models.Hashedrekord{ APIVersion: swag.String(re.APIVersion()), Spec: re.HashedRekordObj, } return doUpload(ctx, rekorClient, &returnVal) } // TLogUploadDSSEEnvelope will upload a DSSE entry for the signature and public key to the Rekor transparency log. func TLogUploadDSSEEnvelope(ctx context.Context, rekorClient *client.Rekor, signature, pemBytes []byte) (*models.LogEntryAnon, error) { e, err := dsseEntry(ctx, signature, pemBytes) if err != nil { return nil, err } return doUpload(ctx, rekorClient, e) } // TLogUploadInTotoAttestation will upload an in-toto entry for the signature and public key to the transparency log. func TLogUploadInTotoAttestation(ctx context.Context, rekorClient *client.Rekor, signature, pemBytes []byte) (*models.LogEntryAnon, error) { e, err := intotoEntry(ctx, signature, pemBytes) if err != nil { return nil, err } return doUpload(ctx, rekorClient, e) } func doUpload(ctx context.Context, rekorClient *client.Rekor, pe models.ProposedEntry) (*models.LogEntryAnon, error) { params := entries.NewCreateLogEntryParamsWithContext(ctx) params.SetProposedEntry(pe) resp, err := rekorClient.Entries.CreateLogEntry(params) if err != nil { // If the entry already exists, we get a specific error. // Here, we display the proof and succeed. var existsErr *entries.CreateLogEntryConflict if errors.As(err, &existsErr) { ui.Infof(ctx, "Signature already exists. Fetching and verifying inclusion proof.") uriSplit := strings.Split(existsErr.Location.String(), "/") uuid := uriSplit[len(uriSplit)-1] e, err := GetTlogEntry(ctx, rekorClient, uuid) if err != nil { return nil, err } rekorPubsFromAPI, err := rekorPubsFromClient(rekorClient) if err != nil { return nil, err } return e, VerifyTLogEntryOffline(ctx, e, rekorPubsFromAPI) } return nil, err } // UUID is at the end of location for _, p := range resp.Payload { return &p, nil } return nil, errors.New("bad response from server") } func rekorEntry(sha256CheckSum hash.Hash, signature, pubKey []byte) hashedrekord_v001.V001Entry { // TODO: Signatures created on a digest using a hash algorithm other than SHA256 will fail // upload right now. Plumb information on the hash algorithm used when signing from the // SignerVerifier to use for the HashedRekordObj.Data.Hash.Algorithm. return hashedrekord_v001.V001Entry{ HashedRekordObj: models.HashedrekordV001Schema{ Data: &models.HashedrekordV001SchemaData{ Hash: &models.HashedrekordV001SchemaDataHash{ Algorithm: swag.String(models.HashedrekordV001SchemaDataHashAlgorithmSha256), Value: swag.String(hex.EncodeToString(sha256CheckSum.Sum(nil))), }, }, Signature: &models.HashedrekordV001SchemaSignature{ Content: strfmt.Base64(signature), PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{ Content: strfmt.Base64(pubKey), }, }, }, } } func ComputeLeafHash(e *models.LogEntryAnon) ([]byte, error) { entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string)) if err != nil { return nil, err } return rfc6962.DefaultHasher.HashLeaf(entryBytes), nil } func getUUID(entryUUID string) (string, error) { switch len(entryUUID) { case uuidHexStringLen: if _, err := hex.DecodeString(entryUUID); err != nil { return "", fmt.Errorf("uuid %v is not a valid hex string: %w", entryUUID, err) } return entryUUID, nil case entryIDHexStringLen: uid := entryUUID[len(entryUUID)-uuidHexStringLen:] return getUUID(uid) default: return "", fmt.Errorf("invalid ID len %v for %v", len(entryUUID), entryUUID) } } func getTreeUUID(entryUUID string) (string, error) { switch len(entryUUID) { case uuidHexStringLen: // No Tree ID provided return "", nil case entryIDHexStringLen: tid := entryUUID[:treeIDHexStringLen] return getTreeUUID(tid) case treeIDHexStringLen: // Check that it's a valid int64 in hex (base 16) i, err := strconv.ParseInt(entryUUID, 16, 64) if err != nil { return "", fmt.Errorf("could not convert treeID %v to int64: %w", entryUUID, err) } // Check for invalid TreeID values if i == 0 { return "", fmt.Errorf("0 is not a valid TreeID") } return entryUUID, nil default: return "", fmt.Errorf("invalid ID len %v for %v", len(entryUUID), entryUUID) } } // Validates UUID and also shard if present. func isExpectedResponseUUID(requestEntryUUID string, responseEntryUUID string) error { // Comparare UUIDs requestUUID, err := getUUID(requestEntryUUID) if err != nil { return err } responseUUID, err := getUUID(responseEntryUUID) if err != nil { return err } if requestUUID != responseUUID { return fmt.Errorf("expected EntryUUID %s got UUID %s", requestEntryUUID, responseEntryUUID) } // Compare shards if it is in the request. requestShardID, err := getTreeUUID(requestEntryUUID) if err != nil { return err } responseShardID, err := getTreeUUID(responseEntryUUID) if err != nil { return err } // no shard ID prepends the entry UUID if requestShardID == "" || responseShardID == "" { return nil } if requestShardID != responseShardID { return fmt.Errorf("expected UUID %s from shard %s: got UUID %s from shard %s", requestEntryUUID, responseEntryUUID, requestShardID, responseShardID) } return nil } func verifyUUID(entryUUID string, e models.LogEntryAnon) error { // Verify and get the UUID. uid, err := getUUID(entryUUID) if err != nil { return err } uuid, _ := hex.DecodeString(uid) // Verify leaf hash matches hash of the entry body. computedLeafHash, err := ComputeLeafHash(&e) if err != nil { return err } if !bytes.Equal(computedLeafHash, uuid) { return fmt.Errorf("computed leaf hash did not match UUID") } return nil } func GetTlogEntry(ctx context.Context, rekorClient *client.Rekor, entryUUID string) (*models.LogEntryAnon, error) { params := entries.NewGetLogEntryByUUIDParamsWithContext(ctx) params.SetEntryUUID(entryUUID) resp, err := rekorClient.Entries.GetLogEntryByUUID(params) if err != nil { return nil, err } for k, e := range resp.Payload { // Validate that request EntryUUID matches the response UUID and response shard ID if err := isExpectedResponseUUID(entryUUID, k); err != nil { return nil, fmt.Errorf("unexpected entry returned from rekor server: %w", err) } // Check that body hash matches UUID if err := verifyUUID(k, e); err != nil { return nil, err } return &e, nil } return nil, errors.New("empty response") } func proposedEntries(b64Sig string, payload, pubKey []byte) ([]models.ProposedEntry, error) { var proposedEntry []models.ProposedEntry signature, err := base64.StdEncoding.DecodeString(b64Sig) if err != nil { return nil, fmt.Errorf("decoding base64 signature: %w", err) } // The fact that there's no signature (or empty rather), implies // that this is an Attestation that we're verifying. if len(signature) == 0 { intotoEntry, err := intotoEntry(context.Background(), payload, pubKey) if err != nil { return nil, err } dsseEntry, err := dsseEntry(context.Background(), payload, pubKey) if err != nil { return nil, err } proposedEntry = []models.ProposedEntry{dsseEntry, intotoEntry} } else { sha256CheckSum := sha256.New() if _, err := sha256CheckSum.Write(payload); err != nil { return nil, err } re := rekorEntry(sha256CheckSum, signature, pubKey) entry := &models.Hashedrekord{ APIVersion: swag.String(re.APIVersion()), Spec: re.HashedRekordObj, } proposedEntry = []models.ProposedEntry{entry} } return proposedEntry, nil } func FindTlogEntry(ctx context.Context, rekorClient *client.Rekor, b64Sig string, payload, pubKey []byte) ([]models.LogEntryAnon, error) { searchParams := entries.NewSearchLogQueryParamsWithContext(ctx) searchLogQuery := models.SearchLogQuery{} proposedEntries, err := proposedEntries(b64Sig, payload, pubKey) if err != nil { return nil, err } searchLogQuery.SetEntries(proposedEntries) searchParams.SetEntry(&searchLogQuery) resp, err := rekorClient.Entries.SearchLogQuery(searchParams) if err != nil { return nil, fmt.Errorf("searching log query: %w", err) } if len(resp.Payload) == 0 { return nil, errors.New("signature not found in transparency log") } // This may accumulate multiple entries on multiple tree IDs. results := make([]models.LogEntryAnon, 0) for _, logEntry := range resp.GetPayload() { for k, e := range logEntry { // Check body hash matches uuid if err := verifyUUID(k, e); err != nil { continue } results = append(results, e) } } return results, nil } // VerifyTLogEntryOffline verifies a TLog entry against a map of trusted rekorPubKeys indexed // by log id. func VerifyTLogEntryOffline(ctx context.Context, e *models.LogEntryAnon, rekorPubKeys *TrustedTransparencyLogPubKeys) error { if e.Verification == nil || e.Verification.InclusionProof == nil { return errors.New("inclusion proof not provided") } if rekorPubKeys == nil || rekorPubKeys.Keys == nil { return errors.New("no trusted rekor public keys provided") } // Make sure all the rekorPubKeys are ecsda.PublicKeys for k, v := range rekorPubKeys.Keys { if _, ok := v.PubKey.(*ecdsa.PublicKey); !ok { return fmt.Errorf("rekor Public key for LogID %s is not type ecdsa.PublicKey", k) } } hashes := [][]byte{} for _, h := range e.Verification.InclusionProof.Hashes { hb, _ := hex.DecodeString(h) hashes = append(hashes, hb) } rootHash, _ := hex.DecodeString(*e.Verification.InclusionProof.RootHash) entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string)) if err != nil { return err } leafHash := rfc6962.DefaultHasher.HashLeaf(entryBytes) // Verify the inclusion proof. if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(*e.Verification.InclusionProof.LogIndex), uint64(*e.Verification.InclusionProof.TreeSize), leafHash, hashes, rootHash); err != nil { return fmt.Errorf("verifying inclusion proof: %w", err) } // Verify rekor's signature over the SET. payload := bundle.RekorPayload{ Body: e.Body, IntegratedTime: *e.IntegratedTime, LogIndex: *e.LogIndex, LogID: *e.LogID, } pubKey, ok := rekorPubKeys.Keys[payload.LogID] if !ok { return errors.New("rekor log public key not found for payload. Check your TUF root (see cosign initialize) or set a custom key with env var SIGSTORE_REKOR_PUBLIC_KEY") } err = VerifySET(payload, []byte(e.Verification.SignedEntryTimestamp), pubKey.PubKey.(*ecdsa.PublicKey)) if err != nil { return fmt.Errorf("verifying signedEntryTimestamp: %w", err) } if pubKey.Status != tuf.Active { ui.Infof(ctx, "Successfully verified Rekor entry using an expired verification key") } return nil } func NewTrustedTransparencyLogPubKeys() TrustedTransparencyLogPubKeys { return TrustedTransparencyLogPubKeys{Keys: make(map[string]TransparencyLogPubKey, 0)} } // constructRekorPubkey returns a log ID and RekorPubKey from a given // byte-array representing the PEM-encoded Rekor key and a status. func (t *TrustedTransparencyLogPubKeys) AddTransparencyLogPubKey(pemBytes []byte, status tuf.StatusKind) error { pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pemBytes) if err != nil { return err } keyID, err := GetTransparencyLogID(pubKey) if err != nil { return err } t.Keys[keyID] = TransparencyLogPubKey{PubKey: pubKey, Status: status} return nil } cosign-2.5.0/pkg/cosign/tlog_test.go000066400000000000000000000127271477503325500174220ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/hex" "encoding/pem" "strings" "testing" ttestdata "github.com/google/certificate-transparency-go/trillian/testdata" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/tuf" ) var ( demoLogID = [32]byte{19, 56, 222, 93, 229, 36, 102, 128, 227, 214, 3, 121, 93, 175, 126, 236, 97, 217, 34, 32, 40, 233, 98, 27, 46, 179, 164, 251, 84, 10, 60, 57} ) func TestGetRekorPubKeys(t *testing.T) { t.Setenv("TUF_ROOT", t.TempDir()) keys, err := GetRekorPubs(context.Background()) if err != nil { t.Fatalf("Unexpected error calling GetRekorPubs, expected nil: %v", err) } if len(keys.Keys) == 0 { t.Errorf("expected 1 or more keys, got 0") } // check that the mapping of key digest to key is correct for logID, key := range keys.Keys { expectedLogID, err := GetTransparencyLogID(key.PubKey) if err != nil { t.Fatalf("unexpected error generated log ID: %v", err) } if logID != expectedLogID { t.Fatalf("key digests are not equal") } } } func TestExpectedRekorResponse(t *testing.T) { validUUID := "f794467401d57241b7903737211c721cb3315648d077a9f02ceefb6e404a05de" validUUID1 := "7794467401d57241b7903737211c721cb3315648d077a9f02ceefb6e404a05de" validTreeID := "0FFFFFFFFFFFFFFF" validTreeID1 := "3315648d077a9f02" invalidTreeID := "0000000000000000" tests := []struct { name string requestUUID string responseUUID string wantErr bool }{ { name: "valid match with request & response entry UUID", requestUUID: validTreeID + validUUID, responseUUID: validTreeID + validUUID, wantErr: false, }, // The following is the current typical Rekor behavior. { name: "valid match with request entry UUID", requestUUID: validTreeID + validUUID, responseUUID: validUUID, wantErr: false, }, { name: "valid match with request UUID", requestUUID: validUUID, responseUUID: validUUID, wantErr: false, }, { name: "valid match with response entry UUID", requestUUID: validUUID, responseUUID: validTreeID + validUUID, wantErr: false, }, { name: "mismatch uuid with response tree id", requestUUID: validUUID, responseUUID: validTreeID + validUUID1, wantErr: true, }, { name: "mismatch uuid with request tree id", requestUUID: validTreeID + validUUID1, responseUUID: validUUID, wantErr: true, }, { name: "mismatch tree id", requestUUID: validTreeID + validUUID, responseUUID: validTreeID1 + validUUID, wantErr: true, }, { name: "invalid response tree id", requestUUID: validTreeID + validUUID, responseUUID: invalidTreeID + validUUID, wantErr: true, }, { name: "invalid request tree id", requestUUID: invalidTreeID + validUUID, responseUUID: validUUID, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := isExpectedResponseUUID(tt.requestUUID, tt.responseUUID); (got != nil) != tt.wantErr { t.Errorf("isExpectedResponseUUID() = %v, want %v", got, tt.wantErr) } }) } } func TestGetCTLogID(t *testing.T) { block, _ := pem.Decode([]byte(ttestdata.DemoPublicKey)) pk, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { t.Fatalf("unexpected error loading public key: %v", err) } got, err := GetTransparencyLogID(pk) if err != nil { t.Fatalf("error getting logid: %v", err) } if want := hex.EncodeToString(demoLogID[:]); got != want { t.Errorf("logID: \n%v want \n%v", got, want) } } func TestVerifyTLogEntryOfflineFailsWithInvalidPublicKey(t *testing.T) { // Then try to validate with keys that are not ecdsa.PublicKey and should // fail. var rsaPrivKey crypto.PrivateKey rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { t.Fatalf("Unable to create RSA test key: %v", err) } var signer crypto.Signer var ok bool if signer, ok = rsaPrivKey.(crypto.Signer); !ok { t.Fatalf("Unable to create signer out of RSA test key: %v", err) } rsaPEM, err := cryptoutils.MarshalPublicKeyToPEM(signer.Public()) if err != nil { t.Fatalf("Unable to marshal RSA test key: %v", err) } rekorPubKeys := NewTrustedTransparencyLogPubKeys() if err = rekorPubKeys.AddTransparencyLogPubKey(rsaPEM, tuf.Active); err != nil { t.Fatalf("failed to add RSA key to transparency log public keys: %v", err) } err = VerifyTLogEntryOffline(context.Background(), &models.LogEntryAnon{Verification: &models.LogEntryAnonVerification{InclusionProof: &models.InclusionProof{}}}, &rekorPubKeys) if err == nil { t.Fatal("Wanted error got none") } if !strings.Contains(err.Error(), "is not type ecdsa.PublicKey") { t.Fatalf("Did not get expected error message, wanted 'is not type ecdsa.PublicKey' got: %v", err) } } cosign-2.5.0/pkg/cosign/tsa.go000066400000000000000000000111731477503325500161770ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "bytes" "context" "crypto/x509" "fmt" "os" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/tuf" ) const ( tsaLeafCertStr = `tsa_leaf.crt.pem` tsaRootCertStr = `tsa_root.crt.pem` tsaIntermediateCertStrPattern = `tsa_intermediate_%d.crt.pem` ) type TSACertificates struct { LeafCert *x509.Certificate IntermediateCerts []*x509.Certificate RootCert []*x509.Certificate } type GetTargetStub func(ctx context.Context, usage tuf.UsageKind, names []string) ([]byte, error) func GetTufTargets(ctx context.Context, usage tuf.UsageKind, names []string) ([]byte, error) { tufClient, err := tuf.NewFromEnv(ctx) if err != nil { return nil, fmt.Errorf("error creating TUF client: %w", err) } targets, err := tufClient.GetTargetsByMeta(usage, names) if err != nil { return nil, fmt.Errorf("error fetching targets by metadata with usage %v: %w", usage, err) } var buffer bytes.Buffer for _, target := range targets { buffer.Write(target.Target) buffer.WriteByte('\n') } return buffer.Bytes(), nil } func isTufTargetExist(ctx context.Context, name string) (bool, error) { tufClient, err := tuf.NewFromEnv(ctx) if err != nil { return false, fmt.Errorf("error creating TUF client: %w", err) } _, err = tufClient.GetTarget(name) if err != nil { return false, nil } return true, nil } // GetTSACerts retrieves trusted TSA certificates from the embedded or cached // TUF root. If expired, makes a network call to retrieve the updated targets. // By default, the certificates come from TUF, but you can override this for test // purposes by using an env variable `SIGSTORE_TSA_CERTIFICATE_FILE` or a file path // specified in `certChainPath`. If using an alternate, the file should be in PEM format. func GetTSACerts(ctx context.Context, certChainPath string, fn GetTargetStub) (*TSACertificates, error) { altTSACert := env.Getenv(env.VariableSigstoreTSACertificateFile) var raw []byte var err error var exists bool switch { case altTSACert != "": raw, err = os.ReadFile(altTSACert) case certChainPath != "": raw, err = os.ReadFile(certChainPath) default: certNames := []string{tsaLeafCertStr, tsaRootCertStr} for i := 0; ; i++ { intermediateCertStr := fmt.Sprintf(tsaIntermediateCertStrPattern, i) exists, err = isTufTargetExist(ctx, intermediateCertStr) if err != nil { return nil, fmt.Errorf("error fetching TSA certificates: %w", err) } if !exists { break } certNames = append(certNames, intermediateCertStr) } raw, err = fn(ctx, tuf.TSA, certNames) if err != nil { return nil, fmt.Errorf("error fetching TSA certificates: %w", err) } } if err != nil { return nil, fmt.Errorf("error reading TSA certificate file: %w", err) } leaves, intermediates, roots, err := splitPEMCertificateChain(raw) if err != nil { return nil, fmt.Errorf("error splitting TSA certificates: %w", err) } if len(leaves) != 1 { return nil, fmt.Errorf("TSA certificate chain must contain exactly one leaf certificate") } if len(roots) == 0 { return nil, fmt.Errorf("TSA certificate chain must contain at least one root certificate") } return &TSACertificates{ LeafCert: leaves[0], IntermediateCerts: intermediates, RootCert: roots, }, nil } // splitPEMCertificateChain returns a list of leaf (non-CA) certificates, a certificate pool for // intermediate CA certificates, and a certificate pool for root CA certificates func splitPEMCertificateChain(pem []byte) (leaves, intermediates, roots []*x509.Certificate, err error) { certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pem) if err != nil { return nil, nil, nil, err } for _, cert := range certs { if !cert.IsCA { leaves = append(leaves, cert) } else { // root certificates are self-signed if bytes.Equal(cert.RawSubject, cert.RawIssuer) { roots = append(roots, cert) } else { intermediates = append(intermediates, cert) } } } return leaves, intermediates, roots, nil } cosign-2.5.0/pkg/cosign/tsa_test.go000066400000000000000000000101031477503325500172260ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "errors" "os" "testing" "github.com/stretchr/testify/require" ) const ( testLeafCert = `-----BEGIN CERTIFICATE----- MIIBjzCCATSgAwIBAgIRAOoa5khdNMW26Nz0VCvjbBAwCgYIKoZIzj0EAwIwGzEZ MBcGA1UEAxMQaHR0cHM6Ly9ibGFoLmNvbTAgFw0yNDA2MDMyMDE2MDFaGA8yMTI0 MDUxMDIwMTYwMFowGzEZMBcGA1UEAxMQaHR0cHM6Ly9ibGFoLmNvbTBZMBMGByqG SM49AgEGCCqGSM49AwEHA0IABL7w/TW5lOU9KwnGQRIyZp/ReNQF1eA2rKC582Jo nMomwCk2bA8c5dHrvvHe+mI8JeMNEg3lkIsVQp46dKGlgYujVzBVMA4GA1UdDwEB /wQEAwIBBjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSA7lVsQm5OUzvYi+o8PuBs CrAnljAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAKBggqhkjOPQQDAgNJADBGAiEA oJSZgJPX2tqXhfvLm+5UR399+E6+rgUnSRUf4+p+K5gCIQCmtfuv8IkUIYE5ybtx +bn5E95xINfDMSPBa+0PEbB5RA== -----END CERTIFICATE-----` testRootCert = `-----BEGIN CERTIFICATE----- MIIBezCCASKgAwIBAgIRAMvdlXw/uuYvsJaCTa02uW4wCgYIKoZIzj0EAwIwGzEZ MBcGA1UEAxMQaHR0cHM6Ly9ibGFoLmNvbTAgFw0yNDA2MDMyMDE1NTFaGA8yMTI0 MDUxMDIwMTU1MFowGzEZMBcGA1UEAxMQaHR0cHM6Ly9ibGFoLmNvbTBZMBMGByqG SM49AgEGCCqGSM49AwEHA0IABLziRBPdWUTx9x3Z7zIMyo/C9cqsLK+hqnWDQS7K TA38mZhMHnJ0vSaEA4g9J2ccI1x4G/HegCi9LkJG/EZLBjyjRTBDMA4GA1UdDwEB /wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBQuDQqo97s/5Lc5 IxmFcVg3arCV2DAKBggqhkjOPQQDAgNHADBEAiAJOr0GnYaqVxShSEgVJKi/hYXf PH5bKk0M9ceasS7VwQIgMkxzlWr+m10OELtAbOlI8faN/5WFKm8m8rrwnhmHzjw= -----END CERTIFICATE-----` ) func MockGetTufTargets(name string) ([]byte, error) { switch name { case `tsa_leaf.crt.pem`: return []byte(testLeafCert), nil case `tsa_root.crt.pem`: return []byte(testRootCert), nil default: return nil, errors.New("no intermediates") } } func TestGetTSACertsFromEnv(t *testing.T) { tempFile, err := os.CreateTemp("", "tsa_cert_chain.pem") require.NoError(t, err) defer os.Remove(tempFile.Name()) _, err = tempFile.Write([]byte(testLeafCert + "\n" + testRootCert)) require.NoError(t, err) os.Setenv("SIGSTORE_TSA_CERTIFICATE_FILE", tempFile.Name()) defer os.Unsetenv("SIGSTORE_TSA_CERTIFICATE_FILE") tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name(), GetTufTargets) if err != nil { t.Fatalf("Failed to get TSA certs from env: %v", err) } require.NotNil(t, tsaCerts) require.NotNil(t, tsaCerts.LeafCert) require.NotNil(t, tsaCerts.RootCert) require.Len(t, tsaCerts.RootCert, 1) } func TestGetTSACertsFromPath(t *testing.T) { tempFile, err := os.CreateTemp("", "tsa_cert_chain_path.pem") require.NoError(t, err) defer os.Remove(tempFile.Name()) _, err = tempFile.Write([]byte(testLeafCert + "\n" + testRootCert)) require.NoError(t, err) tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name(), GetTufTargets) if err != nil { t.Fatalf("Failed to get TSA certs from path: %v", err) } require.NotNil(t, tsaCerts) require.NotNil(t, tsaCerts.LeafCert) require.NotNil(t, tsaCerts.RootCert) require.Len(t, tsaCerts.RootCert, 1) } func TestGetTSACertsFromTUF(t *testing.T) { originalValue := os.Getenv("SIGSTORE_TSA_CERTIFICATE_FILE") os.Unsetenv("SIGSTORE_TSA_CERTIFICATE_FILE") defer os.Setenv("SIGSTORE_TSA_CERTIFICATE_FILE", originalValue) tempFile, err := os.CreateTemp("", "tsa_cert_chain.pem") require.NoError(t, err) defer os.Remove(tempFile.Name()) _, err = tempFile.Write([]byte(testLeafCert + "\n" + testRootCert)) require.NoError(t, err) tsaCerts, err := GetTSACerts(context.Background(), tempFile.Name(), GetTufTargets) if err != nil { t.Fatalf("Failed to get TSA certs from TUF: %v", err) } require.NotNil(t, tsaCerts) require.NotNil(t, tsaCerts.LeafCert) require.NotNil(t, tsaCerts.RootCert) require.Len(t, tsaCerts.RootCert, 1) } cosign-2.5.0/pkg/cosign/verifiers.go000066400000000000000000000050341477503325500174050ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "encoding/base64" "encoding/json" "errors" "fmt" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/in-toto/in-toto-golang/in_toto" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/sigstore/pkg/signature/payload" ) // SimpleClaimVerifier verifies that sig.Payload() is a SimpleContainerImage payload which references the given image digest and contains the given annotations. func SimpleClaimVerifier(sig oci.Signature, imageDigest v1.Hash, annotations map[string]interface{}) error { p, err := sig.Payload() if err != nil { return err } ss := &payload.SimpleContainerImage{} if err := json.Unmarshal(p, ss); err != nil { return err } foundDgst := ss.Critical.Image.DockerManifestDigest if foundDgst != imageDigest.String() { return fmt.Errorf("invalid or missing digest in claim: %s", foundDgst) } if annotations != nil { if !correctAnnotations(annotations, ss.Optional) { return errors.New("missing or incorrect annotation") } } return nil } // IntotoSubjectClaimVerifier verifies that sig.Payload() is an Intoto statement which references the given image digest. func IntotoSubjectClaimVerifier(sig oci.Signature, imageDigest v1.Hash, _ map[string]interface{}) error { p, err := sig.Payload() if err != nil { return err } // The payload here is an envelope. We already verified the signature earlier. e := dsse.Envelope{} if err := json.Unmarshal(p, &e); err != nil { return err } stBytes, err := base64.StdEncoding.DecodeString(e.Payload) if err != nil { return err } st := in_toto.Statement{} if err := json.Unmarshal(stBytes, &st); err != nil { return err } for _, subj := range st.Subject { dgst, ok := subj.Digest["sha256"] if !ok { continue } subjDigest := "sha256:" + dgst if subjDigest == imageDigest.String() { return nil } } return errors.New("no matching subject digest found") } cosign-2.5.0/pkg/cosign/verifiers_test.go000066400000000000000000000111571477503325500204470ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "testing" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/pkg/oci/static" ) /* The following JSON is the payload in valid attestation: { "_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://cosign.sigstore.dev/attestation/v1", "subject": [ { "name": "registry.local:5000/knative/demo", "digest": { "sha256": "6c6fd6a4115c6e998ff357cd914680931bb9a6c1a7cd5f5cb2f5e1c0932ab6ed" } } ], "predicate": { "Data": "foobar test attestation", "Timestamp": "2022-04-07T19:22:25Z" } } */ const ( validIntotoStatement = `{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6InJlZ2lzdHJ5LmxvY2FsOjUwMDAva25hdGl2ZS9kZW1vIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjZjNmZkNmE0MTE1YzZlOTk4ZmYzNTdjZDkxNDY4MDkzMWJiOWE2YzFhN2NkNWY1Y2IyZjVlMWMwOTMyYWI2ZWQifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb2JhciB0ZXN0IGF0dGVzdGF0aW9uIiwiVGltZXN0YW1wIjoiMjAyMi0wNC0wN1QxOToyMjoyNVoifX0=","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}` invalidIntotoStatementBadEncoding = `{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6InJlZ2lzdHJ5LmxvY2FsOjUwMDAva25hdGl2ZS9kZW1vIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjZjNmZkNmE0MTE1YzZlOTk4ZmYzNTdjZDkxNDY4MDkzMWJiOWE2YzFhN2NkNWY1Y2IyZjVlMWMwOTMyYWI2ZWQifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb2JhciB0ZXN0IGF0dGVzdGF0aW9uIiwiVGltZXN0YW1wIjoiMjAyMi0wNC0wN1QxOToyMjoyNV=","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}` // Start with valid, but change subject.Digest.sha256 to subject.Digest.999 validIntotoStatementMissingSubject = `{"payloadType":"application/vnd.in-toto+json","payload":"ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImNvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJyZWdpc3RyeS5sb2NhbDo1MDAwL2tuYXRpdmUvZGVtbyIsCiAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgIjk5OSI6ICI2YzZmZDZhNDExNWM2ZTk5OGZmMzU3Y2Q5MTQ2ODA5MzFiYjlhNmMxYTdjZDVmNWNiMmY1ZTFjMDkzMmFiNmVkIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlIjogewogICAgIkRhdGEiOiAiZm9vYmFyIHRlc3QgYXR0ZXN0YXRpb24iLAogICAgIlRpbWVzdGFtcCI6ICIyMDIyLTA0LTA3VDE5OjIyOjI1WiIKICB9Cn0K","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}` ) var validDigest = v1.Hash{Algorithm: "sha256", Hex: "6c6fd6a4115c6e998ff357cd914680931bb9a6c1a7cd5f5cb2f5e1c0932ab6ed"} var invalidDigest = v1.Hash{Algorithm: "sha256", Hex: "6c6fd6a4115c6e998ff357cd914680931bb9a6c1a7cd5f5cb2f5e1c0932xxxxx"} func Test_IntotoSubjectClaimVerifier(t *testing.T) { tests := []struct { payload string digest v1.Hash shouldFail bool }{{payload: `{"payloadType":"notinttoto"}`, shouldFail: true}, {payload: `{"payloadType":"notmarshallable}`, shouldFail: true}, {payload: invalidIntotoStatementBadEncoding, shouldFail: true}, {payload: validIntotoStatement, shouldFail: true}, // no matching image hash {payload: validIntotoStatement, digest: invalidDigest, shouldFail: true}, {payload: validIntotoStatementMissingSubject, digest: validDigest, shouldFail: true}, {payload: validIntotoStatement, digest: validDigest, shouldFail: false}, } for _, tc := range tests { ociSig, err := static.NewSignature([]byte(tc.payload), "") if err != nil { t.Fatal("Failed to create static.NewSignature: ", err) } got := IntotoSubjectClaimVerifier(ociSig, tc.digest, nil) if got != nil && !tc.shouldFail { t.Error("Expected ClaimVerifier to succeed but failed: ", got) } if got == nil && tc.shouldFail { t.Error("Expected ClaimVerifier to fail but didn't: ") } } } cosign-2.5.0/pkg/cosign/verify.go000066400000000000000000001521211477503325500167130ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/sha256" "crypto/x509" "encoding/asn1" "encoding/base64" "encoding/hex" "encoding/json" "encoding/pem" "errors" "fmt" "io/fs" "net/http" "os" "regexp" "strings" "time" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/digitorus/timestamp" "github.com/go-openapi/runtime" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/nozzle/throttler" ssldsse "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/internal/pkg/cosign" ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote" "github.com/sigstore/cosign/v2/internal/ui" "github.com/sigstore/cosign/v2/pkg/blob" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/layout" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" rekor_types "github.com/sigstore/rekor/pkg/types" dsse_v001 "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1" intoto_v002 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2" rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" "github.com/sigstore/sigstore/pkg/signature/options" "github.com/sigstore/sigstore/pkg/tuf" tsaverification "github.com/sigstore/timestamp-authority/pkg/verification" ) // Identity specifies an issuer/subject to verify a signature against. // Both IssuerRegExp/SubjectRegExp support regexp while Issuer/Subject are for // strict matching. type Identity struct { Issuer string Subject string IssuerRegExp string SubjectRegExp string } // CheckOpts are the options for checking signatures. type CheckOpts struct { // RegistryClientOpts are the options for interacting with the container registry. RegistryClientOpts []ociremote.Option // Annotations optionally specifies image signature annotations to verify. Annotations map[string]interface{} // ClaimVerifier, if provided, verifies claims present in the oci.Signature. ClaimVerifier func(sig oci.Signature, imageDigest v1.Hash, annotations map[string]interface{}) error // RekorClient, if set, is used to make online tlog calls use to verify signatures and public keys. RekorClient *client.Rekor // RekorPubKeys, if set, is used to validate signatures on log entries from // Rekor. It is a map from LogID to crypto.PublicKey. LogID is // derived from the PublicKey (see RFC 6962 S3.2). // Note that even though the type is of crypto.PublicKey, Rekor only allows // for ecdsa.PublicKey: https://github.com/sigstore/cosign/issues/2540 RekorPubKeys *TrustedTransparencyLogPubKeys // SigVerifier is used to verify signatures. SigVerifier signature.Verifier // PKOpts are the options provided to `SigVerifier.PublicKey()`. PKOpts []signature.PublicKeyOption // RootCerts are the root CA certs used to verify a signature's chained certificate. RootCerts *x509.CertPool // IntermediateCerts are the optional intermediate CA certs used to verify a certificate chain. IntermediateCerts *x509.CertPool // CertGithubWorkflowTrigger is the GitHub Workflow Trigger name expected for a certificate to be valid. The empty string means any certificate can be valid. CertGithubWorkflowTrigger string // CertGithubWorkflowSha is the GitHub Workflow SHA expected for a certificate to be valid. The empty string means any certificate can be valid. CertGithubWorkflowSha string // CertGithubWorkflowName is the GitHub Workflow Name expected for a certificate to be valid. The empty string means any certificate can be valid. CertGithubWorkflowName string // CertGithubWorkflowRepository is the GitHub Workflow Repository expected for a certificate to be valid. The empty string means any certificate can be valid. CertGithubWorkflowRepository string // CertGithubWorkflowRef is the GitHub Workflow Ref expected for a certificate to be valid. The empty string means any certificate can be valid. CertGithubWorkflowRef string // IgnoreSCT requires that a certificate contain an embedded SCT during verification. An SCT is proof of inclusion in a // certificate transparency log. IgnoreSCT bool // Detached SCT. Optional, as the SCT is usually embedded in the certificate. SCT []byte // CTLogPubKeys, if set, is used to validate SCTs against those keys. // It is a map from log id to LogIDMetadata. It is a map from LogID to crypto.PublicKey. LogID is derived from the PublicKey (see RFC 6962 S3.2). CTLogPubKeys *TrustedTransparencyLogPubKeys // SignatureRef is the reference to the signature file. PayloadRef should always be specified as well (though it’s possible for a _some_ signatures to be verified without it, with a warning). SignatureRef string // PayloadRef is a reference to the payload file. Applicable only if SignatureRef is set. PayloadRef string // Identities is an array of Identity (Subject, Issuer) matchers that have // to be met for the signature to ve valid. Identities []Identity // Force offline verification of the signature Offline bool // Set of flags to verify an RFC3161 timestamp used for trusted timestamping // TSACertificate is the certificate used to sign the timestamp. Optional, if provided in the timestamp TSACertificate *x509.Certificate // TSARootCertificates are the set of roots to verify the TSA certificate TSARootCertificates []*x509.Certificate // TSAIntermediateCertificates are the set of intermediates for chain building TSAIntermediateCertificates []*x509.Certificate // UseSignedTimestamps enables timestamp verification using a TSA UseSignedTimestamps bool // IgnoreTlog skip tlog verification IgnoreTlog bool // The amount of maximum workers for parallel executions. // Defaults to 10. MaxWorkers int // Should the experimental OCI 1.1 behaviour be enabled or not. // Defaults to false. ExperimentalOCI11 bool // NewBundleFormat enables the new bundle format (Cosign Bundle Spec) and the new verifier. NewBundleFormat bool // TrustedMaterial is the trusted material to use for verification. // Currently, this is only applicable when NewBundleFormat is true. TrustedMaterial root.TrustedMaterial } type verifyTrustedMaterial struct { root.TrustedMaterial keyTrustedMaterial root.TrustedMaterial } func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstrainedVerifier, error) { if v.keyTrustedMaterial == nil { return nil, fmt.Errorf("no public key material available") } return v.keyTrustedMaterial.PublicKeyVerifier(hint) } // verificationOptions returns the verification options for verifying with sigstore-go. func (co *CheckOpts) verificationOptions() (trustedMaterial root.TrustedMaterial, verifierOptions []verify.VerifierOption, policyOptions []verify.PolicyOption, err error) { if co.TrustedMaterial == nil { return nil, nil, nil, fmt.Errorf("TrustMaterial is required") } policyOptions = make([]verify.PolicyOption, 0) if len(co.Identities) > 0 { var sanMatcher verify.SubjectAlternativeNameMatcher var issuerMatcher verify.IssuerMatcher if len(co.Identities) > 1 { return nil, nil, nil, fmt.Errorf("unsupported: multiple identities are not supported at this time") } sanMatcher, err = verify.NewSANMatcher(co.Identities[0].Subject, co.Identities[0].SubjectRegExp) if err != nil { return nil, nil, nil, err } issuerMatcher, err = verify.NewIssuerMatcher(co.Identities[0].Issuer, co.Identities[0].IssuerRegExp) if err != nil { return nil, nil, nil, err } extensions := certificate.Extensions{ GithubWorkflowTrigger: co.CertGithubWorkflowTrigger, GithubWorkflowSHA: co.CertGithubWorkflowSha, GithubWorkflowName: co.CertGithubWorkflowName, GithubWorkflowRepository: co.CertGithubWorkflowRepository, GithubWorkflowRef: co.CertGithubWorkflowRef, } certificateIdentities, err := verify.NewCertificateIdentity(sanMatcher, issuerMatcher, extensions) if err != nil { return nil, nil, nil, err } policyOptions = []verify.PolicyOption{verify.WithCertificateIdentity(certificateIdentities)} } // Wrap TrustedMaterial vTrustedMaterial := &verifyTrustedMaterial{TrustedMaterial: co.TrustedMaterial} verifierOptions = make([]verify.VerifierOption, 0) if co.SigVerifier != nil { // We are verifying with a public key policyOptions = append(policyOptions, verify.WithKey()) newExpiringKey := root.NewExpiringKey(co.SigVerifier, time.Time{}, time.Time{}) vTrustedMaterial.keyTrustedMaterial = root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) { return newExpiringKey, nil }) } else { //nolint:gocritic // We are verifying with a certificate if !co.IgnoreSCT { verifierOptions = append(verifierOptions, verify.WithSignedCertificateTimestamps(1)) } } if !co.IgnoreTlog { verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1)) } if co.UseSignedTimestamps { verifierOptions = append(verifierOptions, verify.WithSignedTimestamps(1)) } if co.IgnoreTlog && !co.UseSignedTimestamps { verifierOptions = append(verifierOptions, verify.WithCurrentTime()) } return vTrustedMaterial, verifierOptions, policyOptions, nil } // This is a substitutable signature verification function that can be used for verifying // attestations of blobs. type signatureVerificationFn func( ctx context.Context, verifier signature.Verifier, sig payloader) error // For unit testing type payloader interface { // no-op for attestations Base64Signature() (string, error) Payload() ([]byte, error) } func verifyOCIAttestation(ctx context.Context, verifier signature.Verifier, att payloader) error { payload, err := att.Payload() if err != nil { return err } env := ssldsse.Envelope{} if err := json.Unmarshal(payload, &env); err != nil { return err } if env.PayloadType != types.IntotoPayloadType { return &VerificationFailure{ fmt.Errorf("invalid payloadType %s on envelope. Expected %s", env.PayloadType, types.IntotoPayloadType), } } dssev, err := ssldsse.NewEnvelopeVerifier(&dsse.VerifierAdapter{SignatureVerifier: verifier}) if err != nil { return err } _, err = dssev.Verify(ctx, &env) return err } func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig payloader) error { b64sig, err := sig.Base64Signature() if err != nil { return err } signature, err := base64.StdEncoding.DecodeString(b64sig) if err != nil { return err } payload, err := sig.Payload() if err != nil { return err } return verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx)) } // ValidateAndUnpackCert creates a Verifier from a certificate. Verifies that the // certificate chains up to a trusted root using intermediate certificate chain coming from CheckOpts. // Optionally verifies the subject and issuer of the certificate. func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Verifier, error) { return ValidateAndUnpackCertWithIntermediates(cert, co, co.IntermediateCerts) } // ValidateAndUnpackCertWithIntermediates creates a Verifier from a certificate. Verifies that the // certificate chains up to a trusted root using intermediate cert passed as separate argument. // Optionally verifies the subject and issuer of the certificate. func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpts, intermediateCerts *x509.CertPool) (signature.Verifier, error) { verifier, err := signature.LoadVerifier(cert.PublicKey, crypto.SHA256) if err != nil { return nil, fmt.Errorf("invalid certificate found on signature: %w", err) } // Handle certificates where the Subject Alternative Name is not set to a supported // GeneralName (RFC 5280 4.2.1.6). Go only supports DNS, IP addresses, email addresses, // or URIs as SANs. Fulcio can issue a certificate with an OtherName GeneralName, so // remove the unhandled critical SAN extension before verifying. if len(cert.UnhandledCriticalExtensions) > 0 { var unhandledExts []asn1.ObjectIdentifier for _, oid := range cert.UnhandledCriticalExtensions { if !oid.Equal(cryptoutils.SANOID) { unhandledExts = append(unhandledExts, oid) } } cert.UnhandledCriticalExtensions = unhandledExts } // Now verify the cert, then the signature. chains, err := TrustedCert(cert, co.RootCerts, intermediateCerts) if err != nil { return nil, err } err = CheckCertificatePolicy(cert, co) if err != nil { return nil, err } // If IgnoreSCT is set, skip the SCT check if co.IgnoreSCT { return verifier, nil } contains, err := ContainsSCT(cert.Raw) if err != nil { return nil, err } if !contains && len(co.SCT) == 0 { return nil, &VerificationFailure{ fmt.Errorf("certificate does not include required embedded SCT and no detached SCT was set"), } } // handle if chains has more than one chain - grab first and print message if len(chains) > 1 { fmt.Fprintf(os.Stderr, "**Info** Multiple valid certificate chains found. Selecting the first to verify the SCT.\n") } if contains { if err := VerifyEmbeddedSCT(context.Background(), chains[0], co.CTLogPubKeys); err != nil { return nil, err } } else { chain := chains[0] if len(chain) < 2 { return nil, errors.New("certificate chain must contain at least a certificate and its issuer") } certPEM, err := cryptoutils.MarshalCertificateToPEM(chain[0]) if err != nil { return nil, err } chainPEM, err := cryptoutils.MarshalCertificatesToPEM(chain[1:]) if err != nil { return nil, err } if err := VerifySCT(context.Background(), certPEM, chainPEM, co.SCT, co.CTLogPubKeys); err != nil { return nil, err } } return verifier, nil } // CheckCertificatePolicy checks that the certificate subject and issuer match // the expected values. func CheckCertificatePolicy(cert *x509.Certificate, co *CheckOpts) error { ce := CertExtensions{Cert: cert} if err := validateCertExtensions(ce, co); err != nil { return err } oidcIssuer := ce.GetIssuer() sans := cryptoutils.GetSubjectAlternateNames(cert) // If there are identities given, go through them and if one of them // matches, call that good, otherwise, return an error. if len(co.Identities) > 0 { for _, identity := range co.Identities { issuerMatches := false switch { // Check the issuer first case identity.IssuerRegExp != "": if regex, err := regexp.Compile(identity.IssuerRegExp); err != nil { return fmt.Errorf("malformed issuer in identity: %s : %w", identity.IssuerRegExp, err) } else if regex.MatchString(oidcIssuer) { issuerMatches = true } case identity.Issuer != "": if identity.Issuer == oidcIssuer { issuerMatches = true } default: // No issuer constraint on this identity, so checks out issuerMatches = true } // Then the subject subjectMatches := false switch { case identity.SubjectRegExp != "": regex, err := regexp.Compile(identity.SubjectRegExp) if err != nil { return fmt.Errorf("malformed subject in identity: %s : %w", identity.SubjectRegExp, err) } for _, san := range sans { if regex.MatchString(san) { subjectMatches = true break } } case identity.Subject != "": for _, san := range sans { if san == identity.Subject { subjectMatches = true break } } default: // No subject constraint on this identity, so checks out subjectMatches = true } if subjectMatches && issuerMatches { // If both issuer / subject match, return verified return nil } } return &VerificationFailure{ fmt.Errorf("none of the expected identities matched what was in the certificate, got subjects [%s] with issuer %s", strings.Join(sans, ", "), oidcIssuer), } } return nil } func validateCertExtensions(ce CertExtensions, co *CheckOpts) error { if co.CertGithubWorkflowTrigger != "" { if ce.GetCertExtensionGithubWorkflowTrigger() != co.CertGithubWorkflowTrigger { return &VerificationFailure{ fmt.Errorf("expected GitHub Workflow Trigger not found in certificate"), } } } if co.CertGithubWorkflowSha != "" { if ce.GetExtensionGithubWorkflowSha() != co.CertGithubWorkflowSha { return &VerificationFailure{ fmt.Errorf("expected GitHub Workflow SHA not found in certificate"), } } } if co.CertGithubWorkflowName != "" { if ce.GetCertExtensionGithubWorkflowName() != co.CertGithubWorkflowName { return &VerificationFailure{ fmt.Errorf("expected GitHub Workflow Name not found in certificate"), } } } if co.CertGithubWorkflowRepository != "" { if ce.GetCertExtensionGithubWorkflowRepository() != co.CertGithubWorkflowRepository { return &VerificationFailure{ fmt.Errorf("expected GitHub Workflow Repository not found in certificate"), } } } if co.CertGithubWorkflowRef != "" { if ce.GetCertExtensionGithubWorkflowRef() != co.CertGithubWorkflowRef { return &VerificationFailure{ fmt.Errorf("expected GitHub Workflow Ref not found in certificate"), } } } return nil } // ValidateAndUnpackCertWithChain creates a Verifier from a certificate. Verifies that the certificate // chains up to the provided root. Chain should start with the parent of the certificate and end with the root. // Optionally verifies the subject and issuer of the certificate. func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certificate, co *CheckOpts) (signature.Verifier, error) { if len(chain) == 0 { return nil, errors.New("no chain provided to validate certificate") } rootPool := x509.NewCertPool() rootPool.AddCert(chain[len(chain)-1]) co.RootCerts = rootPool subPool := x509.NewCertPool() for _, c := range chain[:len(chain)-1] { subPool.AddCert(c) } co.IntermediateCerts = subPool return ValidateAndUnpackCert(cert, co) } func tlogValidateEntry(ctx context.Context, client *client.Rekor, rekorPubKeys *TrustedTransparencyLogPubKeys, sig oci.Signature, pem []byte) (*models.LogEntryAnon, error) { b64sig, err := sig.Base64Signature() if err != nil { return nil, err } payload, err := sig.Payload() if err != nil { return nil, err } tlogEntries, err := FindTlogEntry(ctx, client, b64sig, payload, pem) if err != nil { return nil, err } if len(tlogEntries) == 0 { return nil, fmt.Errorf("no valid tlog entries found with proposed entry") } // Always return the earliest integrated entry. That // always suffices for verification of signature time. var earliestLogEntry models.LogEntryAnon var earliestLogEntryTime *time.Time entryVerificationErrs := make([]string, 0) for _, e := range tlogEntries { entry := e if err := VerifyTLogEntryOffline(ctx, &entry, rekorPubKeys); err != nil { entryVerificationErrs = append(entryVerificationErrs, err.Error()) continue } entryTime := time.Unix(*entry.IntegratedTime, 0) if earliestLogEntryTime == nil || entryTime.Before(*earliestLogEntryTime) { earliestLogEntryTime = &entryTime earliestLogEntry = entry } } if earliestLogEntryTime == nil { return nil, fmt.Errorf("no valid tlog entries found %s", strings.Join(entryVerificationErrs, ", ")) } return &earliestLogEntry, nil } type fakeOCISignatures struct { oci.Signatures signatures []oci.Signature } func (fos *fakeOCISignatures) Get() ([]oci.Signature, error) { return fos.signatures, nil } // VerifyImageSignatures does all the main cosign checks in a loop, returning the verified signatures. // If there were no valid signatures, we return an error. // Note that if co.ExperimentlOCI11 is set, we will attempt to verify // signatures using the experimental OCI 1.1 behavior. func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Try first using OCI 1.1 behavior if experimental flag is set. if co.ExperimentalOCI11 { verified, bundleVerified, err := verifyImageSignaturesExperimentalOCI(ctx, signedImgRef, co) if err == nil { return verified, bundleVerified, nil } } if co.NewBundleFormat { return nil, false, errors.New("bundle support for image signatures is not yet implemented") } // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") } // This is a carefully optimized sequence for fetching the signatures of the // entity that minimizes registry requests when supplied with a digest input digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) if err != nil { if terr := (&transport.Error{}); errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound { return nil, false, &ErrImageTagNotFound{ fmt.Errorf("image tag not found: %w", err), } } return nil, false, err } h, err := v1.NewHash(digest.Identifier()) if err != nil { return nil, false, err } var sigs oci.Signatures sigRef := co.SignatureRef if sigRef == "" { st, err := ociremote.SignatureTag(digest, co.RegistryClientOpts...) if err != nil { return nil, false, err } sigs, err = ociremote.Signatures(st, co.RegistryClientOpts...) if err != nil { return nil, false, err } } else { sigs, err = loadSignatureFromFile(ctx, sigRef, signedImgRef, co) if err != nil { return nil, false, err } } return verifySignatures(ctx, sigs, h, co) } // VerifyLocalImageSignatures verifies signatures from a saved, local image, without any network calls, returning the verified signatures. // If there were no valid signatures, we return an error. func VerifyLocalImageSignatures(ctx context.Context, path string, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") } se, err := layout.SignedImageIndex(path) if err != nil { return nil, false, err } var h v1.Hash // Verify either an image index or image. ii, err := se.SignedImageIndex(v1.Hash{}) if err != nil { return nil, false, err } i, err := se.SignedImage(v1.Hash{}) if err != nil { return nil, false, err } switch { case ii != nil: h, err = ii.Digest() if err != nil { return nil, false, err } case i != nil: h, err = i.Digest() if err != nil { return nil, false, err } default: return nil, false, errors.New("must verify either an image index or image") } sigs, err := se.Signatures() if err != nil { return nil, false, err } if sigs == nil { return nil, false, fmt.Errorf("no signatures associated with the image saved in %s", path) } return verifySignatures(ctx, sigs, h, co) } func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { sl, err := sigs.Get() if err != nil { return nil, false, err } if len(sl) == 0 { return nil, false, &ErrNoSignaturesFound{ errors.New("no signatures found"), } } signatures := make([]oci.Signature, len(sl)) bundlesVerified := make([]bool, len(sl)) workers := co.MaxWorkers if co.MaxWorkers == 0 { workers = cosign.DefaultMaxWorkers } t := throttler.New(workers, len(sl)) for i, sig := range sl { go func(sig oci.Signature, index int) { sig, err := static.Copy(sig) if err != nil { t.Done(err) return } verified, err := VerifyImageSignature(ctx, sig, h, co) bundlesVerified[index] = verified if err != nil { t.Done(err) return } signatures[index] = sig t.Done(nil) }(sig, i) // wait till workers are available t.Throttle() } for _, s := range signatures { if s != nil { checkedSignatures = append(checkedSignatures, s) } } for _, verified := range bundlesVerified { bundleVerified = bundleVerified || verified } if len(checkedSignatures) == 0 { var combinedErrors []string for _, err := range t.Errs() { combinedErrors = append(combinedErrors, err.Error()) } // TODO: ErrNoMatchingSignatures.Unwrap should return []error, // or we should replace "...%s" strings.Join with "...%w", errors.Join. return nil, false, &ErrNoMatchingSignatures{ fmt.Errorf("no matching signatures: %s", strings.Join(combinedErrors, "\n ")), } } return checkedSignatures, bundleVerified, nil } // verifyInternal holds the main verification flow for signatures and attestations. // 1. Verifies the signature using the provided verifier. // 2. Checks for transparency log entry presence: // a. Verifies the Rekor entry in the bundle, if provided. This works offline OR // b. If we don't have a Rekor entry retrieved via cert, do an online lookup (assuming // we are in experimental mode). // 3. If a certificate is provided, check its expiration using the transparency log timestamp. func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, verifyFn signatureVerificationFn, co *CheckOpts) ( bundleVerified bool, err error) { var acceptableRFC3161Time, acceptableRekorBundleTime *time.Time // Timestamps for the signature we accept, or nil if not applicable. var acceptableRFC3161Timestamp *timestamp.Timestamp if co.UseSignedTimestamps { acceptableRFC3161Timestamp, err = VerifyRFC3161Timestamp(sig, co) if err != nil { return false, fmt.Errorf("unable to verify RFC3161 timestamp bundle: %w", err) } if acceptableRFC3161Timestamp != nil { acceptableRFC3161Time = &acceptableRFC3161Timestamp.Time } } if !co.IgnoreTlog { bundleVerified, err = VerifyBundle(sig, co) if err != nil { return false, fmt.Errorf("error verifying bundle: %w", err) } if bundleVerified { // Update with the verified bundle's integrated time. t, err := getBundleIntegratedTime(sig) if err != nil { return false, fmt.Errorf("error getting bundle integrated time: %w", err) } acceptableRekorBundleTime = &t } else { // If the --offline flag was specified, fail here. bundleVerified returns false with // no error when there was no bundle provided. if co.Offline { return false, fmt.Errorf("offline verification failed") } // no Rekor client provided for an online lookup if co.RekorClient == nil { return false, fmt.Errorf("rekor client not provided for online verification") } pemBytes, err := keyBytes(sig, co) if err != nil { return false, err } e, err := tlogValidateEntry(ctx, co.RekorClient, co.RekorPubKeys, sig, pemBytes) if err != nil { return false, err } t := time.Unix(*e.IntegratedTime, 0) acceptableRekorBundleTime = &t bundleVerified = true } } verifier := co.SigVerifier if verifier == nil { // If we don't have a public key to check against, we can try a root cert. cert, err := sig.Cert() if err != nil { return false, err } if cert == nil { return false, &ErrNoCertificateFoundOnSignature{ fmt.Errorf("no certificate found on signature"), } } // Create a certificate pool for intermediate CA certificates, excluding the root chain, err := sig.Chain() if err != nil { return false, err } // If there is no chain annotation present, we preserve the pools set in the CheckOpts. var pool *x509.CertPool if len(chain) > 1 { if co.IntermediateCerts == nil { // If the intermediate certs have not been loaded in by TUF pool = x509.NewCertPool() for _, cert := range chain[:len(chain)-1] { pool.AddCert(cert) } } } // In case pool is not set than set it from co.IntermediateCerts if pool == nil { pool = co.IntermediateCerts } verifier, err = ValidateAndUnpackCertWithIntermediates(cert, co, pool) if err != nil { return false, err } } // 1. Perform cryptographic verification of the signature using the certificate's public key. if err := verifyFn(ctx, verifier, sig); err != nil { return false, err } // We can't check annotations without claims, both require unmarshalling the payload. if co.ClaimVerifier != nil { if err := co.ClaimVerifier(sig, h, co.Annotations); err != nil { return false, err } } // 2. if a certificate was used, verify the certificate expiration against a time cert, err := sig.Cert() if err != nil { return false, err } if cert != nil { // use the provided Rekor bundle or RFC3161 timestamp to check certificate expiration expirationChecked := false if acceptableRFC3161Time != nil { // Verify the cert against the timestamp time. if err := CheckExpiry(cert, *acceptableRFC3161Time); err != nil { return false, fmt.Errorf("checking expiry on certificate with timestamp: %w", err) } expirationChecked = true } if acceptableRekorBundleTime != nil { if err := CheckExpiry(cert, *acceptableRekorBundleTime); err != nil { return false, fmt.Errorf("checking expiry on certificate with bundle: %w", err) } expirationChecked = true } // if no timestamp has been provided, use the current time if !expirationChecked { if err := CheckExpiry(cert, time.Now()); err != nil { // If certificate is expired and not signed timestamp was provided then error the following message. Otherwise throw an expiration error. if co.IgnoreTlog && acceptableRFC3161Time == nil { return false, &VerificationFailure{ fmt.Errorf("expected a signed timestamp to verify an expired certificate"), } } return false, fmt.Errorf("checking expiry on certificate with bundle: %w", err) } } } return bundleVerified, nil } func keyBytes(sig oci.Signature, co *CheckOpts) ([]byte, error) { cert, err := sig.Cert() if err != nil { return nil, err } // We have a public key. if co.SigVerifier != nil { pub, err := co.SigVerifier.PublicKey(co.PKOpts...) if err != nil { return nil, err } return cryptoutils.MarshalPublicKeyToPEM(pub) } return cryptoutils.MarshalCertificateToPEM(cert) } // VerifyBlobSignature verifies a blob signature. func VerifyBlobSignature(ctx context.Context, sig oci.Signature, co *CheckOpts) (bundleVerified bool, err error) { // The hash of the artifact is unused. return verifyInternal(ctx, sig, v1.Hash{}, verifyOCISignature, co) } // VerifyImageSignature verifies a signature func VerifyImageSignature(ctx context.Context, sig oci.Signature, h v1.Hash, co *CheckOpts) (bundleVerified bool, err error) { return verifyInternal(ctx, sig, h, verifyOCISignature, co) } func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name.Reference, co *CheckOpts) (oci.Signatures, error) { var b64sig string targetSig, err := blob.LoadFileOrURL(sigRef) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, err } targetSig = []byte(sigRef) } _, err = base64.StdEncoding.DecodeString(string(targetSig)) if err == nil { b64sig = string(targetSig) } else { b64sig = base64.StdEncoding.EncodeToString(targetSig) } var payload []byte if co.PayloadRef != "" { payload, err = blob.LoadFileOrURL(co.PayloadRef) if err != nil { return nil, err } } else { digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) if err != nil { return nil, err } payload, err = ObsoletePayload(ctx, digest) if err != nil { return nil, err } } sig, err := static.NewSignature(payload, b64sig) if err != nil { return nil, err } return &fakeOCISignatures{ signatures: []oci.Signature{sig}, }, nil } // VerifyImageAttestations does all the main cosign checks in a loop, returning the verified attestations. // If there were no valid attestations, we return an error. func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil { return nil, false, errors.New("one of verifier, root certs, or TrustedMaterial is required") } if co.NewBundleFormat { return verifyImageAttestationsSigstoreBundle(ctx, signedImgRef, co) } // This is a carefully optimized sequence for fetching the attestations of // the entity that minimizes registry requests when supplied with a digest // input. digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) if err != nil { return nil, false, err } h, err := v1.NewHash(digest.Identifier()) if err != nil { return nil, false, err } st, err := ociremote.AttestationTag(digest, co.RegistryClientOpts...) if err != nil { return nil, false, err } atts, err := ociremote.Signatures(st, co.RegistryClientOpts...) if err != nil { return nil, false, err } return VerifyImageAttestation(ctx, atts, h, co) } // VerifyLocalImageAttestations verifies attestations from a saved, local image, without any network calls, // returning the verified attestations. // If there were no valid signatures, we return an error. func VerifyLocalImageAttestations(ctx context.Context, path string, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") } se, err := layout.SignedImageIndex(path) if err != nil { return nil, false, err } var h v1.Hash // Verify either an image index or image. ii, err := se.SignedImageIndex(v1.Hash{}) if err != nil { return nil, false, err } i, err := se.SignedImage(v1.Hash{}) if err != nil { return nil, false, err } switch { case ii != nil: h, err = ii.Digest() if err != nil { return nil, false, err } case i != nil: h, err = i.Digest() if err != nil { return nil, false, err } default: return nil, false, errors.New("must verify either an image index or image") } atts, err := se.Attestations() if err != nil { return nil, false, err } return VerifyImageAttestation(ctx, atts, h, co) } func VerifyBlobAttestation(ctx context.Context, att oci.Signature, h v1.Hash, co *CheckOpts) ( bool, error) { return verifyInternal(ctx, att, h, verifyOCIAttestation, co) } func VerifyImageAttestation(ctx context.Context, atts oci.Signatures, h v1.Hash, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { sl, err := atts.Get() if err != nil { return nil, false, err } attestations := make([]oci.Signature, len(sl)) bundlesVerified := make([]bool, len(sl)) workers := co.MaxWorkers if co.MaxWorkers == 0 { workers = cosign.DefaultMaxWorkers } t := throttler.New(workers, len(sl)) for i, att := range sl { go func(att oci.Signature, index int) { att, err := static.Copy(att) if err != nil { t.Done(err) return } if err := func(att oci.Signature) error { verified, err := verifyInternal(ctx, att, h, verifyOCIAttestation, co) bundlesVerified[index] = verified return err }(att); err != nil { t.Done(err) return } attestations[index] = att t.Done(nil) }(att, i) // wait till workers are available t.Throttle() } for _, a := range attestations { if a != nil { checkedAttestations = append(checkedAttestations, a) } } for _, verified := range bundlesVerified { bundleVerified = bundleVerified || verified } if len(checkedAttestations) == 0 { var combinedErrors []string for _, err := range t.Errs() { combinedErrors = append(combinedErrors, err.Error()) } return nil, false, &ErrNoMatchingAttestations{ fmt.Errorf("no matching attestations: %s", strings.Join(combinedErrors, "\n ")), } } return checkedAttestations, bundleVerified, nil } // CheckExpiry confirms the time provided is within the valid period of the cert func CheckExpiry(cert *x509.Certificate, it time.Time) error { ft := func(t time.Time) string { return t.Format(time.RFC3339) } if cert.NotAfter.Before(it) { return &VerificationFailure{ fmt.Errorf("certificate expired before signatures were entered in log: %s is before %s", ft(cert.NotAfter), ft(it)), } } if cert.NotBefore.After(it) { return &VerificationFailure{ fmt.Errorf("certificate was issued after signatures were entered in log: %s is after %s", ft(cert.NotAfter), ft(it)), } } return nil } func getBundleIntegratedTime(sig oci.Signature) (time.Time, error) { bundle, err := sig.Bundle() if err != nil { return time.Now(), err } else if bundle == nil { return time.Now(), nil } return time.Unix(bundle.Payload.IntegratedTime, 0), nil } // This verifies an offline bundle contained in the sig against the trusted // Rekor publicKeys. func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) { bundle, err := sig.Bundle() if err != nil { return false, err } else if bundle == nil { return false, nil } if co.RekorPubKeys == nil || co.RekorPubKeys.Keys == nil { return false, errors.New("no trusted rekor public keys provided") } // Make sure all the rekorPubKeys are ecsda.PublicKeys for k, v := range co.RekorPubKeys.Keys { if _, ok := v.PubKey.(*ecdsa.PublicKey); !ok { return false, fmt.Errorf("rekor Public key for LogID %s is not type ecdsa.PublicKey", k) } } if err := compareSigs(bundle.Payload.Body.(string), sig); err != nil { return false, err } if err := comparePublicKey(bundle.Payload.Body.(string), sig, co); err != nil { return false, err } pubKey, ok := co.RekorPubKeys.Keys[bundle.Payload.LogID] if !ok { return false, &VerificationFailure{ fmt.Errorf("verifying bundle: rekor log public key not found for payload"), } } err = VerifySET(bundle.Payload, bundle.SignedEntryTimestamp, pubKey.PubKey.(*ecdsa.PublicKey)) if err != nil { return false, err } if pubKey.Status != tuf.Active { fmt.Fprintf(os.Stderr, "**Info** Successfully verified Rekor entry using an expired verification key\n") } payload, err := sig.Payload() if err != nil { return false, fmt.Errorf("reading payload: %w", err) } signature, err := sig.Base64Signature() if err != nil { return false, fmt.Errorf("reading base64signature: %w", err) } alg, bundlehash, err := bundleHash(bundle.Payload.Body.(string), signature) if err != nil { return false, fmt.Errorf("computing bundle hash: %w", err) } h := sha256.Sum256(payload) payloadHash := hex.EncodeToString(h[:]) if alg != "sha256" { return false, fmt.Errorf("unexpected algorithm: %q", alg) } else if bundlehash != payloadHash { return false, fmt.Errorf("matching bundle to payload: bundle=%q, payload=%q", bundlehash, payloadHash) } return true, nil } // VerifyRFC3161Timestamp verifies that the timestamp in sig is correctly signed, and if so, // returns the timestamp value. // It returns (nil, nil) if there is no timestamp, or (nil, err) if there is an invalid timestamp or if // no root is provided with a timestamp. func VerifyRFC3161Timestamp(sig oci.Signature, co *CheckOpts) (*timestamp.Timestamp, error) { ts, err := sig.RFC3161Timestamp() switch { case err != nil: return nil, err case ts == nil: return nil, nil case co.TSARootCertificates == nil: return nil, errors.New("no TSA root certificate(s) provided to verify timestamp") } b64Sig, err := sig.Base64Signature() if err != nil { return nil, fmt.Errorf("reading base64signature: %w", err) } var tsBytes []byte if len(b64Sig) == 0 { // For attestations, the Base64Signature is not set, therefore we rely on the signed payload signedPayload, err := sig.Payload() if err != nil { return nil, fmt.Errorf("reading the payload: %w", err) } tsBytes = signedPayload } else { // create timestamp over raw bytes of signature rawSig, err := base64.StdEncoding.DecodeString(b64Sig) if err != nil { return nil, err } tsBytes = rawSig } return tsaverification.VerifyTimestampResponse(ts.SignedRFC3161Timestamp, bytes.NewReader(tsBytes), tsaverification.VerifyOpts{ TSACertificate: co.TSACertificate, Intermediates: co.TSAIntermediateCertificates, Roots: co.TSARootCertificates, }) } // compare bundle signature to the signature we are verifying func compareSigs(bundleBody string, sig oci.Signature) error { // TODO(nsmith5): modify function signature to make it more clear _why_ // we've returned nil (there are several reasons possible here). actualSig, err := sig.Base64Signature() if err != nil { return fmt.Errorf("base64 signature: %w", err) } if actualSig == "" { // NB: empty sig means this is an attestation return nil } bundleSignature, err := bundleSig(bundleBody) if err != nil { return fmt.Errorf("failed to extract signature from bundle: %w", err) } if bundleSignature == "" { return nil } if bundleSignature != actualSig { return &VerificationFailure{ fmt.Errorf("signature in bundle does not match signature being verified"), } } return nil } func comparePublicKey(bundleBody string, sig oci.Signature, co *CheckOpts) error { pemBytes, err := keyBytes(sig, co) if err != nil { return err } bundleKey, err := bundleKey(bundleBody) if err != nil { return fmt.Errorf("failed to extract key from bundle: %w", err) } decodeSecond, err := base64.StdEncoding.DecodeString(bundleKey) if err != nil { return fmt.Errorf("decoding base64 string %s", bundleKey) } // Compare the PEM bytes, to ignore spurious newlines in the public key bytes. pemFirst, rest := pem.Decode(pemBytes) if len(rest) > 0 { return fmt.Errorf("unexpected PEM block: %s", rest) } pemSecond, rest := pem.Decode(decodeSecond) if len(rest) > 0 { return fmt.Errorf("unexpected PEM block: %s", rest) } if !bytes.Equal(pemFirst.Bytes, pemSecond.Bytes) { return fmt.Errorf("comparing public key PEMs, expected %s, got %s", pemBytes, decodeSecond) } return nil } func extractEntryImpl(bundleBody string) (rekor_types.EntryImpl, error) { pe, err := models.UnmarshalProposedEntry(base64.NewDecoder(base64.StdEncoding, strings.NewReader(bundleBody)), runtime.JSONConsumer()) if err != nil { return nil, err } return rekor_types.UnmarshalEntry(pe) } func bundleHash(bundleBody, _ string) (string, string, error) { ei, err := extractEntryImpl(bundleBody) if err != nil { return "", "", err } switch entry := ei.(type) { case *dsse_v001.V001Entry: return *entry.DSSEObj.EnvelopeHash.Algorithm, *entry.DSSEObj.EnvelopeHash.Value, nil case *hashedrekord_v001.V001Entry: return *entry.HashedRekordObj.Data.Hash.Algorithm, *entry.HashedRekordObj.Data.Hash.Value, nil case *intoto_v001.V001Entry: return *entry.IntotoObj.Content.Hash.Algorithm, *entry.IntotoObj.Content.Hash.Value, nil case *intoto_v002.V002Entry: return *entry.IntotoObj.Content.Hash.Algorithm, *entry.IntotoObj.Content.Hash.Value, nil case *rekord_v001.V001Entry: return *entry.RekordObj.Data.Hash.Algorithm, *entry.RekordObj.Data.Hash.Value, nil default: return "", "", errors.New("unsupported type") } } // bundleSig extracts the signature from the rekor bundle body func bundleSig(bundleBody string) (string, error) { ei, err := extractEntryImpl(bundleBody) if err != nil { return "", err } switch entry := ei.(type) { case *dsse_v001.V001Entry: if len(entry.DSSEObj.Signatures) > 1 { return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") } return *entry.DSSEObj.Signatures[0].Signature, nil case *hashedrekord_v001.V001Entry: return entry.HashedRekordObj.Signature.Content.String(), nil case *intoto_v002.V002Entry: if len(entry.IntotoObj.Content.Envelope.Signatures) > 1 { return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") } return entry.IntotoObj.Content.Envelope.Signatures[0].Sig.String(), nil case *rekord_v001.V001Entry: return entry.RekordObj.Signature.Content.String(), nil default: return "", errors.New("unsupported type") } } // bundleKey extracts the key from the rekor bundle body func bundleKey(bundleBody string) (string, error) { ei, err := extractEntryImpl(bundleBody) if err != nil { return "", err } switch entry := ei.(type) { case *dsse_v001.V001Entry: if len(entry.DSSEObj.Signatures) > 1 { return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") } return entry.DSSEObj.Signatures[0].Verifier.String(), nil case *hashedrekord_v001.V001Entry: return entry.HashedRekordObj.Signature.PublicKey.Content.String(), nil case *intoto_v001.V001Entry: return entry.IntotoObj.PublicKey.String(), nil case *intoto_v002.V002Entry: if len(entry.IntotoObj.Content.Envelope.Signatures) > 1 { return "", errors.New("multiple signatures on DSSE envelopes are not currently supported") } return entry.IntotoObj.Content.Envelope.Signatures[0].PublicKey.String(), nil case *rekord_v001.V001Entry: return entry.RekordObj.Signature.PublicKey.Content.String(), nil default: return "", errors.New("unsupported type") } } func VerifySET(bundlePayload cbundle.RekorPayload, signature []byte, pub *ecdsa.PublicKey) error { contents, err := json.Marshal(bundlePayload) if err != nil { return fmt.Errorf("marshaling: %w", err) } canonicalized, err := jsoncanonicalizer.Transform(contents) if err != nil { return fmt.Errorf("canonicalizing: %w", err) } // verify the SET against the public key hash := sha256.Sum256(canonicalized) if !ecdsa.VerifyASN1(pub, hash[:], signature) { return &VerificationFailure{ fmt.Errorf("unable to verify SET"), } } return nil } func TrustedCert(cert *x509.Certificate, roots *x509.CertPool, intermediates *x509.CertPool) ([][]*x509.Certificate, error) { chains, err := cert.Verify(x509.VerifyOptions{ // THIS IS IMPORTANT: WE DO NOT CHECK TIMES HERE // THE CERTIFICATE IS TREATED AS TRUSTED FOREVER // WE CHECK THAT THE SIGNATURES WERE CREATED DURING THIS WINDOW CurrentTime: cert.NotBefore, Roots: roots, Intermediates: intermediates, KeyUsages: []x509.ExtKeyUsage{ x509.ExtKeyUsageCodeSigning, }, }) if err != nil { return nil, fmt.Errorf("cert verification failed: %w. Check your TUF root (see cosign initialize) or set a custom root with env var SIGSTORE_ROOT_FILE", err) } return chains, nil } func correctAnnotations(wanted, have map[string]interface{}) bool { for k, v := range wanted { if have[k] != v { return false } } return true } // verifyImageSignaturesExperimentalOCI does all the main cosign checks in a loop, returning the verified signatures. // If there were no valid signatures, we return an error, using OCI 1.1+ behavior. func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") } // This is a carefully optimized sequence for fetching the signatures of the // entity that minimizes registry requests when supplied with a digest input digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) if err != nil { return nil, false, err } h, err := v1.NewHash(digest.Identifier()) if err != nil { return nil, false, err } var sigs oci.Signatures sigRef := co.SignatureRef if sigRef == "" { artifactType := ociexperimental.ArtifactType("sig") index, err := ociremote.Referrers(digest, artifactType, co.RegistryClientOpts...) if err != nil { return nil, false, err } results := index.Manifests numResults := len(results) if numResults == 0 { return nil, false, fmt.Errorf("unable to locate reference with artifactType %s", artifactType) } else if numResults > 1 { // TODO: if there is more than 1 result.. what does that even mean? ui.Warnf(ctx, "there were a total of %d references with artifactType %s\n", numResults, artifactType) } // TODO: do this smarter using "created" annotations lastResult := results[numResults-1] st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, lastResult.Digest.String())) if err != nil { return nil, false, err } sigs, err = ociremote.Signatures(st, co.RegistryClientOpts...) if err != nil { return nil, false, err } } else { if co.PayloadRef == "" { return nil, false, errors.New("payload is required with a manually-provided signature") } sigs, err = loadSignatureFromFile(ctx, sigRef, signedImgRef, co) if err != nil { return nil, false, err } } return verifySignatures(ctx, sigs, h, co) } func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) { // This is a carefully optimized sequence for fetching the signatures of the // entity that minimizes registry requests when supplied with a digest input digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) if err != nil { if terr := (&transport.Error{}); errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound { return nil, nil, &ErrImageTagNotFound{ fmt.Errorf("image tag not found: %w", err), } } return nil, nil, err } h, err := v1.NewHash(digest.Identifier()) if err != nil { return nil, nil, err } index, err := ociremote.Referrers(digest, "", co.RegistryClientOpts...) if err != nil { return nil, nil, err } var bundles = make([]*sgbundle.Bundle, 0, len(index.Manifests)) for _, result := range index.Manifests { st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, result.Digest.String())) if err != nil { return nil, nil, err } bundle, err := ociremote.Bundle(st, co.RegistryClientOpts...) if err != nil { // There may be non-Sigstore referrers in the index, so we can ignore them. // TODO: Should we surface any errors here (e.g. if the bundle is invalid)? continue } bundles = append(bundles, bundle) } if len(bundles) == 0 { return nil, nil, &ErrNoMatchingAttestations{ fmt.Errorf("no valid bundles exist in registry"), } } return bundles, &h, nil } // verifyImageAttestationsSigstoreBundle verifies attestations from attached sigstore bundles func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, atLeastOneBundleVerified bool, err error) { bundles, hash, err := getBundles(ctx, signedImgRef, co) if err != nil { return nil, false, err } digestBytes, err := hex.DecodeString(hash.Hex) if err != nil { return nil, false, err } artifactPolicyOption := verify.WithArtifactDigest(hash.Algorithm, digestBytes) attestations := make([]oci.Signature, len(bundles)) bundlesVerified := make([]bool, len(bundles)) workers := co.MaxWorkers if co.MaxWorkers == 0 { workers = cosign.DefaultMaxWorkers } t := throttler.New(workers, len(bundles)) for i, bundle := range bundles { go func(bundle *sgbundle.Bundle, index int) { var att oci.Signature if err := func(bundle *sgbundle.Bundle) error { _, err := VerifyNewBundle(ctx, co, artifactPolicyOption, bundle) if err != nil { return err } dsse, ok := bundle.Content.(*protobundle.Bundle_DsseEnvelope) if !ok { return fmt.Errorf("bundle does not contain a DSSE envelope") } payload, err := json.Marshal(dsse.DsseEnvelope) if err != nil { return fmt.Errorf("marshaling DSSE envelope: %w", err) } // We will return a slice of `[]oci.Signature` from this function for compatibility // with the rest of the codebase. To do that, we wrap the verification output in a // `oci.Signature` using static.NewAttestation(). This type may contain additional // data such as the certificate chain, and rekor/tsa data, but for now we only use // the payload (DSSE). TODO: Add additional data to returned `oci.Signature`. This // can be done by passing a list of static.Option to NewAttestation (e.g. static.WithCertChain()). // Depends on https://github.com/sigstore/sigstore-go/issues/328 att, err = static.NewAttestation(payload) if err != nil { return err } bundlesVerified[index] = true return err }(bundle); err != nil { t.Done(err) return } attestations[index] = att t.Done(nil) }(bundle, i) // wait till workers are available t.Throttle() } for _, a := range attestations { if a != nil { checkedAttestations = append(checkedAttestations, a) } } for _, verified := range bundlesVerified { atLeastOneBundleVerified = atLeastOneBundleVerified || verified } if len(checkedAttestations) == 0 { return nil, false, &ErrNoMatchingAttestations{ fmt.Errorf("no matching attestations: %w", errors.Join(t.Errs()...)), } } return checkedAttestations, atLeastOneBundleVerified, nil } cosign-2.5.0/pkg/cosign/verify_bundle.go000066400000000000000000000023621477503325500202450ustar00rootroot00000000000000// // Copyright 2025 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "github.com/sigstore/sigstore-go/pkg/verify" ) // VerifyNewBundle verifies a SigstoreBundle with the given parameters func VerifyNewBundle(_ context.Context, co *CheckOpts, artifactPolicyOption verify.ArtifactPolicyOption, bundle verify.SignedEntity) (*verify.VerificationResult, error) { trustedMaterial, verifierOptions, policyOptions, err := co.verificationOptions() if err != nil { return nil, err } verifier, err := verify.NewSignedEntityVerifier(trustedMaterial, verifierOptions...) if err != nil { return nil, err } return verifier.Verify(bundle, verify.NewPolicy(artifactPolicyOption, policyOptions...)) } cosign-2.5.0/pkg/cosign/verify_bundle_test.go000066400000000000000000000262501477503325500213060ustar00rootroot00000000000000// // Copyright 2025 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign_test import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "encoding/hex" "fmt" "testing" "github.com/sigstore/cosign/v2/pkg/cosign" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/testing/ca" "github.com/sigstore/sigstore-go/pkg/tlog" "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/signature" "github.com/stretchr/testify/assert" ) type bundleMutator struct { verify.SignedEntity eraseTSA bool eraseTlog bool } func (b *bundleMutator) Timestamps() ([][]byte, error) { if b.eraseTSA { return [][]byte{}, nil } return b.SignedEntity.Timestamps() } func (b *bundleMutator) TlogEntries() ([]*tlog.Entry, error) { if b.eraseTlog { return []*tlog.Entry{}, nil } return b.SignedEntity.TlogEntries() } func TestVerifyBundle(t *testing.T) { virtualSigstore, err := ca.NewVirtualSigstore() assert.NoError(t, err) virtualSigstore2, err := ca.NewVirtualSigstore() // for testing invalid trusted material assert.NoError(t, err) artifact := []byte("artifact") digest := sha256.Sum256(artifact) digestHex := hex.EncodeToString(digest[:]) statementFmt := `{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://example.com/predicateType","subject":[{"name":"subject","digest":{"sha256":"%s"}}],"predicate":{}}` statementCorrect := []byte(fmt.Sprintf(statementFmt, digestHex)) identity := "foo@example.com" issuer := "example issuer" standardIdentities := []cosign.Identity{ { Issuer: issuer, Subject: identity, }, } attestation, err := virtualSigstore.Attest(identity, issuer, statementCorrect) if err != nil { t.Fatal(err) } blobSig, err := virtualSigstore.Sign(identity, issuer, artifact) if err != nil { t.Fatal(err) } for _, tc := range []struct { name string checkOpts *cosign.CheckOpts artifactPolicyOption verify.ArtifactPolicyOption entity verify.SignedEntity wantErr bool }{ { name: "valid", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: attestation, wantErr: false, }, { name: "valid blob signature", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: blobSig, wantErr: false, }, { name: "invalid, wrong artifact", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader([]byte("not the artifact"))), entity: attestation, wantErr: true, }, { name: "invalid blob signature, wrong artifact", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader([]byte("not the artifact"))), entity: blobSig, wantErr: true, }, { name: "valid, pattern match issuer", checkOpts: &cosign.CheckOpts{ Identities: []cosign.Identity{ { IssuerRegExp: ".*issuer", Subject: "foo@example.com", }, }, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: attestation, wantErr: false, }, { name: "valid, pattern match subject", checkOpts: &cosign.CheckOpts{ Identities: []cosign.Identity{ { Issuer: "example issuer", SubjectRegExp: ".*@example.com", }, }, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: attestation, wantErr: false, }, { name: "invalid, pattern match issuer", checkOpts: &cosign.CheckOpts{ Identities: []cosign.Identity{ { IssuerRegExp: ".* not my issuer", Subject: "foo@example.com", }, }, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: attestation, wantErr: true, }, { name: "invalid, pattern match subject", checkOpts: &cosign.CheckOpts{ Identities: []cosign.Identity{ { Issuer: "example issuer", SubjectRegExp: ".*@otherexample.com", }, }, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: attestation, wantErr: true, }, { name: "invalid trusted material", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, TrustedMaterial: virtualSigstore2, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: attestation, wantErr: true, }, { name: "do not require tlog, missing tlog", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, IgnoreTlog: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: &bundleMutator{SignedEntity: attestation, eraseTlog: true}, wantErr: false, }, { name: "do not require tsa, missing tsa", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, IgnoreTlog: false, UseSignedTimestamps: false, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: &bundleMutator{SignedEntity: attestation, eraseTSA: true}, wantErr: false, }, { name: "require tlog, missing tlog", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: &bundleMutator{SignedEntity: attestation, eraseTlog: true}, wantErr: true, }, { name: "require tsa, missing tsa", checkOpts: &cosign.CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: &bundleMutator{SignedEntity: attestation, eraseTSA: true}, wantErr: true, }, } { t.Run(tc.name, func(t *testing.T) { _, err = cosign.VerifyNewBundle(context.Background(), tc.checkOpts, tc.artifactPolicyOption, tc.entity) if tc.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } func TestVerifyBundleWithSigVerifier(t *testing.T) { virtualSigstore, err := ca.NewVirtualSigstore() assert.NoError(t, err) artifact := []byte("artifact") digest := sha256.Sum256(artifact) privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.NoError(t, err) sv, err := signature.LoadECDSASignerVerifier(privKey, crypto.SHA256) assert.NoError(t, err) sig, err := sv.SignMessage(bytes.NewReader(artifact)) assert.NoError(t, err) assert.NotNil(t, sig) ts, err := virtualSigstore.TimestampResponse(sig) assert.NoError(t, err) b, err := sgbundle.NewBundle(&protobundle.Bundle{ MediaType: "application/vnd.dev.sigstore.bundle+json;version=0.3", VerificationMaterial: &protobundle.VerificationMaterial{ Content: &protobundle.VerificationMaterial_PublicKey{ PublicKey: &protocommon.PublicKeyIdentifier{ Hint: "", }, }, TimestampVerificationData: &protobundle.TimestampVerificationData{ Rfc3161Timestamps: []*protocommon.RFC3161SignedTimestamp{{SignedTimestamp: ts}}, }, }, Content: &protobundle.Bundle_MessageSignature{ MessageSignature: &protocommon.MessageSignature{ MessageDigest: &protocommon.HashOutput{ Algorithm: protocommon.HashAlgorithm_SHA2_256, Digest: digest[:], }, Signature: sig, }, }, }) assert.NoError(t, err) assert.NotNil(t, b) for _, tc := range []struct { name string checkOpts *cosign.CheckOpts artifactPolicyOption verify.ArtifactPolicyOption entity verify.SignedEntity wantErr bool }{ { name: "valid", checkOpts: &cosign.CheckOpts{ UseSignedTimestamps: true, IgnoreTlog: true, TrustedMaterial: virtualSigstore, SigVerifier: sv, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), entity: b, wantErr: false, }, { name: "invalid, wrong artifact", checkOpts: &cosign.CheckOpts{ UseSignedTimestamps: true, IgnoreTlog: true, TrustedMaterial: virtualSigstore, SigVerifier: sv, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader([]byte("wrong artifact"))), entity: b, wantErr: true, }, { name: "invalid, sigverifier not set", checkOpts: &cosign.CheckOpts{ UseSignedTimestamps: true, IgnoreTlog: true, TrustedMaterial: virtualSigstore, }, artifactPolicyOption: verify.WithArtifact(bytes.NewReader([]byte("wrong artifact"))), entity: b, wantErr: true, }, } { t.Run(tc.name, func(t *testing.T) { _, err = cosign.VerifyNewBundle(context.Background(), tc.checkOpts, tc.artifactPolicyOption, tc.entity) if tc.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } cosign-2.5.0/pkg/cosign/verify_oci_test.go000066400000000000000000000164401477503325500206070ustar00rootroot00000000000000// Copyright 2025 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" _ "embed" "fmt" "net/http/httptest" "net/url" "testing" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/registry" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" ) //go:embed testdata/oci-attestation.sigstore.json var testAttestation []byte //go:embed testdata/trusted_root_pgi.json var testTrustedRootPGI []byte func TestGetBundles_Empty(t *testing.T) { r := registry.New(registry.WithReferrersSupport(true)) s := httptest.NewServer(r) defer s.Close() u, err := url.Parse(s.URL) assert.NoError(t, err) ref, err := name.ParseReference(fmt.Sprintf("%s/repo:tag", u.Host)) assert.NoError(t, err) // If tag doesn't exist, should return ErrImageTagNotFound bundles, hash, err := getBundles(context.Background(), ref, &CheckOpts{}) imgTagNotFound := &ErrImageTagNotFound{} assert.ErrorAs(t, err, &imgTagNotFound) assert.Len(t, bundles, 0) assert.Nil(t, hash) // Write an image img, err := random.Image(10, 10) assert.NoError(t, err) assert.NoError(t, remote.Write(ref, img)) // Check that no matching attestation error is returned bundles, hash, err = getBundles(context.Background(), ref, &CheckOpts{}) var noMatchErr *ErrNoMatchingAttestations assert.ErrorAs(t, err, &noMatchErr) assert.Len(t, bundles, 0) assert.Nil(t, hash) // Get digest from tag desc, err := remote.Head(ref) assert.NoError(t, err) digestRef := ref.Context().Digest(desc.Digest.String()) // Write invalid attestation err = ociremote.WriteAttestationNewBundleFormat(digestRef, []byte("invalid"), "foo/bar") assert.NoError(t, err) // Should still return no matching attestation error, as it failed to parse the bundle bundles, hash, err = getBundles(context.Background(), ref, &CheckOpts{}) assert.ErrorAs(t, err, &noMatchErr) assert.Len(t, bundles, 0) assert.Nil(t, hash) } func TestGetBundles_Valid(t *testing.T) { r := registry.New(registry.WithReferrersSupport(true)) s := httptest.NewServer(r) defer s.Close() u, err := url.Parse(s.URL) assert.NoError(t, err) ref, err := name.ParseReference(fmt.Sprintf("%s/repo:tag", u.Host)) assert.NoError(t, err) // Test data uses empty image assert.NoError(t, remote.Write(ref, empty.Image)) // Get digest from tag desc, err := remote.Head(ref) assert.NoError(t, err) digestRef := ref.Context().Digest(desc.Digest.String()) // Write valid test attestation err = ociremote.WriteAttestationNewBundleFormat(digestRef, testAttestation, "https://cosign.sigstore.dev/attestation/v1") assert.NoError(t, err) // Retrieve the attestation bundles, hash, err := getBundles(context.Background(), ref, &CheckOpts{}) assert.NoError(t, err) assert.Len(t, bundles, 1) assert.NotNil(t, hash) // Compare the output to the test data expected := sgbundle.Bundle{} err = expected.UnmarshalJSON(testAttestation) assert.NoError(t, err) if !proto.Equal(bundles[0].Bundle, &expected) { t.Errorf("got %v, want %v", bundles[0].Bundle, &expected) } } // TODO: This test is getting long and maybe should be refactored into a // table-based test to exercise more permutations. func TestVerifyImageAttestationsSigstoreBundle(t *testing.T) { r := registry.New(registry.WithReferrersSupport(true)) s := httptest.NewServer(r) defer s.Close() u, err := url.Parse(s.URL) assert.NoError(t, err) ref, err := name.ParseReference(fmt.Sprintf("%s/repo:tag", u.Host)) assert.NoError(t, err) ref2, err := name.ParseReference(fmt.Sprintf("%s/repo:tag2", u.Host)) assert.NoError(t, err) // Parse test root trustedRoot, err := root.NewTrustedRootFromJSON(testTrustedRootPGI) assert.NoError(t, err) // Test data uses empty image assert.NoError(t, remote.Write(ref, empty.Image)) // Also write a second image to test that we only verify the correct image randomImage, err := random.Image(10, 10) assert.NoError(t, err) assert.NoError(t, remote.Write(ref2, randomImage)) // Attempt to verify non-existent attestation atts, bundleVerified, err := VerifyImageAttestations(context.Background(), ref, &CheckOpts{ TrustedMaterial: trustedRoot, NewBundleFormat: true, Identities: []Identity{ { IssuerRegExp: ".*", SubjectRegExp: ".*", }, }, }) var errNoMatchingAttestations *ErrNoMatchingAttestations assert.ErrorAs(t, err, &errNoMatchingAttestations) assert.False(t, bundleVerified) assert.Len(t, atts, 0) // Get digest from tag desc, err := remote.Head(ref) assert.NoError(t, err) digestRef := ref.Context().Digest(desc.Digest.String()) // Write valid test attestation err = ociremote.WriteAttestationNewBundleFormat(digestRef, testAttestation, "https://cosign.sigstore.dev/attestation/v1") assert.NoError(t, err) // Verify the attestation atts, bundleVerified, err = VerifyImageAttestations(context.Background(), ref, &CheckOpts{ TrustedMaterial: trustedRoot, NewBundleFormat: true, Identities: []Identity{ { IssuerRegExp: ".*", SubjectRegExp: ".*", }, }, }) assert.NoError(t, err) assert.True(t, bundleVerified) assert.Len(t, atts, 1) // Wrong identity should not verify atts, bundleVerified, err = VerifyImageAttestations(context.Background(), ref, &CheckOpts{ TrustedMaterial: trustedRoot, NewBundleFormat: true, Identities: []Identity{ { IssuerRegExp: ".*", SubjectRegExp: "wrong", }, }, }) assert.ErrorAs(t, err, &errNoMatchingAttestations) assert.False(t, bundleVerified) assert.Len(t, atts, 0) // Add same attestation to different image with different digest to test that we only verify the correct image // Get digest from tag desc, err = remote.Head(ref2) assert.NoError(t, err) digestRef = ref2.Context().Digest(desc.Digest.String()) // Write valid test attestation err = ociremote.WriteAttestationNewBundleFormat(digestRef, testAttestation, "https://cosign.sigstore.dev/attestation/v1") assert.NoError(t, err) // Verify the attestation atts, bundleVerified, err = VerifyImageAttestations(context.Background(), ref2, &CheckOpts{ TrustedMaterial: trustedRoot, NewBundleFormat: true, Identities: []Identity{ { IssuerRegExp: ".*", SubjectRegExp: ".*", }, }, }) assert.Error(t, err) assert.ErrorAs(t, err, &errNoMatchingAttestations) // TODO: This error message comes from sigstore-go. We may want to update the upstream with a sentinal error type. assert.ErrorContains(t, err, "provided artifact digest does not match any digest in statement") assert.False(t, bundleVerified) assert.Len(t, atts, 0) } cosign-2.5.0/pkg/cosign/verify_sct.go000066400000000000000000000117751477503325500175750ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "context" "crypto/x509" "encoding/hex" "encoding/json" "errors" "fmt" "os" ct "github.com/google/certificate-transparency-go" ctx509 "github.com/google/certificate-transparency-go/x509" "github.com/google/certificate-transparency-go/x509util" "github.com/sigstore/cosign/v2/pkg/cosign/fulcioverifier/ctutil" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/tuf" ) // ContainsSCT checks if the certificate contains embedded SCTs. cert can either be // DER or PEM encoded. func ContainsSCT(cert []byte) (bool, error) { embeddedSCTs, err := x509util.ParseSCTsFromCertificate(cert) if err != nil { return false, err } if len(embeddedSCTs) != 0 { return true, nil } return false, nil } func getCTPublicKey(sct *ct.SignedCertificateTimestamp, pubKeys *TrustedTransparencyLogPubKeys) (*TransparencyLogPubKey, error) { keyID := hex.EncodeToString(sct.LogID.KeyID[:]) pubKeyMetadata, ok := pubKeys.Keys[keyID] if !ok { return nil, errors.New("ctfe public key not found for payload. Check your TUF root (see cosign initialize) or set a custom key with env var SIGSTORE_CT_LOG_PUBLIC_KEY_FILE") } return &pubKeyMetadata, nil } // VerifySCT verifies SCTs against the Fulcio CT log public key. // // The SCT is a `Signed Certificate Timestamp`, which promises that // the certificate issued by Fulcio was also added to the public CT log within // some defined time period. // // VerifySCT can verify an SCT list embedded in the certificate, or a detached // SCT provided by Fulcio. // // Note that we can't pass in the CheckOpts here which has both RawSCT and // CTLogPubKeys due to import cycle, so they are pulled out from the struct // to arguments here. // // By default the public keys comes from TUF, but you can override this for test // purposes by using an env variable `SIGSTORE_CT_LOG_PUBLIC_KEY_FILE`. If using // an alternate, the file can be PEM, or DER format. func VerifySCT(_ context.Context, certPEM, chainPEM, rawSCT []byte, pubKeys *TrustedTransparencyLogPubKeys) error { if pubKeys == nil || len(pubKeys.Keys) == 0 { return errors.New("none of the CTFE keys have been found") } // parse certificate and chain cert, err := x509util.CertificateFromPEM(certPEM) if err != nil { return err } certChain, err := x509util.CertificatesFromPEM(chainPEM) if err != nil { return err } if len(certChain) == 0 { return errors.New("no certificate chain found") } // fetch embedded SCT if present embeddedSCTs, err := x509util.ParseSCTsFromCertificate(certPEM) if err != nil { return err } // SCT must be either embedded or in header if len(embeddedSCTs) == 0 && len(rawSCT) == 0 { return errors.New("no SCT found") } // check SCT embedded in certificate if len(embeddedSCTs) != 0 { for _, sct := range embeddedSCTs { pubKeyMetadata, err := getCTPublicKey(sct, pubKeys) if err != nil { return err } err = ctutil.VerifySCT(pubKeyMetadata.PubKey, []*ctx509.Certificate{cert, certChain[0]}, sct, true) if err != nil { return fmt.Errorf("error verifying embedded SCT: %w", err) } if pubKeyMetadata.Status != tuf.Active { fmt.Fprintf(os.Stderr, "**Info** Successfully verified embedded SCT using an expired verification key\n") } } return nil } // check SCT in response header var addChainResp ct.AddChainResponse if err := json.Unmarshal(rawSCT, &addChainResp); err != nil { return fmt.Errorf("unmarshal") } sct, err := addChainResp.ToSignedCertificateTimestamp() if err != nil { return err } pubKeyMetadata, err := getCTPublicKey(sct, pubKeys) if err != nil { return err } err = ctutil.VerifySCT(pubKeyMetadata.PubKey, []*ctx509.Certificate{cert}, sct, false) if err != nil { return fmt.Errorf("error verifying SCT") } if pubKeyMetadata.Status != tuf.Active { fmt.Fprintf(os.Stderr, "**Info** Successfully verified SCT using an expired verification key\n") } return nil } // VerifyEmbeddedSCT verifies an embedded SCT in a certificate. func VerifyEmbeddedSCT(ctx context.Context, chain []*x509.Certificate, pubKeys *TrustedTransparencyLogPubKeys) error { if len(chain) < 2 { return errors.New("certificate chain must contain at least a certificate and its issuer") } certPEM, err := cryptoutils.MarshalCertificateToPEM(chain[0]) if err != nil { return err } chainPEM, err := cryptoutils.MarshalCertificatesToPEM(chain[1:]) if err != nil { return err } return VerifySCT(ctx, certPEM, chainPEM, []byte{}, pubKeys) } cosign-2.5.0/pkg/cosign/verify_sct_test.go000066400000000000000000000244721477503325500206320ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build sct // +build sct package cosign import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/base64" "encoding/json" "fmt" "os" "strings" "testing" ct "github.com/google/certificate-transparency-go" "github.com/google/certificate-transparency-go/testdata" "github.com/google/certificate-transparency-go/tls" "github.com/sigstore/sigstore/pkg/cryptoutils" ) // TODO: Move back into verify_test.go once the test cert has been regenerated func TestValidateAndUnpackCertWithSCT(t *testing.T) { chain, err := cryptoutils.UnmarshalCertificatesFromPEM([]byte(testdata.TestEmbeddedCertPEM + testdata.CACertPEM)) if err != nil { t.Fatalf("error unmarshalling certificate chain: %v", err) } rootPool := x509.NewCertPool() rootPool.AddCert(chain[1]) // Grab the CTLog public keys pubKeys, err := GetCTLogPubs(context.Background()) if err != nil { t.Fatalf("Failed to get CTLog public keys from TUF: %v", err) } co := &CheckOpts{ RootCerts: rootPool, // explicitly set to false IgnoreSCT: false, CTLogPubKeys: pubKeys, } // write SCT verification key to disk tmpPrivFile, err := os.CreateTemp(t.TempDir(), "cosign_verify_sct_*.key") if err != nil { t.Fatalf("failed to create temp key file: %v", err) } defer tmpPrivFile.Close() if _, err := tmpPrivFile.Write([]byte(testdata.LogPublicKeyPEM)); err != nil { t.Fatalf("failed to write key file: %v", err) } t.Setenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", tmpPrivFile.Name()) // Grab the CTLog public keys again so we get them from env. co.CTLogPubKeys, err = GetCTLogPubs(context.Background()) if err != nil { t.Fatalf("Failed to get CTLog public keys from TUF: %v", err) } _, err = ValidateAndUnpackCert(chain[0], co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } // validate again, explicitly setting ignore SCT to false co.IgnoreSCT = false _, err = ValidateAndUnpackCert(chain[0], co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } } func TestValidateAndUnpackCertWithDetachedSCT(t *testing.T) { chain, err := cryptoutils.UnmarshalCertificatesFromPEM([]byte(testdata.TestCertPEM + testdata.CACertPEM)) if err != nil { t.Fatalf("error unmarshalling certificate chain: %v", err) } rootPool := x509.NewCertPool() rootPool.AddCert(chain[1]) co := &CheckOpts{ RootCerts: rootPool, // explicitly set to false IgnoreSCT: false, } // write SCT verification key to disk tmpPrivFile, err := os.CreateTemp(t.TempDir(), "cosign_verify_sct_*.key") if err != nil { t.Fatalf("failed to create temp key file: %v", err) } defer tmpPrivFile.Close() if _, err := tmpPrivFile.Write([]byte(testdata.LogPublicKeyPEM)); err != nil { t.Fatalf("failed to write key file: %v", err) } t.Setenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", tmpPrivFile.Name()) // Grab the CTLog public keys so we get them from env. co.CTLogPubKeys, err = GetCTLogPubs(context.Background()) if err != nil { t.Fatalf("Failed to get CTLog public keys from TUF: %v", err) } // Fulcio quirk, since it returns a different SCT structure var sct ct.SignedCertificateTimestamp if _, err := tls.Unmarshal(testdata.TestCertProof, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } chainResp, err := toAddChainResponse(&sct) if err != nil { t.Fatalf("error generating chain response: %v", err) } sctBytes, err := json.Marshal(chainResp) if err != nil { t.Fatalf("error marshalling chain: %v", err) } co.SCT = sctBytes _, err = ValidateAndUnpackCert(chain[0], co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } // validate again, explicitly setting ignore SCT to false co.IgnoreSCT = false _, err = ValidateAndUnpackCert(chain[0], co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } } // toAddChainResponse converts an SCT to a response struct, the expected structure for detached SCTs func toAddChainResponse(sct *ct.SignedCertificateTimestamp) (*ct.AddChainResponse, error) { sig, err := tls.Marshal(sct.Signature) if err != nil { return nil, fmt.Errorf("failed to marshal signature: %w", err) } addChainResp := &ct.AddChainResponse{ SCTVersion: sct.SCTVersion, Timestamp: sct.Timestamp, Extensions: base64.StdEncoding.EncodeToString(sct.Extensions), ID: sct.LogID.KeyID[:], Signature: sig, } return addChainResp, nil } func TestContainsSCT(t *testing.T) { // test certificate without embedded SCT contains, err := ContainsSCT([]byte(testdata.TestCertPEM)) if err != nil { t.Fatalf("unexpected error in ContainsSCT: %v", err) } if contains { t.Fatalf("certificate unexpectedly contained SCT") } // test certificate with embedded SCT contains, err = ContainsSCT([]byte(testdata.TestEmbeddedCertPEM)) if err != nil { t.Fatalf("unexpected error in ContainsSCT: %v", err) } if !contains { t.Fatalf("certificate unexpectedly did not contain SCT") } } // From https://github.com/google/certificate-transparency-go/blob/e76f3f637053b90c8168d29b01ca162cd235ace5/ctutil/ctutil_test.go func TestVerifySCT(t *testing.T) { tests := []struct { desc string certPEM string chainPEM string sct []byte embedded bool wantErr bool errMsg string }{ { desc: "cert", certPEM: testdata.TestCertPEM, chainPEM: testdata.CACertPEM, sct: testdata.TestCertProof, }, { desc: "invalid SCT", certPEM: testdata.TestPreCertPEM, chainPEM: testdata.CACertPEM, sct: testdata.TestCertProof, wantErr: true, }, { desc: "cert with embedded SCT", certPEM: testdata.TestEmbeddedCertPEM, chainPEM: testdata.CACertPEM, sct: testdata.TestPreCertProof, embedded: true, }, { desc: "cert with invalid embedded SCT", certPEM: testdata.TestInvalidEmbeddedCertPEM, chainPEM: testdata.CACertPEM, sct: testdata.TestInvalidProof, embedded: true, wantErr: true, errMsg: "failed to verify ECDSA signature", }, } writePubKey(t, testdata.LogPublicKeyPEM) for _, test := range tests { t.Run(test.desc, func(t *testing.T) { // convert SCT to response struct if detached var sctBytes []byte if !test.embedded { var sct ct.SignedCertificateTimestamp if _, err := tls.Unmarshal(test.sct, &sct); err != nil { t.Fatalf("error tls-unmarshalling sct: %s", err) } chainResp, err := toAddChainResponse(&sct) if err != nil { t.Fatalf("error generating chain response: %v", err) } sctBytes, err = json.Marshal(chainResp) if err != nil { t.Fatalf("error marshalling chain: %v", err) } } err := VerifySCT(context.Background(), []byte(test.certPEM), []byte(test.chainPEM), sctBytes, nil) if gotErr := err != nil; gotErr != test.wantErr && !strings.Contains(err.Error(), test.errMsg) { t.Errorf("VerifySCT(_,_,_, %t) = %v, want error? %t", test.embedded, err, test.wantErr) } }) } } func TestVerifySCTError(t *testing.T) { // verify fails with mismatched verifcation key key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("unexpected error generating ECDSA key: %v", err) } pemKey, err := cryptoutils.MarshalPublicKeyToPEM(key.Public()) if err != nil { t.Fatalf("unexpected error marshalling ECDSA key: %v", err) } writePubKey(t, string(pemKey)) // Grab the keys from TUF pubKeys, err := GetCTLogPubs(context.Background()) if err != nil { t.Fatalf("Failed to get CTLog public keys from TUF: %v", err) } err = VerifySCT(context.Background(), []byte(testdata.TestEmbeddedCertPEM), []byte(testdata.CACertPEM), []byte{}, pubKeys) if err == nil || !strings.Contains(err.Error(), "ctfe public key not found") { t.Fatalf("expected error verifying SCT with mismatched key: %v", err) } // verify fails without either a detached SCT or embedded SCT err = VerifySCT(context.Background(), []byte(testdata.TestCertPEM), []byte(testdata.CACertPEM), []byte{}, pubKeys) if err == nil || !strings.Contains(err.Error(), "no SCT found") { t.Fatalf("expected error verifying SCT without SCT: %v", err) } } func TestVerifyEmbeddedSCT(t *testing.T) { chain, err := cryptoutils.UnmarshalCertificatesFromPEM([]byte(testdata.TestEmbeddedCertPEM + testdata.CACertPEM)) if err != nil { t.Fatalf("error unmarshalling certificate chain: %v", err) } // Grab the keys from TUF pubKeys, err := GetCTLogPubs(context.Background()) if err != nil { t.Fatalf("Failed to get CTLog public keys from TUF: %v", err) } // verify fails without a certificate chain err = VerifyEmbeddedSCT(context.Background(), chain[:1], pubKeys) if err == nil || err.Error() != "certificate chain must contain at least a certificate and its issuer" { t.Fatalf("expected error verifying SCT without chain: %v", err) } writePubKey(t, testdata.LogPublicKeyPEM) // Above writes the key to disk and sets up an env variable, so grab the // public keys again to get the env path. pubKeys, err = GetCTLogPubs(context.Background()) if err != nil { t.Fatalf("Failed to get CTLog public keys from TUF: %v", err) } err = VerifyEmbeddedSCT(context.Background(), chain, pubKeys) if err != nil { t.Fatalf("unexpected error verifying embedded SCT: %v", err) } } // writePubKey writes the SCT verification key to disk, since there is not a TUF // test setup func writePubKey(t *testing.T, keyPEM string) { t.Helper() tmpPrivFile, err := os.CreateTemp(t.TempDir(), "cosign_verify_sct_*.key") if err != nil { t.Fatalf("failed to create temp key file: %v", err) } t.Cleanup(func() { tmpPrivFile.Close() }) if _, err := tmpPrivFile.Write([]byte(keyPEM)); err != nil { t.Fatalf("failed to write key file: %v", err) } os.Setenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", tmpPrivFile.Name()) t.Cleanup(func() { os.Unsetenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE") }) } cosign-2.5.0/pkg/cosign/verify_test.go000066400000000000000000001661111477503325500177560ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cosign import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "encoding/hex" "encoding/json" "encoding/pem" "errors" "io" "net" "net/url" "strings" "testing" "time" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/in-toto/in-toto-golang/in_toto" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload" "github.com/sigstore/cosign/v2/internal/pkg/cosign/rekor/mock" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" tsaMock "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/mock" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/cosign/v2/pkg/types" "github.com/sigstore/cosign/v2/test" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" rtypes "github.com/sigstore/rekor/pkg/types" hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/options" "github.com/sigstore/sigstore/pkg/tuf" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/transparency-dev/merkle/rfc6962" ) type mockVerifier struct { shouldErr bool } func (m *mockVerifier) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) { //nolint: revive return nil, nil } func (m *mockVerifier) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error { //nolint: revive if m.shouldErr { return errors.New("failure") } return nil } var _ signature.Verifier = (*mockVerifier)(nil) type mockAttestation struct { payload interface{} } var _ payloader = (*mockAttestation)(nil) func (m *mockAttestation) Annotations() (map[string]string, error) { return nil, nil } func (m *mockAttestation) Payload() ([]byte, error) { return json.Marshal(m.payload) } func (m *mockAttestation) Base64Signature() (string, error) { b, err := json.Marshal(m.payload) return string(b), err } func appendSlices(slices [][]byte) []byte { var tmp []byte for _, s := range slices { tmp = append(tmp, s...) } return tmp } func Test_verifyOCIAttestation(t *testing.T) { stmt, err := json.Marshal(in_toto.ProvenanceStatementSLSA02{}) if err != nil { t.Fatal(err) } valid := map[string]interface{}{ "payloadType": types.IntotoPayloadType, "payload": stmt, "signatures": []dsse.Signature{{Sig: base64.StdEncoding.EncodeToString([]byte("foobar"))}}, } // Should Verify if err := verifyOCIAttestation(context.TODO(), &mockVerifier{}, &mockAttestation{payload: valid}); err != nil { t.Errorf("verifyOCIAttestation() error = %v", err) } invalid := map[string]interface{}{ "payloadType": "not valid type", "payload": stmt, "signatures": []dsse.Signature{{Sig: base64.StdEncoding.EncodeToString([]byte("foobar"))}}, } // Should Not Verify if err := verifyOCIAttestation(context.TODO(), &mockVerifier{}, &mockAttestation{payload: invalid}); err == nil { t.Error("verifyOCIAttestation() expected invalid payload type error, got nil") } if err := verifyOCIAttestation(context.TODO(), &mockVerifier{shouldErr: true}, &mockAttestation{payload: valid}); err == nil { t.Error("verifyOCIAttestation() expected invalid payload type error, got nil") } } func TestVerifyImageSignature(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub, pemRoot}))) verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, IgnoreTlog: true, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}}) if err != nil { t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) } // TODO: Create fake bundle and test verification if verified == true { t.Fatalf("expected verified=false, got verified=true") } } func TestVerifyImageSignatureMultipleSubs(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert1, subKey1, _ := test.GenerateSubordinateCa(rootCert, rootKey) subCert2, subKey2, _ := test.GenerateSubordinateCa(subCert1, subKey1) subCert3, subKey3, _ := test.GenerateSubordinateCa(subCert2, subKey2) leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert3, subKey3) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub1 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert1.Raw}) pemSub2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert2.Raw}) pemSub3 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert3.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub3, pemSub2, pemSub1, pemRoot}))) verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, IgnoreTlog: true, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}}) if err != nil { t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) } // TODO: Create fake bundle and test verification if verified == true { t.Fatalf("expected verified=false, got verified=true") } } func signEntry(ctx context.Context, t *testing.T, signer signature.Signer, entry bundle.RekorPayload) []byte { payload, err := json.Marshal(entry) if err != nil { t.Fatalf("marshalling error: %v", err) } canonicalized, err := jsoncanonicalizer.Transform(payload) if err != nil { t.Fatalf("canonicalizing error: %v", err) } signature, err := signer.SignMessage(bytes.NewReader(canonicalized), options.WithContext(ctx)) if err != nil { t.Fatalf("signing error: %v", err) } return signature } func CreateTestBundle(ctx context.Context, t *testing.T, rekor signature.Signer, leaf []byte) *bundle.RekorBundle { // generate log ID according to rekor public key pk, _ := rekor.PublicKey(nil) keyID, _ := GetTransparencyLogID(pk) pyld := bundle.RekorPayload{ Body: base64.StdEncoding.EncodeToString(leaf), IntegratedTime: time.Now().Unix(), LogIndex: 693591, LogID: keyID, } // Sign with root. signature := signEntry(ctx, t, rekor, pyld) b := &bundle.RekorBundle{ SignedEntryTimestamp: strfmt.Base64(signature), Payload: pyld, } return b } func Test_verifySignaturesErrNoSignaturesFound(t *testing.T) { _, _, err := verifySignatures(context.Background(), &fakeOCISignatures{}, v1.Hash{}, nil) var e *ErrNoSignaturesFound if !errors.As(err, &e) { t.Fatalf("%T{%q} is not a %T", err, err, &ErrNoSignaturesFound{}) } } func Test_verifySignaturesErrNoMatchingSignatures(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub, pemRoot}))) _, _, err := verifySignatures(context.Background(), &fakeOCISignatures{signatures: []oci.Signature{ociSig}}, v1.Hash{}, &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, IgnoreTlog: true, Identities: []Identity{{Subject: "another-subject@mail.com", Issuer: "oidc-issuer"}}}) var e *ErrNoMatchingSignatures if !errors.As(err, &e) { t.Fatalf("%T{%q} is not a %T", err, err, &ErrNoMatchingSignatures{}) } } func TestVerifyImageSignatureWithNoChain(t *testing.T) { ctx := context.Background() rootCert, rootKey, _ := test.GenerateRootCa() sv, _, err := signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256) if err != nil { t.Fatalf("creating signer: %v", err) } leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) // Create a fake bundle pe, _ := proposedEntries(base64.StdEncoding.EncodeToString(signature), payload, pemLeaf) entry, _ := rtypes.UnmarshalEntry(pe[0]) leaf, _ := entry.Canonicalize(ctx) rekorBundle := CreateTestBundle(ctx, t, sv, leaf) pemBytes, _ := cryptoutils.MarshalPublicKeyToPEM(sv.Public()) rekorPubKeys := NewTrustedTransparencyLogPubKeys() rekorPubKeys.AddTransparencyLogPubKey(pemBytes, tuf.Active) opts := []static.Option{static.WithCertChain(pemLeaf, []byte{}), static.WithBundle(rekorBundle)} ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), opts...) verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, RekorPubKeys: &rekorPubKeys}) if err != nil { t.Fatalf("unexpected error %v", err) } if verified == false { t.Fatalf("expected verified=true, got verified=false") } } func TestVerifyImageSignatureWithInvalidPublicKeyType(t *testing.T) { ctx := context.Background() rootCert, rootKey, _ := test.GenerateRootCa() sv, _, err := signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256) if err != nil { t.Fatalf("creating signer: %v", err) } leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) // Create a fake bundle pe, _ := proposedEntries(base64.StdEncoding.EncodeToString(signature), payload, pemLeaf) entry, _ := rtypes.UnmarshalEntry(pe[0]) leaf, _ := entry.Canonicalize(ctx) rekorBundle := CreateTestBundle(ctx, t, sv, leaf) pemBytes, _ := cryptoutils.MarshalPublicKeyToPEM(sv.Public()) rekorPubKeys := NewTrustedTransparencyLogPubKeys() // Add one valid key here. rekorPubKeys.AddTransparencyLogPubKey(pemBytes, tuf.Active) opts := []static.Option{static.WithCertChain(pemLeaf, []byte{}), static.WithBundle(rekorBundle)} ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), opts...) // Then try to validate with keys that are not ecdsa.PublicKey and should // fail. var rsaPrivKey crypto.PrivateKey rsaPrivKey, err = rsa.GenerateKey(rand.Reader, 4096) if err != nil { t.Fatalf("Unable to create RSA test key: %v", err) } var signer crypto.Signer var ok bool if signer, ok = rsaPrivKey.(crypto.Signer); !ok { t.Fatalf("Unable to create signer out of RSA test key: %v", err) } rsaPEM, err := cryptoutils.MarshalPublicKeyToPEM(signer.Public()) if err != nil { t.Fatalf("Unable to marshal RSA test key: %v", err) } if err = rekorPubKeys.AddTransparencyLogPubKey(rsaPEM, tuf.Active); err != nil { t.Fatalf("failed to add RSA key to transparency log public keys: %v", err) } verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, RekorPubKeys: &rekorPubKeys}) if err == nil { t.Fatal("expected error got none") } if !strings.Contains(err.Error(), "is not type ecdsa.PublicKey") { t.Errorf("did not get expected failure message, wanted 'is not type ecdsa.PublicKey' got: %v", err) } if verified == true { t.Fatalf("expected verified=false, got verified=true") } } func TestVerifyImageSignatureWithOnlyRoot(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, pemRoot)) verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, IgnoreTlog: true}) if err != nil { t.Fatalf("unexpected error while verifying signature, expected no error, got %v", err) } // TODO: Create fake bundle and test verification if verified == true { t.Fatalf("expected verified=false, got verified=true") } } func TestVerifyImageSignatureWithMissingSub(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, pemRoot)) verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, IgnoreTlog: true}) if err == nil { t.Fatal("expected error while verifying signature") } if !strings.Contains(err.Error(), "certificate signed by unknown authority") { t.Fatal("expected error while verifying signature") } // TODO: Create fake bundle and test verification if verified == true { t.Fatalf("expected verified=false, got verified=true") } } func TestVerifyImageSignatureWithExistingSub(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) otherSubCert, _, _ := test.GenerateSubordinateCa(rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) subPool := x509.NewCertPool() // Load in different sub cert so the chain doesn't verify rootPool.AddCert(otherSubCert) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub, pemRoot}))) verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, &CheckOpts{ RootCerts: rootPool, IntermediateCerts: subPool, IgnoreSCT: true, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, IgnoreTlog: true}) if err == nil { t.Fatal("expected error while verifying signature") } if !strings.Contains(err.Error(), "certificate signed by unknown authority") { t.Fatal("expected error while verifying signature") } // TODO: Create fake bundle and test verification if verified == true { t.Fatalf("expected verified=false, got verified=true") } } var ( lea = models.LogEntryAnon{ Attestation: &models.LogEntryAnonAttestation{}, Body: base64.StdEncoding.EncodeToString([]byte("asdf")), IntegratedTime: new(int64), LogID: new(string), LogIndex: new(int64), Verification: &models.LogEntryAnonVerification{ InclusionProof: &models.InclusionProof{ RootHash: new(string), TreeSize: new(int64), LogIndex: new(int64), }, }, } data = models.LogEntry{ uuid(lea): lea, } ) // uuid generates the UUID for the given LogEntry. // This is effectively a reimplementation of // pkg/cosign/tlog.go -> verifyUUID / ComputeLeafHash, but separated // to avoid a circular dependency. // TODO?: Perhaps we should refactor the tlog libraries into a separate // package? func uuid(e models.LogEntryAnon) string { entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string)) if err != nil { panic(err) } return hex.EncodeToString(rfc6962.DefaultHasher.HashLeaf(entryBytes)) } func TestImageSignatureVerificationWithRekor(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Generate ECDSA signer and public key for signing the blob. signer, publicKey := generateSigner(t) blob, blobSignature, blobSignatureBase64 := generateBlobSignature(t, signer) // Create an OCI signature which will be verified. ociSignature, err := static.NewSignature(blob, blobSignatureBase64) require.NoError(t, err, "error creating OCI signature") // Set up mock Rekor signer and log ID. rekorSigner, rekorPublicKey := generateSigner(t) logID := calculateLogID(t, rekorPublicKey) // Create a mock Rekor log entry to simulate Rekor behavior. rekorEntry := createRekorEntry(ctx, t, logID, rekorSigner, blob, blobSignature, publicKey) // Mock Rekor client to return the mock log entry for verification. mockClient := &client.Rekor{ Entries: &mockEntriesClient{ searchLogQueryFunc: func(_ *entries.SearchLogQueryParams, _ ...entries.ClientOption) (*entries.SearchLogQueryOK, error) { return &entries.SearchLogQueryOK{ Payload: []models.LogEntry{*rekorEntry}, }, nil }, }, } // Define trusted Rekor public keys for verification. trustedRekorPubKeys := &TrustedTransparencyLogPubKeys{ Keys: map[string]TransparencyLogPubKey{ logID: { PubKey: rekorPublicKey, Status: tuf.Active, }, }, } // Generate non-matching public key for failure test cases. _, nonMatchingPublicKey := generateSigner(t) nonMatchingRekorPubKeys := &TrustedTransparencyLogPubKeys{ Keys: map[string]TransparencyLogPubKey{ logID: { PubKey: nonMatchingPublicKey, Status: tuf.Active, }, }, } tests := []struct { name string checkOpts CheckOpts rekorClient *client.Rekor expectError bool errorMsg string }{ { name: "Verification succeeds with valid Rekor public keys", checkOpts: CheckOpts{ SigVerifier: signer, RekorClient: mockClient, RekorPubKeys: trustedRekorPubKeys, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, }, rekorClient: mockClient, expectError: false, }, { name: "Verification fails with no Rekor public keys", checkOpts: CheckOpts{ SigVerifier: signer, RekorClient: mockClient, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, }, rekorClient: mockClient, expectError: true, errorMsg: "no valid tlog entries found no trusted rekor public keys provided", }, { name: "Verification fails with non-matching Rekor public keys", checkOpts: CheckOpts{ SigVerifier: signer, RekorClient: mockClient, RekorPubKeys: nonMatchingRekorPubKeys, Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, }, rekorClient: mockClient, expectError: true, errorMsg: "verifying signedEntryTimestamp: unable to verify SET", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { bundleVerified, err := VerifyImageSignature(ctx, ociSignature, v1.Hash{}, &tt.checkOpts) if tt.expectError { assert.Error(t, err) assert.Contains(t, err.Error(), tt.errorMsg) } else { assert.NoError(t, err) assert.True(t, bundleVerified, "bundle verification failed") } }) } } func TestVerifyImageSignatureWithSigVerifierAndTSA(t *testing.T) { client, err := tsaMock.NewTSAClient((tsaMock.TSAClientOptions{Time: time.Now()})) if err != nil { t.Fatal(err) } sv, _, err := signature.NewDefaultECDSASignerVerifier() if err != nil { t.Fatalf("error generating verifier: %v", err) } payloadSigner := payload.NewSigner(sv) testSigner := tsa.NewSigner(payloadSigner, client) certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(client.CertChain) if err != nil { t.Fatalf("unexpected error marshalling cert chain: %v", err) } leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain(certChainPEM) if err != nil { t.Fatal("error splitting response into certificate chain") } payload := []byte{1, 2, 3, 4} sig, _, err := testSigner.Sign(context.Background(), bytes.NewReader(payload)) if err != nil { t.Fatalf("error signing the payload with the tsa client server: %v", err) } if bundleVerified, err := VerifyImageSignature(context.TODO(), sig, v1.Hash{}, &CheckOpts{ SigVerifier: sv, TSACertificate: leaves[0], TSAIntermediateCertificates: intermediates, TSARootCertificates: roots, IgnoreTlog: true, }); err != nil || bundleVerified { // bundle is not verified since there's no Rekor bundle t.Fatalf("unexpected error while verifying signature, got %v", err) } } func TestVerifyImageSignatureWithSigVerifierAndRekorTSA(t *testing.T) { // Add a fake rekor client - this makes it look like there's a matching // tlog entry for the signature during validation (even though it does not // match the underlying data / key) mClient := new(client.Rekor) mClient.Entries = &mock.EntriesClient{ Entries: []*models.LogEntry{&data}, } client, err := tsaMock.NewTSAClient((tsaMock.TSAClientOptions{Time: time.Now()})) if err != nil { t.Fatal(err) } sv, _, err := signature.NewDefaultECDSASignerVerifier() if err != nil { t.Fatalf("error generating verifier: %v", err) } payloadSigner := payload.NewSigner(sv) tsaSigner := tsa.NewSigner(payloadSigner, client) certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(client.CertChain) if err != nil { t.Fatalf("unexpected error marshalling cert chain: %v", err) } leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain(certChainPEM) if err != nil { t.Fatal("error splitting response into certificate chain") } payload := []byte{1, 2, 3, 4} sig, _, err := tsaSigner.Sign(context.Background(), bytes.NewReader(payload)) if err != nil { t.Fatalf("error signing the payload with the rekor and tsa clients: %v", err) } if _, err := VerifyImageSignature(context.TODO(), sig, v1.Hash{}, &CheckOpts{ SigVerifier: sv, TSACertificate: leaves[0], TSAIntermediateCertificates: intermediates, TSARootCertificates: roots, RekorClient: mClient, }); err == nil || !strings.Contains(err.Error(), "no trusted rekor public keys provided") { // TODO(wlynch): This is a weak test, since this is really failing because // there is no inclusion proof for the Rekor entry rather than failing to // validate the Rekor public key itself. At the very least this ensures // that we're hitting tlog validation during signature checking, // but we should look into improving this once there is an in-memory // Rekor client that is capable of performing inclusion proof validation // in unit tests. t.Fatalf("expected error while verifying signature, got %s", err) } } func TestValidateAndUnpackCertSuccess(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, } _, err := ValidateAndUnpackCert(leafCert, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) } } func TestValidateAndUnpackCertSuccessAllowAllValues(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, } _, err := ValidateAndUnpackCert(leafCert, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) } } func TestValidateAndUnpackCertWithoutRequiredSCT(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, // explicitly set to false IgnoreSCT: false, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "certificate does not include required embedded SCT") } func TestValidateAndUnpackCertSuccessWithDnsSan(t *testing.T) { subject := "example.com" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithSubjectAlternateNames( []string{subject}, /* dnsNames */ nil, /* emailAddresses */ nil, /* ipAddresses */ nil, /* uris */ oidcIssuer, /* oidcIssuer */ rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) } } func TestValidateAndUnpackCertSuccessWithEmailSan(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithSubjectAlternateNames( nil, /* dnsNames */ []string{subject}, /* emailAddresses */ nil, /* ipAddresses */ nil, /* uris */ oidcIssuer, /* oidcIssuer */ rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) } } func TestValidateAndUnpackCertSuccessWithIpAddressSan(t *testing.T) { subject := "127.0.0.1" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithSubjectAlternateNames( nil, /* dnsNames */ nil, /* emailAddresses */ []net.IP{net.ParseIP(subject)}, /* ipAddresses */ nil, /* uris */ oidcIssuer, /* oidcIssuer */ rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) } } func TestValidateAndUnpackCertSuccessWithUriSan(t *testing.T) { subject, _ := url.Parse("scheme://userinfo@host") oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithSubjectAlternateNames( nil, /* dnsNames */ nil, /* emailAddresses */ nil, /* ipAddresses */ []*url.URL{subject}, /* uris */ oidcIssuer, /* oidcIssuer */ rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: "scheme://userinfo@host", Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) } } func TestValidateAndUnpackCertSuccessWithOtherNameSan(t *testing.T) { // generate with OtherName, which will override other SANs subject := "subject-othername" ext, err := cryptoutils.MarshalOtherNameSAN(subject, true) if err != nil { t.Fatalf("error marshalling SANs: %v", err) } exts := []pkix.Extension{*ext} oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert("unused", oidcIssuer, rootCert, rootKey, exts...) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err = ValidateAndUnpackCert(leafCert, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) } } func TestValidateAndUnpackCertInvalidRoot(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) otherRoot, _, _ := test.GenerateRootCa() rootPool := x509.NewCertPool() rootPool.AddCert(otherRoot) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "certificate signed by unknown authority") } func TestValidateAndUnpackCertInvalidOidcIssuer(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: "other"}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "none of the expected identities matched what was in the certificate") err = CheckCertificatePolicy(leafCert, co) require.Contains(t, err.Error(), "none of the expected identities matched what was in the certificate") } func TestValidateAndUnpackCertInvalidEmail(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: "other", Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "none of the expected identities matched what was in the certificate") err = CheckCertificatePolicy(leafCert, co) require.Contains(t, err.Error(), "none of the expected identities matched what was in the certificate") } func TestValidateAndUnpackCertInvalidGithubWorkflowTrigger(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" githubWorkFlowTrigger := "myTrigger" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithGitHubOIDs(subject, oidcIssuer, githubWorkFlowTrigger, "", "", "", "", rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowTrigger: "otherTrigger", IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow Trigger not found in certificate") err = CheckCertificatePolicy(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow Trigger not found in certificate") } func TestValidateAndUnpackCertInvalidGithubWorkflowSHA(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" githubWorkFlowSha := "mySHA" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithGitHubOIDs(subject, oidcIssuer, "", githubWorkFlowSha, "", "", "", rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowSha: "otherSHA", IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow SHA not found in certificate") err = CheckCertificatePolicy(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow SHA not found in certificate") } func TestValidateAndUnpackCertInvalidGithubWorkflowName(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" githubWorkFlowName := "myName" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithGitHubOIDs(subject, oidcIssuer, "", "", githubWorkFlowName, "", "", rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowName: "otherName", IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow Name not found in certificate") err = CheckCertificatePolicy(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow Name not found in certificate") } func TestValidateAndUnpackCertInvalidGithubWorkflowRepository(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" githubWorkFlowRepository := "myRepository" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithGitHubOIDs(subject, oidcIssuer, "", "", "", githubWorkFlowRepository, "", rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowRepository: "otherRepository", IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow Repository not found in certificate") err = CheckCertificatePolicy(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow Repository not found in certificate") } func TestValidateAndUnpackCertInvalidGithubWorkflowRef(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" githubWorkFlowRef := "myRef" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCertWithGitHubOIDs(subject, oidcIssuer, "", "", "", "", githubWorkFlowRef, rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, CertGithubWorkflowRef: "otherRef", IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow Ref not found in certificate") err = CheckCertificatePolicy(leafCert, co) require.Contains(t, err.Error(), "expected GitHub Workflow Ref not found in certificate") } func TestValidateAndUnpackCertWithChainSuccess(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, subCert, subKey) co := &CheckOpts{ Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{subCert, leafCert}, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } } func TestValidateAndUnpackCertWithChainSuccessWithRoot(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) co := &CheckOpts{ Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{rootCert}, co) if err != nil { t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) } } func TestValidateAndUnpackCertWithChainFailsWithoutChain(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) co := &CheckOpts{ Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{}, co) if err == nil || err.Error() != "no chain provided to validate certificate" { t.Errorf("expected error without chain, got %v", err) } } func TestValidateAndUnpackCertWithChainFailsWithInvalidChain(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) rootCertOther, _, _ := test.GenerateRootCa() co := &CheckOpts{ Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, IgnoreSCT: true, } _, err := ValidateAndUnpackCertWithChain(leafCert, []*x509.Certificate{rootCertOther}, co) if err == nil || !strings.Contains(err.Error(), "certificate signed by unknown authority") { t.Errorf("expected error without valid chain, got %v", err) } } func TestValidateAndUnpackCertWithIdentities(t *testing.T) { u, err := url.Parse("http://url.example.com") if err != nil { t.Fatal("failed to parse url", err) } emailSubject := "email@example.com" dnsSubjects := []string{"dnssubject.example.com"} ipSubjects := []net.IP{net.ParseIP("1.2.3.4")} uriSubjects := []*url.URL{u} otherName := "email!example.com" oidcIssuer := "https://accounts.google.com" tests := []struct { identities []Identity wantErrSubstring string dnsNames []string emailAddresses []string ipAddresses []net.IP uris []*url.URL otherName string }{ {identities: nil /* No matches required, checks out */}, {identities: []Identity{ // Strict match on both {Subject: emailSubject, Issuer: oidcIssuer}}, emailAddresses: []string{emailSubject}}, {identities: []Identity{ // just issuer {Issuer: oidcIssuer}}, emailAddresses: []string{emailSubject}}, {identities: []Identity{ // just subject {Subject: emailSubject}}, emailAddresses: []string{emailSubject}}, {identities: []Identity{ // mis-match {Subject: "wrongsubject", Issuer: oidcIssuer}, {Subject: emailSubject, Issuer: "wrongissuer"}}, emailAddresses: []string{emailSubject}, wantErrSubstring: "none of the expected identities matched"}, {identities: []Identity{ // one good identity, other does not match {Subject: "wrongsubject", Issuer: "wrongissuer"}, {Subject: emailSubject, Issuer: oidcIssuer}}, emailAddresses: []string{emailSubject}}, {identities: []Identity{ // illegal regex for subject {SubjectRegExp: "****", Issuer: oidcIssuer}}, emailAddresses: []string{emailSubject}, wantErrSubstring: "malformed subject in identity"}, {identities: []Identity{ // illegal regex for issuer {Subject: emailSubject, IssuerRegExp: "****"}}, wantErrSubstring: "malformed issuer in identity"}, {identities: []Identity{ // regex matches {SubjectRegExp: ".*example.com", IssuerRegExp: ".*accounts.google.*"}}, emailAddresses: []string{emailSubject}, wantErrSubstring: ""}, {identities: []Identity{ // regex matches dnsNames {SubjectRegExp: ".*ubject.example.com", IssuerRegExp: ".*accounts.google.*"}}, dnsNames: dnsSubjects, wantErrSubstring: ""}, {identities: []Identity{ // regex matches ip {SubjectRegExp: "1.2.3.*", IssuerRegExp: ".*accounts.google.*"}}, ipAddresses: ipSubjects, wantErrSubstring: ""}, {identities: []Identity{ // regex matches urls {SubjectRegExp: ".*url.examp.*", IssuerRegExp: ".*accounts.google.*"}}, uris: uriSubjects, wantErrSubstring: ""}, {identities: []Identity{ // regex matches otherName {SubjectRegExp: ".*example.com", IssuerRegExp: ".*accounts.google.*"}}, otherName: otherName, wantErrSubstring: ""}, } for _, tc := range tests { rootCert, rootKey, _ := test.GenerateRootCa() var leafCert *x509.Certificate if len(tc.otherName) == 0 { leafCert, _, _ = test.GenerateLeafCertWithSubjectAlternateNames(tc.dnsNames, tc.emailAddresses, tc.ipAddresses, tc.uris, oidcIssuer, rootCert, rootKey) } else { // generate with OtherName, which will override other SANs ext, err := cryptoutils.MarshalOtherNameSAN(tc.otherName, true) if err != nil { t.Fatalf("error marshalling SANs: %v", err) } exts := []pkix.Extension{*ext} leafCert, _, _ = test.GenerateLeafCert("unused", oidcIssuer, rootCert, rootKey, exts...) } rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) co := &CheckOpts{ RootCerts: rootPool, Identities: tc.identities, IgnoreSCT: true, } _, err := ValidateAndUnpackCert(leafCert, co) if err == nil && tc.wantErrSubstring != "" { t.Errorf("Expected error %s got none", tc.wantErrSubstring) } else if err != nil { if tc.wantErrSubstring == "" { t.Errorf("Did not expect an error, got err = %v", err) } else if !strings.Contains(err.Error(), tc.wantErrSubstring) { t.Errorf("Did not get the expected error %s, got err = %v", tc.wantErrSubstring, err) } } // Test CheckCertificatePolicy err = CheckCertificatePolicy(leafCert, co) if err == nil && tc.wantErrSubstring != "" { t.Errorf("Expected error %s got none", tc.wantErrSubstring) } else if err != nil { if tc.wantErrSubstring == "" { t.Errorf("Did not expect an error, got err = %v", err) } else if !strings.Contains(err.Error(), tc.wantErrSubstring) { t.Errorf("Did not get the expected error %s, got err = %v", tc.wantErrSubstring, err) } } } } func TestValidateAndUnpackCertWithIntermediatesSuccess(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, subCert, subKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) subPool := x509.NewCertPool() rootPool.AddCert(subCert) co := &CheckOpts{ RootCerts: rootPool, IgnoreSCT: true, Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, } _, err := ValidateAndUnpackCertWithIntermediates(leafCert, co, subPool) if err != nil { t.Errorf("ValidateAndUnpackCertWithIntermediates expected no error, got err = %v", err) } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) } } func TestCompareSigs(t *testing.T) { // TODO(nsmith5): Add test cases for invalid signature, missing signature etc tests := []struct { description string b64sig string bundleBody string shouldErr bool }{ { description: "sigs match", b64sig: "MEQCIDO3XHbLovPWK+bk8ItCig2cwlr/8MXbLvz3UFzxMGIMAiA1lqdM9IqqUvCUqzOjufTq3sKU3qSn7R5tPqPz0ddNwQ==", bundleBody: `eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzODE1MmQxZGQzMjZhZjQwNWY4OTlkYmNjMmNlMzUwYjVmMTZkNDVkZjdmMjNjNDg4ZjQ4NTBhZmExY2Q4NmQxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRE8zWEhiTG92UFdLK2JrOEl0Q2lnMmN3bHIvOE1YYkx2ejNVRnp4TUdJTUFpQTFscWRNOUlxcVV2Q1Vxek9qdWZUcTNzS1UzcVNuN1I1dFBxUHowZGROd1E9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGVUN0RVIyb3ZXWFV4VG5vd01XVjVSV2hVZDNRMlQya3hXV3BGWXdwSloxRldjRlZTTjB0bUwwSm1hVk16Y1ZReFVHd3dkbGh3ZUZwNVMyWkpSMHMyZWxoQ04ybE5aV3RFVTA1M1dHWldPSEpKYUdaMmRrOW5QVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=`, }, { description: "sigs don't match", b64sig: "bm9wZQo=", bundleBody: `eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIzODE1MmQxZGQzMjZhZjQwNWY4OTlkYmNjMmNlMzUwYjVmMTZkNDVkZjdmMjNjNDg4ZjQ4NTBhZmExY2Q4NmQxIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJRE8zWEhiTG92UFdLK2JrOEl0Q2lnMmN3bHIvOE1YYkx2ejNVRnp4TUdJTUFpQTFscWRNOUlxcVV2Q1Vxek9qdWZUcTNzS1UzcVNuN1I1dFBxUHowZGROd1E9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGVUN0RVIyb3ZXWFV4VG5vd01XVjVSV2hVZDNRMlQya3hXV3BGWXdwSloxRldjRlZTTjB0bUwwSm1hVk16Y1ZReFVHd3dkbGh3ZUZwNVMyWkpSMHMyZWxoQ04ybE5aV3RFVTA1M1dHWldPSEpKYUdaMmRrOW5QVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=`, shouldErr: true, }, } for _, test := range tests { t.Run(test.description, func(t *testing.T) { sig, err := static.NewSignature([]byte("payload"), test.b64sig) if err != nil { t.Fatalf("failed to create static signature: %v", err) } err = compareSigs(test.bundleBody, sig) if err == nil && test.shouldErr { t.Fatal("test should have errored") } if err != nil && !test.shouldErr { t.Fatal(err) } }) } } func TestTrustedCertSuccess(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey) leafCert, _, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) subPool := x509.NewCertPool() subPool.AddCert(subCert) chains, err := TrustedCert(leafCert, rootPool, subPool) if err != nil { t.Fatalf("expected no error verifying certificate, got %v", err) } if len(chains) != 1 { t.Fatalf("unexpected number of chains found, expected 1, got %v", len(chains)) } if len(chains[0]) != 3 { t.Fatalf("unexpected number of certs in chain, expected 3, got %v", len(chains[0])) } } func TestTrustedCertSuccessNoIntermediates(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) _, err := TrustedCert(leafCert, rootPool, nil) if err != nil { t.Fatalf("expected no error verifying certificate, got %v", err) } } // Tests that verification succeeds if both a root and subordinate pool are // present, but a chain is built with only the leaf and root certificates. func TestTrustedCertSuccessChainFromRoot(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() leafCert, _, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) subCert, _, _ := test.GenerateSubordinateCa(rootCert, rootKey) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) subPool := x509.NewCertPool() subPool.AddCert(subCert) _, err := TrustedCert(leafCert, rootPool, subPool) if err != nil { t.Fatalf("expected no error verifying certificate, got %v", err) } } func TestVerifyRFC3161Timestamp(t *testing.T) { // generate signed artifact rootCert, rootKey, _ := test.GenerateRootCa() leafCert, privKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) payload := []byte{1, 2, 3, 4} h := sha256.Sum256(payload) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) client, err := tsaMock.NewTSAClient((tsaMock.TSAClientOptions{Time: time.Now()})) if err != nil { t.Fatal(err) } tsBytes, err := tsa.GetTimestampedSignature(signature, client) if err != nil { t.Fatalf("unexpected error creating timestamp: %v", err) } rfc3161TS := bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} certChainPEM, err := cryptoutils.MarshalCertificatesToPEM(client.CertChain) if err != nil { t.Fatalf("unexpected error marshalling cert chain: %v", err) } leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain(certChainPEM) if err != nil { t.Fatal("error splitting response into certificate chain") } ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), static.WithRFC3161Timestamp(&rfc3161TS)) // success, signing over signature ts, err := VerifyRFC3161Timestamp(ociSig, &CheckOpts{ TSACertificate: leaves[0], TSAIntermediateCertificates: intermediates, TSARootCertificates: roots, }) if err != nil { t.Fatalf("unexpected error verifying timestamp with signature: %v", err) } if err := CheckExpiry(leafCert, ts.Time); err != nil { t.Fatalf("unexpected error using time from timestamp to verify certificate: %v", err) } // success, signing over payload tsBytes, err = tsa.GetTimestampedSignature(payload, client) if err != nil { t.Fatalf("unexpected error creating timestamp: %v", err) } rfc3161TS = bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} ociSig, _ = static.NewSignature(payload, "", /*signature*/ static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), static.WithRFC3161Timestamp(&rfc3161TS)) _, err = VerifyRFC3161Timestamp(ociSig, &CheckOpts{ TSACertificate: leaves[0], TSAIntermediateCertificates: intermediates, TSARootCertificates: roots, }) if err != nil { t.Fatalf("unexpected error verifying timestamp with payload: %v", err) } // failure with non-base64 encoded signature ociSig, _ = static.NewSignature(payload, string(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), static.WithRFC3161Timestamp(&rfc3161TS)) _, err = VerifyRFC3161Timestamp(ociSig, &CheckOpts{ TSACertificate: leaves[0], TSAIntermediateCertificates: intermediates, TSARootCertificates: roots, }) if err == nil || !strings.Contains(err.Error(), "base64 data") { t.Fatalf("expected error verifying timestamp with raw signature, got: %v", err) } // failure with mismatched signature tsBytes, err = tsa.GetTimestampedSignature(signature, client) if err != nil { t.Fatalf("unexpected error creating timestamp: %v", err) } rfc3161TS = bundle.RFC3161Timestamp{SignedRFC3161Timestamp: tsBytes} // regenerate signature signature, _ = privKey.Sign(rand.Reader, h[:], crypto.SHA256) ociSig, _ = static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature), static.WithCertChain(pemLeaf, appendSlices([][]byte{pemRoot})), static.WithRFC3161Timestamp(&rfc3161TS)) _, err = VerifyRFC3161Timestamp(ociSig, &CheckOpts{ TSACertificate: leaves[0], TSAIntermediateCertificates: intermediates, TSARootCertificates: roots, }) if err == nil || !strings.Contains(err.Error(), "hashed messages don't match") { t.Fatalf("expected error verifying mismatched signatures, got: %v", err) } // failure without root certificate _, err = VerifyRFC3161Timestamp(ociSig, &CheckOpts{ TSACertificate: leaves[0], TSAIntermediateCertificates: intermediates, }) if err == nil || !strings.Contains(err.Error(), "no TSA root certificate(s) provided to verify timestamp") { t.Fatalf("expected error verifying without a root certificate, got: %v", err) } } // Mock Rekor client type mockEntriesClient struct { entries.ClientService searchLogQueryFunc func(params *entries.SearchLogQueryParams, opts ...entries.ClientOption) (*entries.SearchLogQueryOK, error) } func (m *mockEntriesClient) SearchLogQuery(params *entries.SearchLogQueryParams, opts ...entries.ClientOption) (*entries.SearchLogQueryOK, error) { if m.searchLogQueryFunc != nil { return m.searchLogQueryFunc(params, opts...) } return nil, nil } // createRekorEntry creates a mock Rekor log entry. func createRekorEntry(ctx context.Context, t *testing.T, logID string, signer signature.Signer, payload, signature []byte, publicKey crypto.PublicKey) *models.LogEntry { payloadHash := sha256.Sum256(payload) publicKeyBytes, err := cryptoutils.MarshalPublicKeyToPEM(publicKey) require.NoError(t, err) artifactProperties := rtypes.ArtifactProperties{ ArtifactHash: hex.EncodeToString(payloadHash[:]), SignatureBytes: signature, PublicKeyBytes: [][]byte{publicKeyBytes}, PKIFormat: "x509", } // Create and canonicalize Rekor entry entryProps, err := hashedrekord_v001.V001Entry{}.CreateFromArtifactProperties(ctx, artifactProperties) require.NoError(t, err) rekorEntry, err := rtypes.UnmarshalEntry(entryProps) require.NoError(t, err) canonicalEntry, err := rekorEntry.Canonicalize(ctx) require.NoError(t, err) // Create log entry integratedTime := time.Now().Unix() logEntry := models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString(canonicalEntry), IntegratedTime: swag.Int64(integratedTime), LogIndex: swag.Int64(0), LogID: swag.String(logID), } // Canonicalize the log entry and sign it jsonLogEntry, err := json.Marshal(logEntry) require.NoError(t, err) canonicalPayload, err := jsoncanonicalizer.Transform(jsonLogEntry) require.NoError(t, err) signedEntryTimestamp, err := signer.SignMessage(bytes.NewReader(canonicalPayload)) require.NoError(t, err) // Calculate leaf hash and add verification entryUUID, err := ComputeLeafHash(&logEntry) require.NoError(t, err) logEntry.Verification = &models.LogEntryAnonVerification{ SignedEntryTimestamp: signedEntryTimestamp, InclusionProof: &models.InclusionProof{ LogIndex: swag.Int64(0), TreeSize: swag.Int64(1), RootHash: swag.String(hex.EncodeToString(entryUUID)), Hashes: []string{}, }, } // Return the constructed log entry return &models.LogEntry{hex.EncodeToString(entryUUID): logEntry} } // generateSigner creates an ECDSA signer and public key. func generateSigner(t *testing.T) (signature.SignerVerifier, crypto.PublicKey) { privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err, "error generating private key") signer, err := signature.LoadECDSASignerVerifier(privateKey, crypto.SHA256) require.NoError(t, err, "error loading signer") publicKey, err := signer.PublicKey() require.NoError(t, err, "error getting public key") return signer, publicKey } // generateBlobSignature signs a blob and returns the blob, its signature, and the base64-encoded signature. func generateBlobSignature(t *testing.T, signer signature.Signer) ([]byte, []byte, string) { blob := []byte("foo") blobSignature, err := signer.SignMessage(bytes.NewReader(blob)) require.NoError(t, err, "error signing blob") blobSignatureBase64 := base64.StdEncoding.EncodeToString(blobSignature) return blob, blobSignature, blobSignatureBase64 } // calculateLogID generates a SHA-256 hash of the given public key and returns it as a hexadecimal string. func calculateLogID(t *testing.T, pub crypto.PublicKey) string { pubBytes, err := x509.MarshalPKIXPublicKey(pub) require.NoError(t, err, "error marshalling public key") digest := sha256.Sum256(pubBytes) return hex.EncodeToString(digest[:]) } cosign-2.5.0/pkg/oci/000077500000000000000000000000001477503325500143465ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/doc.go000066400000000000000000000013351477503325500154440ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package oci holds functions and types intended to align and compose with // github.com/google/go-containerregistry. package oci cosign-2.5.0/pkg/oci/empty/000077500000000000000000000000001477503325500155045ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/empty/empty.go000066400000000000000000000025341477503325500171750ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package empty import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/oci" ) // Signatures constructs an empty oci.Signatures. func Signatures() oci.Signatures { base := empty.Image if !oci.DockerMediaTypes() { base = mutate.MediaType(base, types.OCIManifestSchema1) base = mutate.ConfigMediaType(base, types.OCIConfigJSON) } return &emptyImage{ Image: base, } } type emptyImage struct { v1.Image } var _ oci.Signatures = (*emptyImage)(nil) // Get implements oci.Signatures func (*emptyImage) Get() ([]oci.Signature, error) { return nil, nil } cosign-2.5.0/pkg/oci/empty/empty_test.go000066400000000000000000000036431477503325500202360ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package empty import ( "os" "testing" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) func TestEmptyImage(t *testing.T) { tests := []struct { name string value string wantMT types.MediaType wantConfigMT types.MediaType }{{ name: "unset", wantMT: types.OCIManifestSchema1, wantConfigMT: types.OCIConfigJSON, }, { name: "set false", value: "false", wantMT: types.OCIManifestSchema1, wantConfigMT: types.OCIConfigJSON, }, { name: "set true", value: "true", wantMT: types.DockerManifestSchema2, wantConfigMT: types.DockerConfigJSON, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { if err := os.Setenv(env.VariableDockerMediaTypes.String(), test.value); err != nil { t.Fatalf("Setenv() = %v", err) } img := Signatures() if mt, err := img.MediaType(); err != nil { t.Errorf("MediaType() = %v", err) } else if mt != test.wantMT { t.Errorf("MediaType() = %v, wanted %v", mt, test.wantMT) } m, err := img.Manifest() if err != nil { t.Fatalf("ConfigFile() = %v", err) } if mt := m.Config.MediaType; mt != test.wantConfigMT { t.Errorf("Config.MediaType = %v, wanted %v", mt, test.wantConfigMT) } }) } } cosign-2.5.0/pkg/oci/empty/signed.go000066400000000000000000000034331477503325500173070ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package empty import ( "errors" "fmt" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/sigstore/cosign/v2/pkg/oci" ) type signedImage struct { v1.Image digest v1.Hash signature oci.Signatures attestations oci.Signatures } func (se *signedImage) Signatures() (oci.Signatures, error) { return se.signature, nil } func (se *signedImage) Attestations() (oci.Signatures, error) { return se.attestations, nil } func (se *signedImage) Attachment(name string) (oci.File, error) { //nolint: revive return nil, errors.New("no attachments") } func (se *signedImage) Digest() (v1.Hash, error) { if se.digest.Hex == "" { return v1.Hash{}, fmt.Errorf("digest not available") } return se.digest, nil } func SignedImage(ref name.Reference) (oci.SignedImage, error) { var err error d := v1.Hash{} base := empty.Image if digest, ok := ref.(name.Digest); ok { d, err = v1.NewHash(digest.DigestStr()) if err != nil { return nil, err } } return &signedImage{ Image: base, digest: d, signature: Signatures(), attestations: Signatures(), }, nil } cosign-2.5.0/pkg/oci/empty/signed_test.go000066400000000000000000000040631477503325500203460ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package empty import ( "testing" "github.com/google/go-containerregistry/pkg/name" ) func TestSignedImage(t *testing.T) { tests := []struct { ref string digestStr string digestErr string }{ { ref: "hello-world:latest", digestErr: "digest not available", }, { ref: "hello-world@sha256:e1c082e3d3c45cccac829840a25941e679c25d438cc8412c2fa221cf1a824e6a", digestStr: "sha256:e1c082e3d3c45cccac829840a25941e679c25d438cc8412c2fa221cf1a824e6a", }, } for _, test := range tests { ref, err := name.ParseReference(test.ref) if err != nil { t.Errorf("failed to parse ref \"%s\": %v", test.ref, err) continue } se, err := SignedImage(ref) if err != nil { t.Errorf("failed to create signed image for \"%s\": %v", test.ref, err) continue } d, err := se.Digest() if (err == nil && test.digestErr != "") || (err != nil && test.digestErr == "") || (err != nil && test.digestErr != "" && err.Error() != test.digestErr) { t.Errorf("digest error mismatch for \"%s\": expected %s, saw %v", test.ref, test.digestErr, err) } if test.digestStr != "" && d.String() != test.digestStr { t.Errorf("digest mismatch for \"%s\": expected %s, saw %s", test.ref, test.digestStr, d.String()) } _, err = se.Signatures() if err != nil { t.Errorf("failed to get signatures for %s: %v", test.ref, err) } _, err = se.Attestations() if err != nil { t.Errorf("failed to get attestations for %s: %v", test.ref, err) } } } cosign-2.5.0/pkg/oci/errors.go000066400000000000000000000020471477503325500162140ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oci import "fmt" // MaxLayersExceeded is an error indicating that the artifact has too many layers and cosign should abort processing it. type MaxLayersExceeded struct { value int64 maximum int64 } func NewMaxLayersExceeded(value, maximum int64) *MaxLayersExceeded { return &MaxLayersExceeded{value, maximum} } func (e *MaxLayersExceeded) Error() string { return fmt.Sprintf("number of layers (%d) exceeded the limit (%d)", e.value, e.maximum) } cosign-2.5.0/pkg/oci/file.go000066400000000000000000000020171477503325500156140ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oci import "github.com/google/go-containerregistry/pkg/v1/types" // File is a degenerate form of SignedImage that stores a single file as a v1.Layer type File interface { SignedImage // FileMediaType retrieves the media type of the File FileMediaType() (types.MediaType, error) // Payload fetches the opaque data that is being signed. // This will always return data when there is no error. Payload() ([]byte, error) } cosign-2.5.0/pkg/oci/image.go000066400000000000000000000015331477503325500157610ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oci import v1 "github.com/google/go-containerregistry/pkg/v1" // SignedImage represents an OCI Image, complemented with accessors // for retrieving signed metadata associated with that image. type SignedImage interface { v1.Image SignedEntity } cosign-2.5.0/pkg/oci/index.go000066400000000000000000000022631477503325500160070ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oci import v1 "github.com/google/go-containerregistry/pkg/v1" // SignedIndex represents an OCI ImageIndex, complemented with accessors // for retrieving signed metadata associated with that ImageIndex. type SignedImageIndex interface { v1.ImageIndex SignedEntity // SignedImage is the same as Image, but provides accessors for the nested // image's signed metadata. SignedImage(v1.Hash) (SignedImage, error) // SignedImageIndex is the same as ImageIndex, but provides accessors for // the nested image index's signed metadata. SignedImageIndex(v1.Hash) (SignedImageIndex, error) } cosign-2.5.0/pkg/oci/interface.go000066400000000000000000000025461477503325500166440ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oci import v1 "github.com/google/go-containerregistry/pkg/v1" type SignedEntity interface { // Digest returns the sha256 of this image's manifest. Digest() (v1.Hash, error) // Signatures returns the set of signatures currently associated with this // entity, or the empty equivalent if none are found. Signatures() (Signatures, error) // Attestations returns the set of attestations currently associated with this // entity, or the empty equivalent if none are found. // Attestations are just like a Signature, but they do not contain // Base64Signature because it's baked into the payload. Attestations() (Signatures, error) // Attachment returns a named entity associated with this entity, or error if not found. Attachment(name string) (File, error) } cosign-2.5.0/pkg/oci/internal/000077500000000000000000000000001477503325500161625ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/internal/signature/000077500000000000000000000000001477503325500201635ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/internal/signature/layer.go000066400000000000000000000075231477503325500216350ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signature import ( "crypto/x509" "encoding/base64" "encoding/json" "fmt" "io" "strings" v1 "github.com/google/go-containerregistry/pkg/v1" payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/sigstore/pkg/cryptoutils" ) const ( sigkey = "dev.cosignproject.cosign/signature" certkey = "dev.sigstore.cosign/certificate" chainkey = "dev.sigstore.cosign/chain" BundleKey = "dev.sigstore.cosign/bundle" RFC3161TimestampKey = "dev.sigstore.cosign/rfc3161timestamp" ) type sigLayer struct { v1.Layer desc v1.Descriptor } func New(l v1.Layer, desc v1.Descriptor) oci.Signature { return &sigLayer{ Layer: l, desc: desc, } } var _ oci.Signature = (*sigLayer)(nil) // Annotations implements oci.Signature func (s *sigLayer) Annotations() (map[string]string, error) { return s.desc.Annotations, nil } // Payload implements oci.Signature func (s *sigLayer) Payload() ([]byte, error) { size, err := s.Size() if err != nil { return nil, err } err = payloadsize.CheckSize(uint64(size)) if err != nil { return nil, err } // Compressed is a misnomer here, we just want the raw bytes from the registry. r, err := s.Compressed() if err != nil { return nil, err } defer r.Close() payload, err := io.ReadAll(r) if err != nil { return nil, err } return payload, nil } // Signature implements oci.Signature func (s *sigLayer) Signature() ([]byte, error) { b64sig, err := s.Base64Signature() if err != nil { return nil, err } return base64.StdEncoding.DecodeString(b64sig) } // Base64Signature implements oci.Signature func (s *sigLayer) Base64Signature() (string, error) { b64sig, ok := s.desc.Annotations[sigkey] if !ok { return "", fmt.Errorf("signature layer %s is missing %q annotation", s.desc.Digest, sigkey) } return b64sig, nil } // Cert implements oci.Signature func (s *sigLayer) Cert() (*x509.Certificate, error) { certPEM := s.desc.Annotations[certkey] if certPEM == "" { return nil, nil } certs, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(certPEM)) if err != nil { return nil, err } return certs[0], nil } // Chain implements oci.Signature func (s *sigLayer) Chain() ([]*x509.Certificate, error) { chainPEM := s.desc.Annotations[chainkey] if chainPEM == "" { return nil, nil } certs, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(chainPEM)) if err != nil { return nil, err } return certs, nil } // Bundle implements oci.Signature func (s *sigLayer) Bundle() (*bundle.RekorBundle, error) { val := s.desc.Annotations[BundleKey] if val == "" { return nil, nil } var b bundle.RekorBundle if err := json.Unmarshal([]byte(val), &b); err != nil { return nil, fmt.Errorf("unmarshaling bundle: %w", err) } return &b, nil } // RFC3161Timestamp implements oci.Signature func (s *sigLayer) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) { val := s.desc.Annotations[RFC3161TimestampKey] if val == "" { return nil, nil } var b bundle.RFC3161Timestamp if err := json.Unmarshal([]byte(val), &b); err != nil { return nil, fmt.Errorf("unmarshaling RFC3161 timestamp bundle: %w", err) } return &b, nil } cosign-2.5.0/pkg/oci/internal/signature/layer_test.go000066400000000000000000000370331477503325500226730ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signature import ( "bytes" "encoding/base64" "errors" "fmt" "io" "strings" "testing" "github.com/google/go-cmp/cmp" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" ) func mustDecode(s string) []byte { b, err := base64.StdEncoding.DecodeString(s) if err != nil { panic(err.Error()) } return b } func TestSignature(t *testing.T) { layer, err := random.Layer(300 /* byteSize */, types.DockerLayer) if err != nil { t.Fatalf("random.Layer() = %v", err) } digest, err := layer.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } tests := []struct { name string l *sigLayer env map[string]string wantPayloadErr error wantSig string wantSigErr error wantCert bool wantCertErr error wantChain int wantChainErr error wantBundle *bundle.RekorBundle wantBundleErr error }{{ name: "just payload and signature", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", }, }, }, wantSig: "blah", }, { name: "with empty other keys", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", certkey: "", chainkey: "", BundleKey: "", }, }, }, wantSig: "blah", }, { name: "missing signature", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, }, }, wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey), }, { name: "min plus bad bundle", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", BundleKey: `}`, }, }, }, wantSig: "blah", wantBundleErr: errors.New(`unmarshaling bundle: invalid character '}' looking for beginning of value`), }, { name: "min plus bad cert", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", certkey: `GARBAGE`, }, }, }, wantSig: "blah", wantCertErr: errors.New(`error during PEM decoding`), }, { name: "min plus bad chain", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", chainkey: `GARBAGE`, }, }, }, wantSig: "blah", wantChainErr: errors.New(`error during PEM decoding`), }, { name: "min plus bundle", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16. // The Body has been removed for brevity BundleKey: `{"SignedEntryTimestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`, }, }, }, wantSig: "blah", wantBundle: &bundle.RekorBundle{ SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), Payload: bundle.RekorPayload{ Body: "REMOVED", IntegratedTime: 1631646761, LogIndex: 693591, LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", }, }, }, { name: "min plus good cert", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 certkey: ` -----BEGIN CERTIFICATE----- MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt 6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G 3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG 0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY= -----END CERTIFICATE----- `, }, }, }, wantSig: "blah", wantCert: true, }, { name: "min plus bad chain", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 chainkey: ` -----BEGIN CERTIFICATE----- MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== -----END CERTIFICATE----- `, }, }, }, wantSig: "blah", wantChain: 1, }, { name: "payload size exceeds default limit", l: &sigLayer{ Layer: &mockLayer{size: 134217728 + 42}, // 128MB + 42 bytes }, wantPayloadErr: errors.New("size of layer (134217770) exceeded the limit (134217728)"), }, { name: "payload size exceeds overridden limit", l: &sigLayer{ Layer: &mockLayer{size: 1000000000 + 42}, // 1GB + 42 bytes }, env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1GB"}, wantPayloadErr: errors.New("size of layer (1000000042) exceeded the limit (1000000000)"), }, { name: "payload size is within overridden limit", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", }, }, }, env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "5KB"}, wantSig: "blah", }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { for k, v := range test.env { t.Setenv(k, v) } b, err := test.l.Payload() switch { case (err != nil) != (test.wantPayloadErr != nil): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error(): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) case err == nil: if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil { t.Errorf("v1.SHA256() = %v", err) } else if want := digest; want != got { t.Errorf("v1.SHA256() = %v, wanted %v", got, want) } } if err != nil { return } switch got, err := test.l.Base64Signature(); { case (err != nil) != (test.wantSigErr != nil): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error(): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) case got != test.wantSig: t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig) } switch got, err := test.l.Cert(); { case (err != nil) != (test.wantCertErr != nil): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error(): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) case (got != nil) != test.wantCert: t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert) } switch got, err := test.l.Chain(); { case (err != nil) != (test.wantChainErr != nil): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error(): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) case len(got) != test.wantChain: t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain) } switch got, err := test.l.Bundle(); { case (err != nil) != (test.wantBundleErr != nil): t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr) case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error(): t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr) case !cmp.Equal(got, test.wantBundle): t.Errorf("Bundle() %s", cmp.Diff(got, test.wantBundle)) } }) } } func TestSignatureWithTSAAnnotation(t *testing.T) { layer, err := random.Layer(300 /* byteSize */, types.DockerLayer) if err != nil { t.Fatalf("random.Layer() = %v", err) } digest, err := layer.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } tests := []struct { name string l *sigLayer wantPayloadErr error wantSig string wantSigErr error wantCert bool wantCertErr error wantChain int wantChainErr error wantBundle *bundle.RFC3161Timestamp wantBundleErr error }{{ name: "just payload and signature", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", }, }, }, wantSig: "blah", }, { name: "with empty other keys", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", certkey: "", chainkey: "", RFC3161TimestampKey: "", }, }, }, wantSig: "blah", }, { name: "missing signature", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, }, }, wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey), }, { name: "min plus bad bundle", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", RFC3161TimestampKey: `}`, }, }, }, wantSig: "blah", wantBundleErr: errors.New(`unmarshaling RFC3161 timestamp bundle: invalid character '}' looking for beginning of value`), }, { name: "min plus bad cert", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", certkey: `GARBAGE`, }, }, }, wantSig: "blah", wantCertErr: errors.New(`error during PEM decoding`), }, { name: "min plus bad chain", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", chainkey: `GARBAGE`, }, }, }, wantSig: "blah", wantChainErr: errors.New(`error during PEM decoding`), }, { name: "min plus RFC3161 timestamp bundle", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "TSA blah", // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16. // The Body has been removed for brevity RFC3161TimestampKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="}`, }, }, }, wantSig: "TSA blah", wantBundle: &bundle.RFC3161Timestamp{ SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), }, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { b, err := test.l.Payload() switch { case (err != nil) != (test.wantPayloadErr != nil): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error(): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) case err == nil: if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil { t.Errorf("v1.SHA256() = %v", err) } else if want := digest; want != got { t.Errorf("v1.SHA256() = %v, wanted %v", got, want) } } switch got, err := test.l.Base64Signature(); { case (err != nil) != (test.wantSigErr != nil): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error(): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) case got != test.wantSig: t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig) } switch got, err := test.l.Cert(); { case (err != nil) != (test.wantCertErr != nil): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error(): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) case (got != nil) != test.wantCert: t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert) } switch got, err := test.l.Chain(); { case (err != nil) != (test.wantChainErr != nil): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error(): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) case len(got) != test.wantChain: t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain) } switch got, err := test.l.RFC3161Timestamp(); { case (err != nil) != (test.wantBundleErr != nil): t.Errorf("RFC3161Timestamp() = %v, wanted %v", err, test.wantBundleErr) case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error(): t.Errorf("RFC3161Timestamp() = %v, wanted %v", err, test.wantBundleErr) case !cmp.Equal(got, test.wantBundle): t.Errorf("RFC3161Timestamp() %s", cmp.Diff(got, test.wantBundle)) } }) } } type mockLayer struct { size int64 } func (m *mockLayer) Size() (int64, error) { return m.size, nil } func (m *mockLayer) Compressed() (io.ReadCloser, error) { return io.NopCloser(strings.NewReader("data")), nil } func (m *mockLayer) Digest() (v1.Hash, error) { panic("not implemented") } func (m *mockLayer) DiffID() (v1.Hash, error) { panic("not implemented") } func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") } func (m *mockLayer) MediaType() (types.MediaType, error) { panic("not implemented") } cosign-2.5.0/pkg/oci/layout/000077500000000000000000000000001477503325500156635ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/layout/index.go000066400000000000000000000076041477503325500173300ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package layout import ( "fmt" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/signed" ) const ( kindAnnotation = "kind" imageAnnotation = "dev.cosignproject.cosign/image" imageIndexAnnotation = "dev.cosignproject.cosign/imageIndex" sigsAnnotation = "dev.cosignproject.cosign/sigs" attsAnnotation = "dev.cosignproject.cosign/atts" ) // SignedImageIndex provides access to a local index reference, and its signatures. func SignedImageIndex(path string) (oci.SignedImageIndex, error) { p, err := layout.FromPath(path) if err != nil { return nil, err } ii, err := p.ImageIndex() if err != nil { return nil, err } return &index{ v1Index: ii, }, nil } // We alias ImageIndex so that we can inline it without the type // name colliding with the name of a method it had to implement. type v1Index v1.ImageIndex type index struct { v1Index } var _ oci.SignedImageIndex = (*index)(nil) // Signatures implements oci.SignedImageIndex func (i *index) Signatures() (oci.Signatures, error) { img, err := i.imageByAnnotation(sigsAnnotation) if err != nil { return nil, err } if img == nil { return nil, nil } return &sigs{img}, nil } // Attestations implements oci.SignedImageIndex func (i *index) Attestations() (oci.Signatures, error) { img, err := i.imageByAnnotation(attsAnnotation) if err != nil { return nil, err } if img == nil { return nil, nil } return &sigs{img}, nil } // Attestations implements oci.SignedImage func (i *index) Attachment(name string) (oci.File, error) { //nolint: revive return nil, fmt.Errorf("not yet implemented") } // SignedImage implements oci.SignedImageIndex // if an empty hash is passed in, return the original image that was signed func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) { var img v1.Image var err error if h.String() == ":" { img, err = i.imageByAnnotation(imageAnnotation) } else { img, err = i.Image(h) } if err != nil { return nil, err } if img == nil { return nil, nil } return signed.Image(img), nil } // imageByAnnotation searches through all manifests in the index.json // and returns the image that has the matching annotation func (i *index) imageByAnnotation(annotation string) (v1.Image, error) { manifest, err := i.IndexManifest() if err != nil { return nil, err } for _, m := range manifest.Manifests { if val, ok := m.Annotations[kindAnnotation]; ok && val == annotation { return i.Image(m.Digest) } } return nil, nil } func (i *index) imageIndexByAnnotation(annotation string) (v1.ImageIndex, error) { manifest, err := i.IndexManifest() if err != nil { return nil, err } for _, m := range manifest.Manifests { if val, ok := m.Annotations[kindAnnotation]; ok && val == annotation { return i.ImageIndex(m.Digest) } } return nil, nil } // SignedImageIndex implements oci.SignedImageIndex func (i *index) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) { var ii v1.ImageIndex var err error if h.String() == ":" { ii, err = i.imageIndexByAnnotation(imageIndexAnnotation) } else { ii, err = i.ImageIndex(h) } if err != nil { return nil, err } if ii == nil { return nil, nil } return &index{ v1Index: ii, }, nil } cosign-2.5.0/pkg/oci/layout/signatures.go000066400000000000000000000026141477503325500204010ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package layout import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/internal/signature" ) const maxLayers = 1000 type sigs struct { v1.Image } var _ oci.Signatures = (*sigs)(nil) // Get implements oci.Signatures func (s *sigs) Get() ([]oci.Signature, error) { manifest, err := s.Manifest() if err != nil { return nil, err } numLayers := int64(len(manifest.Layers)) if numLayers > maxLayers { return nil, oci.NewMaxLayersExceeded(numLayers, maxLayers) } signatures := make([]oci.Signature, 0, numLayers) for _, desc := range manifest.Layers { l, err := s.LayerByDigest(desc.Digest) if err != nil { return nil, err } signatures = append(signatures, signature.New(l, desc)) } return signatures, nil } cosign-2.5.0/pkg/oci/layout/signatures_test.go000066400000000000000000000031331477503325500214350ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package layout import ( "errors" "testing" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/fake" ) func TestGet(t *testing.T) { tests := []struct { name string layers int wantError error }{ { name: "within limit", layers: 23, wantError: nil, }, { name: "exceeds limit", layers: 4242, wantError: errors.New("number of layers (4242) exceeded the limit (1000)"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { s := sigs{ Image: &fake.FakeImage{ ManifestStub: func() (*v1.Manifest, error) { return &v1.Manifest{ Layers: make([]v1.Descriptor, test.layers), }, nil }, }, } _, err := s.Get() if test.wantError != nil && test.wantError.Error() != err.Error() { t.Fatalf("Get() = %v, wanted %v", err, test.wantError) } if test.wantError == nil && err != nil { t.Fatalf("Get() = %v, wanted %v", err, test.wantError) } }) } } cosign-2.5.0/pkg/oci/layout/write.go000066400000000000000000000054551477503325500173550ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package layout import ( "fmt" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/sigstore/cosign/v2/pkg/oci" ) // WriteSignedImage writes the image and all related signatures, attestations and attachments func WriteSignedImage(path string, si oci.SignedImage) error { // First, write an empty index layoutPath, err := layout.Write(path, empty.Index) if err != nil { return err } // write the image if err := appendImage(layoutPath, si, imageAnnotation); err != nil { return fmt.Errorf("appending signed image: %w", err) } return writeSignedEntity(layoutPath, si) } // WriteSignedImageIndex writes the image index and all related signatures, attestations and attachments func WriteSignedImageIndex(path string, si oci.SignedImageIndex) error { // First, write an empty index layoutPath, err := layout.Write(path, empty.Index) if err != nil { return err } // write the image index if err := layoutPath.AppendIndex(si, layout.WithAnnotations( map[string]string{kindAnnotation: imageIndexAnnotation}, )); err != nil { return fmt.Errorf("appending signed image index: %w", err) } return writeSignedEntity(layoutPath, si) } func writeSignedEntity(path layout.Path, se oci.SignedEntity) error { // write the signatures sigs, err := se.Signatures() if err != nil { return fmt.Errorf("getting signatures: %w", err) } if !isEmpty(sigs) { if err := appendImage(path, sigs, sigsAnnotation); err != nil { return fmt.Errorf("appending signatures: %w", err) } } // write attestations atts, err := se.Attestations() if err != nil { return fmt.Errorf("getting atts") } if !isEmpty(atts) { if err := appendImage(path, atts, attsAnnotation); err != nil { return fmt.Errorf("appending atts: %w", err) } } // TODO (priyawadhwa@) and attachments return nil } // isEmpty returns true if the signatures or attestations are empty func isEmpty(s oci.Signatures) bool { ss, _ := s.Get() return ss == nil } func appendImage(path layout.Path, img v1.Image, annotation string) error { return path.AppendImage(img, layout.WithAnnotations( map[string]string{kindAnnotation: annotation}, )) } cosign-2.5.0/pkg/oci/layout/write_test.go000066400000000000000000000071761477503325500204160ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package layout import ( "fmt" "runtime" "testing" "github.com/google/go-cmp/cmp" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/mutate" "github.com/sigstore/cosign/v2/pkg/oci/signed" "github.com/sigstore/cosign/v2/pkg/oci/static" ) func TestReadWrite(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("test is flaky on windows, see https://github.com/sigstore/cosign/v2/issues/1389") } // write random signed image to disk si := randomSignedImage(t) tmp := t.TempDir() if err := WriteSignedImage(tmp, si); err != nil { t.Fatal(err) } // read the image and make sure the signatures exist imageIndex, err := SignedImageIndex(tmp) if err != nil { t.Fatal(err) } gotSignedImage, err := imageIndex.SignedImage(v1.Hash{}) if err != nil { t.Fatal(err) } // compare the image we read with the one we wrote compareDigests(t, si, gotSignedImage) // make sure we have 5 attestations attImg, err := imageIndex.Attestations() if err != nil { t.Fatal(err) } atts, err := attImg.Get() if err != nil { t.Fatal(err) } if len(atts) != 5 { t.Fatal("expected 5 attestations") } // make sure signatures are correct sigImage, err := imageIndex.Signatures() if err != nil { t.Fatal(err) } sigs, err := sigImage.Get() if err != nil { t.Fatal(err) } want := 6 if len(sigs) != want { t.Fatal("didn't get the expected number of signatures") } // make sure the annotation is correct for i, sig := range sigs { annotations, err := sig.Annotations() if err != nil { t.Fatal(err) } val, ok := annotations["layer"] if !ok { t.Fatal("expected annotation doesn't exist on signature") } if val != fmt.Sprintf("%d", i) { t.Fatal("expected annotation isn't correct") } } } func randomSignedImage(t *testing.T) oci.SignedImage { i, err := random.Image(300 /* byteSize */, 7 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } si := signed.Image(i) want := 6 // Add 6 signatures for i := 0; i < want; i++ { annotationOption := static.WithAnnotations(map[string]string{"layer": fmt.Sprintf("%d", i)}) sig, err := static.NewSignature(nil, fmt.Sprintf("%d", i), annotationOption) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } si, err = mutate.AttachSignatureToImage(si, sig) if err != nil { t.Fatalf("SignEntity() = %v", err) } } want = 5 // Add 5 attestations for i := 0; i < want; i++ { sig, err := static.NewAttestation([]byte(fmt.Sprintf("%d", i))) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } si, err = mutate.AttachAttestationToImage(si, sig) if err != nil { t.Fatalf("SignEntity() = %v", err) } } return si } func compareDigests(t *testing.T, img1 oci.SignedImage, img2 oci.SignedImage) { d1, err := img1.Digest() if err != nil { t.Fatal(err) } d2, err := img2.Digest() if err != nil { t.Fatal(err) } if d := cmp.Diff(d1, d2); d != "" { t.Fatalf("digests are different: %s", d) } } cosign-2.5.0/pkg/oci/mediatypes.go000066400000000000000000000017121477503325500170420ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oci import ( "strconv" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) const ( // Deprecated: use `pkg/cosign/env/VariableDockerMediaTypes` instead. DockerMediaTypesEnv = env.VariableDockerMediaTypes ) func DockerMediaTypes() bool { if b, err := strconv.ParseBool(env.Getenv(env.VariableDockerMediaTypes)); err == nil { return b } return false } cosign-2.5.0/pkg/oci/mutate/000077500000000000000000000000001477503325500156455ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/mutate/map.go000066400000000000000000000123251477503325500167540ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( "context" "errors" "fmt" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/oci" ) // Fn is the signature of the callback supplied to Map. // The oci.SignedEntity is either an oci.SignedImageIndex or an oci.SignedImage. // This callback is called on oci.SignedImageIndex *before* its children are // processed with a context that returns IsBeforeChildren(ctx) == true. // If the images within the SignedImageIndex change after the Before pass, then // the Fn will be invoked again on the new SignedImageIndex with a context // that returns IsAfterChildren(ctx) == true. // If the returned entity is nil, it is filtered from the result of Map. type Fn func(context.Context, oci.SignedEntity) (oci.SignedEntity, error) // ErrSkipChildren is a special error that may be returned from a Mutator // to skip processing of an index's child entities. var ErrSkipChildren = errors.New("skip child entities") // Map calls `fn` on the signed entity and each of its constituent entities (`SignedImageIndex` // or `SignedImage`) transitively. // Any errors returned by an `fn` are returned by `Map`. func Map(ctx context.Context, parent oci.SignedEntity, fn Fn) (oci.SignedEntity, error) { parent, err := fn(before(ctx), parent) switch { case errors.Is(err, ErrSkipChildren): return parent, nil case err != nil: return nil, err case parent == nil: // If the function returns nil, it filters it. return nil, nil } sii, ok := parent.(oci.SignedImageIndex) if !ok { return parent, nil } im, err := sii.IndexManifest() if err != nil { return nil, err } // Track whether any of the child entities change. changed := false adds := []IndexAddendum{} for _, desc := range im.Manifests { switch desc.MediaType { case types.OCIImageIndex, types.DockerManifestList: x, err := sii.SignedImageIndex(desc.Digest) if err != nil { return nil, err } se, err := Map(ctx, x, fn) if err != nil { return nil, err } else if se == nil { // If the function returns nil, it filters it. changed = true continue } changed = changed || (x != se) adds = append(adds, IndexAddendum{ Add: se.(oci.SignedImageIndex), // Must be an image index. Descriptor: v1.Descriptor{ URLs: desc.URLs, MediaType: desc.MediaType, Annotations: desc.Annotations, Platform: desc.Platform, }, }) case types.OCIManifestSchema1, types.DockerManifestSchema2: x, err := sii.SignedImage(desc.Digest) if err != nil { return nil, err } se, err := fn(ctx, x) if err != nil { return nil, err } else if se == nil { // If the function returns nil, it filters it. changed = true continue } changed = changed || (x != se) adds = append(adds, IndexAddendum{ Add: se.(oci.SignedImage), // Must be an image Descriptor: v1.Descriptor{ URLs: desc.URLs, MediaType: desc.MediaType, Annotations: desc.Annotations, Platform: desc.Platform, }, }) default: return nil, fmt.Errorf("unknown mime type: %v", desc.MediaType) } } if !changed { return parent, nil } // Preserve the key attributes from the base IndexManifest. e := mutate.IndexMediaType(empty.Index, im.MediaType) e = mutate.Annotations(e, im.Annotations).(v1.ImageIndex) // Construct a new ImageIndex from the new constituent signed images. result := AppendManifests(e, adds...) // Since the children changed, give the callback a crack at the new image index. return fn(after(ctx), result) } // This is used to associate which pass of the Map a particular // callback is being invoked for. type mapPassKey struct{} // before decorates the context such that IsBeforeChildren(ctx) is true. func before(ctx context.Context) context.Context { return context.WithValue(ctx, mapPassKey{}, "before") } // after decorates the context such that IsAfterChildren(ctx) is true. func after(ctx context.Context) context.Context { return context.WithValue(ctx, mapPassKey{}, "after") } // IsBeforeChildren is true within a Mutator when it is called before the children // have been processed. func IsBeforeChildren(ctx context.Context) bool { return ctx.Value(mapPassKey{}) == "before" } // IsAfterChildren is true within a Mutator when it is called after the children // have been processed; however, this call is only made if the set of children // changes since the Before call. func IsAfterChildren(ctx context.Context) bool { return ctx.Value(mapPassKey{}) == "after" } cosign-2.5.0/pkg/oci/mutate/map_test.go000066400000000000000000000160211477503325500200100ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( "context" "errors" "testing" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/signed" ) func TestMapImage(t *testing.T) { i, err := random.Image(300 /* bytes */, 3 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } si := signed.Image(i) t.Run("one call to identity mutator", func(t *testing.T) { calls := 0 rsi, err := Map(context.Background(), si, func(_ context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { calls++ return se, nil }) if err != nil { t.Fatalf("Map() = %v", err) } if rsi != si { t.Fatalf("Map() = %#v, wanted %#v", rsi, si) } if calls != 1 { t.Fatalf("Map called %d times, wanted 1", calls) } }) t.Run("error propagates", func(t *testing.T) { want := errors.New("this is the error I expect") _, got := Map(context.Background(), si, func(_ context.Context, _ oci.SignedEntity) (oci.SignedEntity, error) { return nil, want }) if !errors.Is(got, want) { t.Fatalf("Map() = %v, wanted %v", got, want) } }) t.Run("new result image", func(t *testing.T) { i, err := random.Image(300 /* bytes */, 3 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } want := signed.Image(i) got, err := Map(context.Background(), si, func(_ context.Context, _ oci.SignedEntity) (oci.SignedEntity, error) { return want, nil }) if err != nil { t.Fatalf("Map() = %v", err) } if got != want { t.Fatalf("Map() = %#v, wanted %#v", got, want) } }) t.Run("filtered image", func(t *testing.T) { got, err := Map(context.Background(), si, func(_ context.Context, _ oci.SignedEntity) (oci.SignedEntity, error) { return nil, nil }) if err != nil { t.Fatalf("Map() = %v", err) } if got != nil { t.Fatalf("Map() = %#v, wanted nil", got) } }) } func TestMapImageIndex(t *testing.T) { ii, err := random.Index(300 /* bytes */, 3 /* layers */, 2 /* images */) if err != nil { t.Fatalf("random.Image() = %v", err) } ii2, err := random.Index(300 /* bytes */, 3 /* layers */, 2 /* images */) if err != nil { t.Fatalf("random.Image() = %v", err) } sii := signed.ImageIndex(mutate.AppendManifests(ii, mutate.IndexAddendum{ Add: ii2, })) t.Run("six calls to identity mutator", func(t *testing.T) { calls := 0 after := 0 rsi, err := Map(context.Background(), sii, func(ctx context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { calls++ if IsAfterChildren(ctx) { after++ } return se, nil }) if err != nil { t.Fatalf("Map() = %v", err) } if rsi != sii { t.Fatalf("Map() = %#v, wanted %#v", rsi, sii) } if calls != 6 { t.Fatalf("Map called %d times, wanted 6", calls) } if after != 0 { t.Fatalf("Map called %d times (w/ after), wanted 0", after) } }) t.Run("just one call to root index w/ ErrSkipChildren", func(t *testing.T) { calls := 0 _, err := Map(context.Background(), sii, func(_ context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { calls++ if se != sii { t.Errorf("Wanted mutator called on %#v, got call on %#v", sii, se) } return se, ErrSkipChildren }) if err != nil { t.Fatalf("Map() = %v", err) } if calls != 1 { t.Fatalf("Map called %d times, wanted 1", calls) } }) t.Run("two calls to mutator with IsAfterChildren", func(t *testing.T) { before := 0 after := 0 rsi, err := Map(context.Background(), sii, func(ctx context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { if IsBeforeChildren(ctx) { before++ } if IsAfterChildren(ctx) { after++ } if sii, ok := se.(oci.SignedImageIndex); ok { return sii, nil } i, err := random.Image(300 /* bytes */, 3 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } return signed.Image(i), nil }) if err != nil { t.Fatalf("Map() = %v", err) } if rsi == sii { t.Fatalf("Map() = %#v, wanted something new!", rsi) } if before != 2 { t.Fatalf("Map called %d times (w/ before), wanted 2", before) } if after != 2 { t.Fatalf("Map called %d times (w/ after), wanted 2", after) } }) t.Run("test filtering images", func(t *testing.T) { rsi, err := Map(context.Background(), sii, func(_ context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { if _, ok := se.(oci.SignedImage); ok { return nil, nil } return se, nil }) if err != nil { t.Fatalf("Map() = %v", err) } if rsi == sii { t.Fatalf("Map() = %#v, wanted something new!", rsi) } // Make sure no images remain! im, err := rsi.(oci.SignedImageIndex).IndexManifest() if err != nil { t.Fatalf("IndexManifest() = %v", err) } for _, desc := range im.Manifests { if desc.MediaType == types.DockerManifestSchema2 { t.Error("Found an image media type!") } } }) t.Run("test filtering indices", func(t *testing.T) { rsi, err := Map(context.Background(), sii, func(ctx context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { if IsBeforeChildren(ctx) && se != sii { return nil, nil } return se, nil }) if err != nil { t.Fatalf("Map() = %v", err) } if rsi == sii { t.Fatalf("Map() = %#v, wanted something new!", rsi) } // Make sure no indices remain! im, err := rsi.(oci.SignedImageIndex).IndexManifest() if err != nil { t.Fatalf("IndexManifest() = %v", err) } for _, desc := range im.Manifests { if desc.MediaType != types.DockerManifestSchema2 { t.Errorf("MediaType = %s, wanted %s", desc.MediaType, types.DockerManifestSchema2) } } }) t.Run("error propagates from child image", func(t *testing.T) { want := errors.New("this is the error I expect") _, got := Map(context.Background(), sii, func(_ context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { if _, ok := se.(oci.SignedImage); !ok { return se, nil } return nil, want }) if !errors.Is(got, want) { t.Fatalf("Map() = %v, wanted %v", got, want) } }) t.Run("error propagates from child index", func(t *testing.T) { want := errors.New("this is the error I expect") _, got := Map(context.Background(), sii, func(ctx context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { if IsBeforeChildren(ctx) && se != sii { return nil, want } return se, nil }) if !errors.Is(got, want) { t.Fatalf("Map() = %v, wanted %v", got, want) } }) } cosign-2.5.0/pkg/oci/mutate/mutate.go000066400000000000000000000263541477503325500175050ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( "errors" "fmt" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/empty" "github.com/sigstore/cosign/v2/pkg/oci/signed" ) // Appendable is our signed version of mutate.Appendable type Appendable interface { oci.SignedEntity mutate.Appendable } // IndexAddendum is our signed version of mutate.IndexAddendum type IndexAddendum struct { Add Appendable v1.Descriptor } // AppendManifests is a form of mutate.AppendManifests that produces an // oci.SignedImageIndex. The index itself will contain no signatures, // but allows access to the contained signed entities. func AppendManifests(base v1.ImageIndex, adds ...IndexAddendum) oci.SignedImageIndex { madds := make([]mutate.IndexAddendum, 0, len(adds)) for _, add := range adds { madds = append(madds, mutate.IndexAddendum{ Add: add.Add, Descriptor: add.Descriptor, }) } return &indexWrapper{ v1Index: mutate.AppendManifests(base, madds...), ogbase: base, addendum: adds, } } // We alias ImageIndex so that we can inline it without the type // name colliding with the name of a method it had to implement. type v1Index v1.ImageIndex type indexWrapper struct { v1Index ogbase v1Index addendum []IndexAddendum } var _ oci.SignedImageIndex = (*indexWrapper)(nil) // Signatures implements oci.SignedImageIndex func (i *indexWrapper) Signatures() (oci.Signatures, error) { return empty.Signatures(), nil } // Attestations implements oci.SignedImageIndex func (i *indexWrapper) Attestations() (oci.Signatures, error) { return empty.Signatures(), nil } // Attachment implements oci.SignedImage func (*indexWrapper) Attachment(name string) (oci.File, error) { //nolint: revive return nil, errors.New("unimplemented") } // SignedImage implements oci.SignedImageIndex func (i *indexWrapper) SignedImage(h v1.Hash) (oci.SignedImage, error) { for _, add := range i.addendum { si, ok := add.Add.(oci.SignedImage) if !ok { continue } if d, err := si.Digest(); err != nil { return nil, err } else if d == h { return si, nil } } if sb, ok := i.ogbase.(oci.SignedImageIndex); ok { return sb.SignedImage(h) } unsigned, err := i.Image(h) if err != nil { return nil, err } return signed.Image(unsigned), nil } // SignedImageIndex implements oci.SignedImageIndex func (i *indexWrapper) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) { for _, add := range i.addendum { sii, ok := add.Add.(oci.SignedImageIndex) if !ok { continue } if d, err := sii.Digest(); err != nil { return nil, err } else if d == h { return sii, nil } } if sb, ok := i.ogbase.(oci.SignedImageIndex); ok { return sb.SignedImageIndex(h) } unsigned, err := i.ImageIndex(h) if err != nil { return nil, err } return signed.ImageIndex(unsigned), nil } // AttachSignatureToEntity attaches the provided signature to the provided entity. func AttachSignatureToEntity(se oci.SignedEntity, sig oci.Signature, opts ...SignOption) (oci.SignedEntity, error) { switch obj := se.(type) { case oci.SignedImage: return AttachSignatureToImage(obj, sig, opts...) case oci.SignedImageIndex: return AttachSignatureToImageIndex(obj, sig, opts...) default: return AttachSignatureToUnknown(obj, sig, opts...) } } // AttachAttestationToEntity attaches the provided attestation to the provided entity. func AttachAttestationToEntity(se oci.SignedEntity, att oci.Signature, opts ...SignOption) (oci.SignedEntity, error) { switch obj := se.(type) { case oci.SignedImage: return AttachAttestationToImage(obj, att, opts...) case oci.SignedImageIndex: return AttachAttestationToImageIndex(obj, att, opts...) default: return AttachAttestationToUnknown(obj, att, opts...) } } // AttachFileToEntity attaches the provided file to the provided entity. func AttachFileToEntity(se oci.SignedEntity, name string, f oci.File, opts ...SignOption) (oci.SignedEntity, error) { switch obj := se.(type) { case oci.SignedImage: return AttachFileToImage(obj, name, f, opts...) case oci.SignedImageIndex: return AttachFileToImageIndex(obj, name, f, opts...) default: return AttachFileToUnknown(obj, name, f, opts...) } } // AttachSignatureToImage attaches the provided signature to the provided image. func AttachSignatureToImage(si oci.SignedImage, sig oci.Signature, opts ...SignOption) (oci.SignedImage, error) { return &signedImage{ SignedImage: si, sig: sig, attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } // AttachAttestationToImage attaches the provided attestation to the provided image. func AttachAttestationToImage(si oci.SignedImage, att oci.Signature, opts ...SignOption) (oci.SignedImage, error) { return &signedImage{ SignedImage: si, att: att, attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } // AttachFileToImage attaches the provided file to the provided image. func AttachFileToImage(si oci.SignedImage, name string, f oci.File, opts ...SignOption) (oci.SignedImage, error) { return &signedImage{ SignedImage: si, attachments: map[string]oci.File{ name: f, }, so: makeSignOpts(opts...), }, nil } type signedImage struct { oci.SignedImage sig oci.Signature att oci.Signature so *signOpts attachments map[string]oci.File } // Signatures implements oci.SignedImage func (si *signedImage) Signatures() (oci.Signatures, error) { return si.so.dedupeAndReplace(si.sig, si.SignedImage.Signatures) } // Attestations implements oci.SignedImage func (si *signedImage) Attestations() (oci.Signatures, error) { return si.so.dedupeAndReplace(si.att, si.SignedImage.Attestations) } // Attachment implements oci.SignedImage func (si *signedImage) Attachment(attName string) (oci.File, error) { if f, ok := si.attachments[attName]; ok { return f, nil } return nil, fmt.Errorf("attachment %q not found", attName) } // AttachSignatureToImageIndex attaches the provided signature to the provided image index. func AttachSignatureToImageIndex(sii oci.SignedImageIndex, sig oci.Signature, opts ...SignOption) (oci.SignedImageIndex, error) { return &signedImageIndex{ ociSignedImageIndex: sii, sig: sig, attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } // AttachAttestationToImageIndex attaches the provided attestation to the provided image index. func AttachAttestationToImageIndex(sii oci.SignedImageIndex, att oci.Signature, opts ...SignOption) (oci.SignedImageIndex, error) { return &signedImageIndex{ ociSignedImageIndex: sii, att: att, attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } // AttachFileToImageIndex attaches the provided file to the provided image index. func AttachFileToImageIndex(sii oci.SignedImageIndex, name string, f oci.File, opts ...SignOption) (oci.SignedImageIndex, error) { return &signedImageIndex{ ociSignedImageIndex: sii, attachments: map[string]oci.File{ name: f, }, so: makeSignOpts(opts...), }, nil } type ociSignedImageIndex oci.SignedImageIndex type signedImageIndex struct { ociSignedImageIndex sig oci.Signature att oci.Signature so *signOpts attachments map[string]oci.File } // Signatures implements oci.SignedImageIndex func (sii *signedImageIndex) Signatures() (oci.Signatures, error) { return sii.so.dedupeAndReplace(sii.sig, sii.ociSignedImageIndex.Signatures) } // Attestations implements oci.SignedImageIndex func (sii *signedImageIndex) Attestations() (oci.Signatures, error) { return sii.so.dedupeAndReplace(sii.att, sii.ociSignedImageIndex.Attestations) } // Attachment implements oci.SignedImageIndex func (sii *signedImageIndex) Attachment(attName string) (oci.File, error) { if f, ok := sii.attachments[attName]; ok { return f, nil } return nil, fmt.Errorf("attachment %q not found", attName) } // AttachSignatureToUnknown attaches the provided signature to the provided image. func AttachSignatureToUnknown(se oci.SignedEntity, sig oci.Signature, opts ...SignOption) (oci.SignedEntity, error) { return &signedUnknown{ SignedEntity: se, sig: sig, attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } // AttachAttestationToUnknown attaches the provided attestation to the provided image. func AttachAttestationToUnknown(se oci.SignedEntity, att oci.Signature, opts ...SignOption) (oci.SignedEntity, error) { return &signedUnknown{ SignedEntity: se, att: att, attachments: make(map[string]oci.File), so: makeSignOpts(opts...), }, nil } // AttachFileToUnknown attaches the provided file to the provided image. func AttachFileToUnknown(se oci.SignedEntity, name string, f oci.File, opts ...SignOption) (oci.SignedEntity, error) { return &signedUnknown{ SignedEntity: se, attachments: map[string]oci.File{ name: f, }, so: makeSignOpts(opts...), }, nil } type signedUnknown struct { oci.SignedEntity sig oci.Signature att oci.Signature so *signOpts attachments map[string]oci.File } type digestable interface { Digest() (v1.Hash, error) } // Digest is generally implemented by oci.SignedEntity implementations. func (si *signedUnknown) Digest() (v1.Hash, error) { d, ok := si.SignedEntity.(digestable) if !ok { return v1.Hash{}, fmt.Errorf("underlying signed entity not digestable: %T", si.SignedEntity) } return d.Digest() } // Signatures implements oci.SignedEntity func (si *signedUnknown) Signatures() (oci.Signatures, error) { return si.so.dedupeAndReplace(si.sig, si.SignedEntity.Signatures) } // Attestations implements oci.SignedEntity func (si *signedUnknown) Attestations() (oci.Signatures, error) { return si.so.dedupeAndReplace(si.att, si.SignedEntity.Attestations) } // Attachment implements oci.SignedEntity func (si *signedUnknown) Attachment(attName string) (oci.File, error) { if f, ok := si.attachments[attName]; ok { return f, nil } return nil, fmt.Errorf("attachment %q not found", attName) } func (so *signOpts) dedupeAndReplace(sig oci.Signature, basefn func() (oci.Signatures, error)) (oci.Signatures, error) { base, err := basefn() if err != nil { return nil, err } else if sig == nil { return base, nil } if so.dd != nil { if existing, err := so.dd.Find(base, sig); err != nil { return nil, err } else if existing != nil { // Just return base if the signature is redundant return base, nil } } if so.ro != nil { replace, err := so.ro.Replace(base, sig) if err != nil { return nil, err } return ReplaceSignatures(replace) } return AppendSignatures(base, so.rct, sig) } cosign-2.5.0/pkg/oci/mutate/mutate_test.go000066400000000000000000000312601477503325500205340ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( "crypto/rand" "errors" "fmt" "reflect" "testing" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/signed" "github.com/sigstore/cosign/v2/pkg/oci/static" ) func TestAppendManifests(t *testing.T) { ii, err := random.Index(300 /* bytes */, 3 /* layers */, 5 /* images */) if err != nil { t.Fatalf("random.Index() = %v", err) } i2, err := random.Image(300 /* bytes */, 3 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } ii3, err := random.Index(300 /* bytes */, 3 /* layers */, 5 /* images */) if err != nil { t.Fatalf("random.Index() = %v", err) } ii = mutate.AppendManifests(ii, mutate.IndexAddendum{ Add: i2, }, mutate.IndexAddendum{ Add: ii3, }) ii2, err := random.Index(300 /* bytes */, 3 /* layers */, 5 /* images */) if err != nil { t.Fatalf("random.Index() = %v", err) } i1, err := random.Image(300 /* bytes */, 3 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } tests := []struct { name string fn func(v1.ImageIndex) v1.ImageIndex }{{ name: "unsigned", fn: func(in v1.ImageIndex) v1.ImageIndex { return in }, }, { name: "signed", fn: func(in v1.ImageIndex) v1.ImageIndex { return signed.ImageIndex(in) }, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { ni := AppendManifests(test.fn(ii), IndexAddendum{ Add: signed.Image(i1), }, IndexAddendum{ Add: signed.ImageIndex(ii2), }) if err != nil { t.Fatalf("AppendManifests() = %v", err) } im, err := ni.IndexManifest() if err != nil { t.Fatalf("IndexManifest() = %v", err) } if got, want := len(im.Manifests), 7+2; got != want { t.Errorf("len(im.Manifests) = %d, wanted %d", got, want) } if sigs, err := ni.Signatures(); err != nil { t.Errorf("Signatures() = %v", err) } else if sl, err := sigs.Get(); err != nil { t.Errorf("Get() = %v", err) } else if len(sl) != 0 { t.Errorf("len(Get()) = %d, wanted 0", len(sl)) } if atts, err := ni.Attestations(); err != nil { t.Errorf("Attestations() = %v", err) } else if al, err := atts.Get(); err != nil { t.Errorf("Get() = %v", err) } else if len(al) != 0 { t.Errorf("len(Get()) = %d, wanted 0", len(al)) } d1, err := i1.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } if _, err := ni.SignedImage(d1); err != nil { t.Fatalf("SignedImage() = %v", err) } d2, err := ii2.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } if _, err := ni.SignedImageIndex(d2); err != nil { t.Fatalf("SignedImageIndex() = %v", err) } if se, err := ni.SignedImage(d2); err == nil { t.Fatalf("SignedImage() = %#v, wanted error", se) } if se, err := ni.SignedImageIndex(d1); err == nil { t.Fatalf("SignedImageIndex() = %#v, wanted error", se) } d3, err := i2.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } if _, err := ni.SignedImage(d3); err != nil { t.Fatalf("SignedImage() = %v", err) } d4, err := ii3.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } if _, err := ni.SignedImageIndex(d4); err != nil { t.Fatalf("SignedImageIndex() = %v", err) } }) } } func TestSignEntity(t *testing.T) { i, err := random.Image(300 /* bytes */, 3 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } si := signed.Image(i) ii, err := random.Index(300 /* bytes */, 3 /* layers */, 5 /* images */) if err != nil { t.Fatalf("random.Index() = %v", err) } sii := signed.ImageIndex(ii) // Create an explicitly unknown implementation of oci.SignedEntity, which we // feed through the table tests below. want := make([]byte, 300) rand.Read(want) orig, err := static.NewFile(want) if err != nil { t.Fatalf("static.NewFile() = %v", err) } sunk, err := AttachFileToUnknown(sii, "sbom", orig) if err != nil { t.Fatalf("AttachFileToUnknown() = %v", err) } t.Run("attach SBOMs", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii, sunk} { want := make([]byte, 300) rand.Read(want) orig, err := static.NewFile(want) if err != nil { t.Fatalf("static.NewFile() = %v", err) } se, err = AttachFileToEntity(se, "sbom", orig) if err != nil { t.Fatalf("AttachFileToEntity() = %v", err) } f, err := se.Attachment("sbom") if err != nil { t.Fatalf("Attachment(sbom) = %v", err) } got, err := f.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if !reflect.DeepEqual(want, got) { t.Errorf("Payload() = %v, wanted %v", got, want) } f, err = se.Attachment("gitbom") if err == nil { t.Errorf("Attachment(gitbom) = %T, wanted error", f) } } }) t.Run("without duplicate detector (signature)", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii, sunk} { orig, err := static.NewSignature(nil, "") if err != nil { t.Fatalf("static.NewSignature() = %v", err) } se, err = AttachSignatureToEntity(se, orig) if err != nil { t.Fatalf("AttachSignatureToEntity() = %v", err) } for i := 2; i < 10; i++ { sig, err := static.NewSignature(nil, fmt.Sprintf("%d", i)) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } se, err = AttachSignatureToEntity(se, sig) if err != nil { t.Fatalf("AttachSignatureToEntity() = %v", err) } sigs, err := se.Signatures() if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if len(sl) != i { t.Errorf("len(Get()) = %d, wanted %d", len(sl), i) } } } }) t.Run("without duplicate detector (attestation)", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii, sunk} { orig, err := static.NewAttestation([]byte("payload")) if err != nil { t.Fatalf("static.NewAttestation() = %v", err) } se, err = AttachAttestationToEntity(se, orig) if err != nil { t.Fatalf("AttachAttestationToEntity() = %v", err) } for i := 2; i < 10; i++ { sig, err := static.NewAttestation([]byte(fmt.Sprintf("%d", i))) if err != nil { t.Fatalf("static.NewAttestation() = %v", err) } se, err = AttachAttestationToEntity(se, sig) if err != nil { t.Fatalf("AttachAttestationToEntity() = %v", err) } atts, err := se.Attestations() if err != nil { t.Fatalf("Attestations() = %v", err) } if al, err := atts.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if len(al) != i { t.Errorf("len(Get()) = %d, wanted %d", len(al), i) } } } }) t.Run("with duplicate detector (signature)", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii, sunk} { orig, err := static.NewSignature(nil, "") if err != nil { t.Fatalf("static.NewSignature() = %v", err) } se, err = AttachSignatureToEntity(se, orig) if err != nil { t.Fatalf("AttachSignatureToEntity() = %v", err) } dd := &dupe{ sig: orig, } for i := 2; i < 10; i++ { sig, err := static.NewSignature(nil, fmt.Sprintf("%d", i)) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } se, err = AttachSignatureToEntity(se, sig, WithDupeDetector(dd)) if err != nil { t.Fatalf("AttachSignatureToEntity() = %v", err) } sigs, err := se.Signatures() if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if len(sl) != 1 { t.Errorf("len(Get()) = %d, wanted %d", len(sl), i) } } } }) t.Run("with duplicate detector (attestation)", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii, sunk} { orig, err := static.NewAttestation([]byte("blah")) if err != nil { t.Fatalf("static.NewAttestation() = %v", err) } se, err = AttachAttestationToEntity(se, orig) if err != nil { t.Fatalf("AttachAttestationToEntity() = %v", err) } dd := &dupe{ sig: orig, } for i := 2; i < 10; i++ { sig, err := static.NewAttestation([]byte(fmt.Sprintf("%d", i))) if err != nil { t.Fatalf("static.NewAttestation() = %v", err) } se, err = AttachAttestationToEntity(se, sig, WithDupeDetector(dd)) if err != nil { t.Fatalf("AttachAttestationToEntity() = %v", err) } atts, err := se.Attestations() if err != nil { t.Fatalf("Attestations() = %v", err) } if al, err := atts.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if len(al) != 1 { t.Errorf("len(Get()) = %d, wanted %d", len(al), 1) } } } }) t.Run("with erroring duplicate detector (signature)", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii, sunk} { orig, err := static.NewSignature(nil, "") if err != nil { t.Fatalf("static.NewSignature() = %v", err) } se, err = AttachSignatureToEntity(se, orig) if err != nil { t.Fatalf("AttachSignatureToEntity() = %v", err) } want := errors.New("expected error") dd := &dupe{ err: want, } for i := 2; i < 10; i++ { sig, err := static.NewSignature(nil, fmt.Sprintf("%d", i)) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } se, err = AttachSignatureToEntity(se, sig, WithDupeDetector(dd)) if err != nil { t.Fatalf("AttachSignatureToEntity() = %v", err) } if _, got := se.Signatures(); !errors.Is(got, want) { t.Fatalf("Signatures() = %v, wanted %v", got, want) } } } }) t.Run("with erroring duplicate detector (attestation)", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii, sunk} { orig, err := static.NewAttestation([]byte("blah")) if err != nil { t.Fatalf("static.NewAttestation() = %v", err) } se, err = AttachAttestationToEntity(se, orig) if err != nil { t.Fatalf("AttachAttestationToEntity() = %v", err) } want := errors.New("expected error") dd := &dupe{ err: want, } for i := 2; i < 10; i++ { sig, err := static.NewAttestation([]byte(fmt.Sprintf("%d", i))) if err != nil { t.Fatalf("static.NewAttestation() = %v", err) } se, err = AttachAttestationToEntity(se, sig, WithDupeDetector(dd)) if err != nil { t.Fatalf("AttachAttestationToEntity() = %v", err) } if _, got := se.Attestations(); !errors.Is(got, want) { t.Fatalf("Attestations() = %v, wanted %v", got, want) } } } }) t.Run("with replace op (attestation)", func(t *testing.T) { for _, se := range []oci.SignedEntity{si, sii, sunk} { orig, err := static.NewAttestation([]byte("blah")) if err != nil { t.Fatalf("static.NewAttestation() = %v", err) } se, err = AttachAttestationToEntity(se, orig) if err != nil { t.Fatalf("AttachAttestationToEntity() = %v", err) } ro := &replaceAll{} for i := 2; i < 10; i++ { sig, err := static.NewAttestation([]byte(fmt.Sprintf("%d", i))) if err != nil { t.Fatalf("static.NewAttestation() = %v", err) } se, err = AttachAttestationToEntity(se, sig, WithReplaceOp(ro)) if err != nil { t.Fatalf("AttachAttestationToEntity() = %v", err) } atts, err := se.Attestations() if err != nil { t.Fatalf("Attestations() = %v", err) } if al, err := atts.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if len(al) != 1 { t.Errorf("len(Get()) = %d, wanted %d", len(al), 1) } } } }) } type dupe struct { sig oci.Signature err error } var _ DupeDetector = (*dupe)(nil) // Find implements DupeDetector func (d *dupe) Find(oci.Signatures, oci.Signature) (oci.Signature, error) { return d.sig, d.err } type replaceAll struct { } func (r *replaceAll) Replace(signatures oci.Signatures, o oci.Signature) (oci.Signatures, error) { return &replaceOCISignatures{ Signatures: signatures, attestations: []oci.Signature{o}, }, nil } type replaceOCISignatures struct { oci.Signatures attestations []oci.Signature } func (r *replaceOCISignatures) Get() ([]oci.Signature, error) { return r.attestations, nil } cosign-2.5.0/pkg/oci/mutate/options.go000066400000000000000000000060621477503325500176730ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" ) // DupeDetector scans a list of signatures looking for a duplicate. type DupeDetector interface { Find(oci.Signatures, oci.Signature) (oci.Signature, error) } type ReplaceOp interface { Replace(oci.Signatures, oci.Signature) (oci.Signatures, error) } type SignOption func(*signOpts) type signOpts struct { dd DupeDetector ro ReplaceOp rct bool } func makeSignOpts(opts ...SignOption) *signOpts { so := &signOpts{} for _, opt := range opts { opt(so) } return so } // WithDupeDetector configures Sign* to use the following DupeDetector // to avoid attaching duplicate signatures. func WithDupeDetector(dd DupeDetector) SignOption { return func(so *signOpts) { so.dd = dd } } func WithReplaceOp(ro ReplaceOp) SignOption { return func(so *signOpts) { so.ro = ro } } func WithRecordCreationTimestamp(rct bool) SignOption { return func(so *signOpts) { so.rct = rct } } type signatureOpts struct { annotations map[string]string bundle *bundle.RekorBundle rfc3161Timestamp *bundle.RFC3161Timestamp cert []byte chain []byte mediaType types.MediaType } type SignatureOption func(*signatureOpts) // WithAnnotations specifies the annotations the Signature should have. func WithAnnotations(annotations map[string]string) SignatureOption { return func(so *signatureOpts) { so.annotations = annotations } } // WithBundle specifies the new Bundle the Signature should have. func WithBundle(b *bundle.RekorBundle) SignatureOption { return func(so *signatureOpts) { so.bundle = b } } // WithRFC3161Timestamp specifies the new RFC3161Timestamp the Signature should have. func WithRFC3161Timestamp(b *bundle.RFC3161Timestamp) SignatureOption { return func(so *signatureOpts) { so.rfc3161Timestamp = b } } // WithCertChain specifies the new cert and chain the Signature should have. func WithCertChain(cert, chain []byte) SignatureOption { return func(so *signatureOpts) { so.cert = cert so.chain = chain } } // WithMediaType specifies the new MediaType the Signature should have. func WithMediaType(mediaType types.MediaType) SignatureOption { return func(so *signatureOpts) { so.mediaType = mediaType } } func makeSignatureOption(opts ...SignatureOption) *signatureOpts { so := &signatureOpts{} for _, opt := range opts { opt(so) } return so } cosign-2.5.0/pkg/oci/mutate/signature.go000066400000000000000000000130411477503325500201740ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( "bytes" "crypto/x509" "encoding/json" "fmt" "io" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" "github.com/sigstore/sigstore/pkg/cryptoutils" ) type sigWrapper struct { wrapped oci.Signature annotations map[string]string bundle *bundle.RekorBundle rfc3161Timestamp *bundle.RFC3161Timestamp cert *x509.Certificate chain []*x509.Certificate mediaType types.MediaType } var _ v1.Layer = (*sigWrapper)(nil) var _ oci.Signature = (*sigWrapper)(nil) func copyAnnotations(ann map[string]string) map[string]string { new := make(map[string]string, len(ann)) //nolint: revive for k, v := range ann { new[k] = v } return new } // Annotations implements oci.Signature. func (sw *sigWrapper) Annotations() (map[string]string, error) { if sw.annotations != nil { return copyAnnotations(sw.annotations), nil } return sw.wrapped.Annotations() } // Payload implements oci.Signature. func (sw *sigWrapper) Payload() ([]byte, error) { return sw.wrapped.Payload() } // Signature implements oci.Signature func (sw *sigWrapper) Signature() ([]byte, error) { return sw.wrapped.Signature() } // Base64Signature implements oci.Signature. func (sw *sigWrapper) Base64Signature() (string, error) { return sw.wrapped.Base64Signature() } // Cert implements oci.Signature. func (sw *sigWrapper) Cert() (*x509.Certificate, error) { if sw.cert != nil { return sw.cert, nil } return sw.wrapped.Cert() } // Chain implements oci.Signature. func (sw *sigWrapper) Chain() ([]*x509.Certificate, error) { if sw.chain != nil { return sw.chain, nil } return sw.wrapped.Chain() } // Bundle implements oci.Signature. func (sw *sigWrapper) Bundle() (*bundle.RekorBundle, error) { if sw.bundle != nil { return sw.bundle, nil } return sw.wrapped.Bundle() } // RFC3161Timestamp implements oci.Signature. func (sw *sigWrapper) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) { if sw.rfc3161Timestamp != nil { return sw.rfc3161Timestamp, nil } return sw.wrapped.RFC3161Timestamp() } // MediaType implements v1.Layer func (sw *sigWrapper) MediaType() (types.MediaType, error) { if sw.mediaType != "" { return sw.mediaType, nil } return sw.wrapped.MediaType() } // Digest implements v1.Layer func (sw *sigWrapper) Digest() (v1.Hash, error) { return sw.wrapped.Digest() } // DiffID implements v1.Layer func (sw *sigWrapper) DiffID() (v1.Hash, error) { return sw.wrapped.DiffID() } // Compressed implements v1.Layer func (sw *sigWrapper) Compressed() (io.ReadCloser, error) { return sw.wrapped.Compressed() } // Uncompressed implements v1.Layer func (sw *sigWrapper) Uncompressed() (io.ReadCloser, error) { return sw.wrapped.Uncompressed() } // Size implements v1.Layer func (sw *sigWrapper) Size() (int64, error) { return sw.wrapped.Size() } // Signature returns a new oci.Signature based on the provided original, plus the requested mutations. func Signature(original oci.Signature, opts ...SignatureOption) (oci.Signature, error) { newSig := sigWrapper{wrapped: original} so := makeSignatureOption(opts...) oldAnn, err := original.Annotations() if err != nil { return nil, fmt.Errorf("could not get annotations from signature to mutate: %w", err) } var newAnn map[string]string if so.annotations != nil { newAnn = copyAnnotations(so.annotations) newAnn[static.SignatureAnnotationKey] = oldAnn[static.SignatureAnnotationKey] for _, key := range []string{static.BundleAnnotationKey, static.CertificateAnnotationKey, static.ChainAnnotationKey, static.RFC3161TimestampAnnotationKey} { if val, isSet := oldAnn[key]; isSet { newAnn[key] = val } else { delete(newAnn, key) } } } else { newAnn = copyAnnotations(oldAnn) } if so.bundle != nil { newSig.bundle = so.bundle b, err := json.Marshal(so.bundle) if err != nil { return nil, err } newAnn[static.BundleAnnotationKey] = string(b) } if so.rfc3161Timestamp != nil { newSig.rfc3161Timestamp = so.rfc3161Timestamp b, err := json.Marshal(so.rfc3161Timestamp) if err != nil { return nil, err } newAnn[static.RFC3161TimestampAnnotationKey] = string(b) } if so.cert != nil { var cert *x509.Certificate var chain []*x509.Certificate certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(so.cert)) if err != nil { return nil, err } newAnn[static.CertificateAnnotationKey] = string(so.cert) cert = certs[0] delete(newAnn, static.ChainAnnotationKey) if so.chain != nil { chain, err = cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(so.chain)) if err != nil { return nil, err } newAnn[static.ChainAnnotationKey] = string(so.chain) } newSig.cert = cert newSig.chain = chain } if so.mediaType != "" { newSig.mediaType = so.mediaType } newSig.annotations = newAnn return &newSig, nil } cosign-2.5.0/pkg/oci/mutate/signature_test.go000066400000000000000000000344131477503325500212410ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( "encoding/base64" "io" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" ) var ( testCertBytes = []byte(` -----BEGIN CERTIFICATE----- MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt 6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G 3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG 0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY= -----END CERTIFICATE----- `) testChainBytes = []byte(` -----BEGIN CERTIFICATE----- MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== -----END CERTIFICATE----- `) ) func mustCreateSignature(t *testing.T, payload []byte, b64sig string, opts ...static.Option) oci.Signature { t.Helper() sig, err := static.NewSignature(payload, b64sig, opts...) if err != nil { t.Fatalf("failed to create static signature: %v", err) } return sig } func mustBase64Decode(t *testing.T, s string) []byte { t.Helper() b, err := base64.StdEncoding.DecodeString(s) if err != nil { t.Fatalf("failed to base64 decode: %v", err) } return b } func assertSignaturesEqual(t *testing.T, wanted, got oci.Signature) { t.Helper() t.Run("Payloads match", func(t *testing.T) { t.Helper() wantedPayload, err := wanted.Payload() if err != nil { t.Fatalf("wanted.Payload() returned error: %v", err) } gotPayload, err := got.Payload() if err != nil { t.Fatalf("got.Payload() returned error: %v", err) } if diff := cmp.Diff(wantedPayload, gotPayload); diff != "" { t.Errorf("Payload() mismatch (-want +got):\n%s", diff) } }) t.Run("Base64Signatures match", func(t *testing.T) { t.Helper() wantedB64Sig, err := wanted.Base64Signature() if err != nil { t.Fatalf("wanted.Base64Signature() returned error: %v", err) } gotB64Sig, err := got.Base64Signature() if err != nil { t.Fatalf("got.Base64Signature() returned error: %v", err) } if diff := cmp.Diff(wantedB64Sig, gotB64Sig); diff != "" { t.Errorf("Base64Signature() mismatch (-want +got):\n%s", diff) } }) t.Run("Bundles match", func(t *testing.T) { t.Helper() wantedBundle, err := wanted.Bundle() if err != nil { t.Fatalf("wanted.Bundle() returned error: %v", err) } gotBundle, err := got.Bundle() if err != nil { t.Fatalf("got.Bundle() returned error: %v", err) } if diff := cmp.Diff(wantedBundle, gotBundle); diff != "" { t.Errorf("Bundle() mismatch (-want +got):\n%s", diff) } }) t.Run("RFC3161 timestamp bundles match", func(t *testing.T) { t.Helper() wantedBundle, err := wanted.RFC3161Timestamp() if err != nil { t.Fatalf("wanted.Bundle() returned error: %v", err) } gotBundle, err := got.RFC3161Timestamp() if err != nil { t.Fatalf("got.RFC3161Timestamp() returned error: %v", err) } if diff := cmp.Diff(wantedBundle, gotBundle); diff != "" { t.Errorf("RFC3161Timestamp() mismatch (-want +got):\n%s", diff) } }) t.Run("Certs match", func(t *testing.T) { t.Helper() wantedCert, err := wanted.Cert() if err != nil { t.Fatalf("wanted.Bundle() returned error: %v", err) } gotCert, err := got.Cert() if err != nil { t.Fatalf("got.Cert() returned error: %v", err) } if diff := cmp.Diff(wantedCert, gotCert); diff != "" { t.Errorf("Cert() mismatch (-want +got):\n%s", diff) } }) t.Run("Chains match", func(t *testing.T) { t.Helper() wantedChain, err := wanted.Chain() if err != nil { t.Fatalf("wanted.Bundle() returned error: %v", err) } gotChain, err := got.Chain() if err != nil { t.Fatalf("got.Chain() returned error: %v", err) } if diff := cmp.Diff(wantedChain, gotChain); diff != "" { t.Errorf("Chain() mismatch (-want +got):\n%s", diff) } }) t.Run("MediaTypes match", func(t *testing.T) { t.Helper() wantedMediaType, err := wanted.MediaType() if err != nil { t.Fatalf("wanted.MediaType() returned error: %v", err) } gotMediaType, err := got.MediaType() if err != nil { t.Fatalf("got.MediaType() returned error: %v", err) } if diff := cmp.Diff(wantedMediaType, gotMediaType); diff != "" { t.Errorf("MediaType() mismatch (-want +got):\n%s", diff) } }) var gotAnnotations map[string]string t.Run("Annotations match", func(t *testing.T) { t.Helper() wantedAnnotations, err := wanted.Annotations() if err != nil { t.Fatalf("wanted.Annotations() returned error: %v", err) } gotAnnotations, err = got.Annotations() if err != nil { t.Fatalf("got.Annotations() returned error: %v", err) } if diff := cmp.Diff(wantedAnnotations, gotAnnotations); diff != "" { t.Errorf("Annotations() mismatch (-want +got):\n%s", diff) } }) t.Run("DiffIDs match", func(t *testing.T) { t.Helper() wantedDiffID, err := wanted.DiffID() if err != nil { t.Fatalf("wanted.DiffID() returned error: %v", err) } gotDiffID, err := got.DiffID() if err != nil { t.Fatalf("got.DiffID() returned error: %v", err) } if wantedDiffID != gotDiffID { t.Errorf("DiffID() mismatch. Wanted: %v, got: %v", wantedDiffID, gotDiffID) } }) t.Run("Sizes match", func(t *testing.T) { t.Helper() wantedSize, err := wanted.Size() if err != nil { t.Fatalf("wanted.Size() returned error: %v", err) } gotSize, err := got.Size() if err != nil { t.Fatalf("got.Size() returned error: %v", err) } if wantedSize != gotSize { t.Errorf("Size() mismatch. Wanted: %v, got: %v", wantedSize, gotSize) } }) t.Run("Compressed values match", func(t *testing.T) { t.Helper() wantedCompReader, err := wanted.Compressed() if err != nil { t.Fatalf("wanted.Compressed() returned error: %v", err) } defer wantedCompReader.Close() wantedCompressed, err := io.ReadAll(wantedCompReader) if err != nil { t.Fatalf("io.ReadAll(wanted.Compressed()) returned error: %v", err) } gotCompReader, err := got.Compressed() if err != nil { t.Fatalf("got.Compressed() returned error: %v", err) } defer gotCompReader.Close() gotCompressed, err := io.ReadAll(gotCompReader) if err != nil { t.Fatalf("io.ReadAll(got.Compressed()) returned error: %v", err) } if diff := cmp.Diff(wantedCompressed, gotCompressed); diff != "" { t.Errorf("MediaType() mismatch (-want +got):\n%s", diff) } }) t.Run("Uncompressed values match", func(t *testing.T) { t.Helper() wantedUncompReader, err := wanted.Uncompressed() if err != nil { t.Fatalf("wanted.Uncompressed() returned error: %v", err) } defer wantedUncompReader.Close() wantedUncompressed, err := io.ReadAll(wantedUncompReader) if err != nil { t.Fatalf("io.ReadAll(wanted.Uncompressed()) returned error: %v", err) } gotUncompReader, err := got.Uncompressed() if err != nil { t.Fatalf("got.Compressed() returned error: %v", err) } defer gotUncompReader.Close() gotUncompressed, err := io.ReadAll(gotUncompReader) if err != nil { t.Fatalf("io.ReadAll(got.Uncompressed()) returned error: %v", err) } if diff := cmp.Diff(wantedUncompressed, gotUncompressed); diff != "" { t.Errorf("MediaType() mismatch (-want +got):\n%s", diff) } }) } func TestSignatureWithAnnotations(t *testing.T) { payload := "this is the TestSignatureWithAnnotations content!" b64sig := "b64 content1=" annotations := map[string]string{ "foo": "bar", "test": "yes", } originalSig := mustCreateSignature(t, []byte(payload), b64sig) expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithAnnotations(annotations)) newSig, err := Signature(originalSig, WithAnnotations(annotations)) if err != nil { t.Fatalf("Signature(WithAnnotations()) returned error: %v", err) } assertSignaturesEqual(t, expectedSig, newSig) } func TestSignatureWithBundle(t *testing.T) { payload := "this is the TestSignatureWithBundle content!" b64sig := "b64 content2=" b := &bundle.RekorBundle{ SignedEntryTimestamp: mustBase64Decode(t, "MEUCIEDcarEwRYkrxE9ne+kzEVvUhnWaauYzxhUyXOLy1hwAAiEA4VdVCvNRs+D/5o33C2KBy+q2YX3lP4Y7nqRFU+K3hi0="), Payload: bundle.RekorPayload{ Body: "REMOVED", IntegratedTime: 1631646761, LogIndex: 693591, LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", }, } originalSig := mustCreateSignature(t, []byte(payload), b64sig) expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithBundle(b)) newSig, err := Signature(originalSig, WithBundle(b)) if err != nil { t.Fatalf("Signature(WithBundle()) returned error: %v", err) } assertSignaturesEqual(t, expectedSig, newSig) } func TestSignatureWithRFC3161Timestamp(t *testing.T) { payload := "this is the TestSignatureWithBundle content!" b64sig := "b64 content2=" b := &bundle.RFC3161Timestamp{ SignedRFC3161Timestamp: mustBase64Decode(t, "MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), } originalSig := mustCreateSignature(t, []byte(payload), b64sig) expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithRFC3161Timestamp(b)) newSig, err := Signature(originalSig, WithRFC3161Timestamp(b)) if err != nil { t.Fatalf("Signature(WithRFC3161Timestamp()) returned error: %v", err) } assertSignaturesEqual(t, expectedSig, newSig) } func TestSignatureWithCertChain(t *testing.T) { payload := "this is the TestSignatureWithCertChain content!" b64sig := "b64 content3=" originalSig := mustCreateSignature(t, []byte(payload), b64sig) expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithCertChain(testCertBytes, testChainBytes)) newSig, err := Signature(originalSig, WithCertChain(testCertBytes, testChainBytes)) if err != nil { t.Fatalf("Signature(WithCertChain()) returned error: %v", err) } assertSignaturesEqual(t, expectedSig, newSig) } func TestSignatureWithMediaType(t *testing.T) { payload := "this is the TestSignatureWithMediaType content!" b64sig := "b64 content4=" mediaType := types.MediaType("test/media.type") originalSig := mustCreateSignature(t, []byte(payload), b64sig) expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithLayerMediaType(mediaType)) newSig, err := Signature(originalSig, WithMediaType(mediaType)) if err != nil { t.Fatalf("Signature(WithMediaType()) returned error: %v", err) } assertSignaturesEqual(t, expectedSig, newSig) } func TestSignatureWithEverything(t *testing.T) { payload := "this is the TestSignatureWithEverything content!" b64sig := "b64 content5=" annotations := map[string]string{ "foo": "bar", "test": "yes", } b := &bundle.RekorBundle{ SignedEntryTimestamp: mustBase64Decode(t, "MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), Payload: bundle.RekorPayload{ Body: "REMOVED", IntegratedTime: 1631646761, LogIndex: 693591, LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", }, } mediaType := types.MediaType("test/media.type") originalSig := mustCreateSignature(t, []byte(payload), b64sig) expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithAnnotations(annotations), static.WithBundle(b), static.WithCertChain(testCertBytes, testChainBytes), static.WithLayerMediaType(mediaType)) newSig, err := Signature(originalSig, WithAnnotations(annotations), WithBundle(b), WithCertChain(testCertBytes, testChainBytes), WithMediaType(mediaType)) if err != nil { t.Fatalf("Signature(With...) returned error: %v", err) } assertSignaturesEqual(t, expectedSig, newSig) } func TestSignatureWithEverythingTSA(t *testing.T) { payload := "this is the TestSignatureWithEverything content!" b64sig := "b64 content5=" annotations := map[string]string{ "foo": "bar", "test": "yes", } b := &bundle.RFC3161Timestamp{ SignedRFC3161Timestamp: mustBase64Decode(t, "MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), } mediaType := types.MediaType("test/media.type") originalSig := mustCreateSignature(t, []byte(payload), b64sig) expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithAnnotations(annotations), static.WithRFC3161Timestamp(b), static.WithCertChain(testCertBytes, testChainBytes), static.WithLayerMediaType(mediaType)) newSig, err := Signature(originalSig, WithAnnotations(annotations), WithRFC3161Timestamp(b), WithCertChain(testCertBytes, testChainBytes), WithMediaType(mediaType)) if err != nil { t.Fatalf("Signature(With...) returned error: %v", err) } assertSignaturesEqual(t, expectedSig, newSig) } cosign-2.5.0/pkg/oci/mutate/signatures.go000066400000000000000000000055401477503325500203640ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/sigstore/cosign/v2/internal/pkg/now" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/empty" ) const maxLayers = 1000 // AppendSignatures produces a new oci.Signatures with the provided signatures // appended to the provided base signatures. func AppendSignatures(base oci.Signatures, recordCreationTimestamp bool, sigs ...oci.Signature) (oci.Signatures, error) { adds := make([]mutate.Addendum, 0, len(sigs)) for _, sig := range sigs { ann, err := sig.Annotations() if err != nil { return nil, err } adds = append(adds, mutate.Addendum{ Layer: sig, Annotations: ann, }) } img, err := mutate.Append(base, adds...) if err != nil { return nil, err } if recordCreationTimestamp { t, err := now.Now() if err != nil { return nil, err } // Set the Created date to time of execution img, err = mutate.CreatedAt(img, v1.Time{Time: t}) if err != nil { return nil, err } } return &sigAppender{ Image: img, base: base, sigs: sigs, }, nil } // ReplaceSignatures produces a new oci.Signatures provided by the base signatures // replaced with the new oci.Signatures. func ReplaceSignatures(base oci.Signatures) (oci.Signatures, error) { sigs, err := base.Get() if err != nil { return nil, err } adds := make([]mutate.Addendum, 0, len(sigs)) for _, sig := range sigs { ann, err := sig.Annotations() if err != nil { return nil, err } adds = append(adds, mutate.Addendum{ Layer: sig, Annotations: ann, }) } img, err := mutate.Append(empty.Signatures(), adds...) if err != nil { return nil, err } return &sigAppender{ Image: img, base: base, sigs: []oci.Signature{}, }, nil } type sigAppender struct { v1.Image base oci.Signatures sigs []oci.Signature } var _ oci.Signatures = (*sigAppender)(nil) // Get implements oci.Signatures func (sa *sigAppender) Get() ([]oci.Signature, error) { sl, err := sa.base.Get() if err != nil { return nil, err } sumLayers := int64(len(sl) + len(sa.sigs)) if sumLayers > maxLayers { return nil, oci.NewMaxLayersExceeded(sumLayers, maxLayers) } return append(sl, sa.sigs...), nil } cosign-2.5.0/pkg/oci/mutate/signatures_test.go000066400000000000000000000112611477503325500214200ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mutate import ( "errors" "testing" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/empty" "github.com/sigstore/cosign/v2/pkg/oci/static" ) func TestAppendSignatures(t *testing.T) { base := empty.Signatures() s1, err := static.NewSignature([]byte{}, "s1") if err != nil { t.Fatalf("NewSignature() = %v", err) } s2, err := static.NewSignature([]byte{}, "s2") if err != nil { t.Fatalf("NewSignature() = %v", err) } s3, err := static.NewSignature([]byte{}, "s3") if err != nil { t.Fatalf("NewSignature() = %v", err) } oneSig, err := AppendSignatures(base, false, s1) if err != nil { t.Fatalf("AppendSignatures() = %v", err) } twoSig, err := AppendSignatures(oneSig, false, s2) if err != nil { t.Fatalf("AppendSignatures() = %v", err) } threeSig, err := AppendSignatures(oneSig, true, s2, s3) if err != nil { t.Fatalf("AppendSignatures() = %v", err) } if sl, err := oneSig.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if got, want := len(sl), 1; got != want { t.Errorf("len(Get()) = %d, wanted %d", got, want) } if sl, err := twoSig.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if got, want := len(sl), 2; got != want { t.Errorf("len(Get()) = %d, wanted %d", got, want) } if sl, err := threeSig.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if got, want := len(sl), 3; got != want { t.Errorf("len(Get()) = %d, wanted %d", got, want) } if testCfg, err := threeSig.ConfigFile(); err != nil { t.Fatalf("ConfigFile() = %v", err) } else if testCfg.Created.IsZero() { t.Errorf("Date of Signature was Zero") } if testDefaultCfg, err := twoSig.ConfigFile(); err != nil { t.Fatalf("ConfigFile() = %v", err) } else if !testDefaultCfg.Created.IsZero() { t.Errorf("Date of Signature was Zero") } } func TestReplaceSignatures(t *testing.T) { base := empty.Signatures() s1, err := static.NewSignature([]byte{}, "s1") if err != nil { t.Fatalf("NewSignature() = %v", err) } oneSig, err := AppendSignatures(base, false, s1) if err != nil { t.Fatalf("AppendSignatures() = %v", err) } replaceSig, err := ReplaceSignatures(oneSig) if err != nil { t.Fatalf("ReplaceSignatures() = %v", err) } if sl, err := replaceSig.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if got, want := len(sl), 1; got != want { t.Errorf("len(Get()) = %d, wanted %d", got, want) } if mt, err := replaceSig.MediaType(); err != nil { t.Fatalf("MediaType() = %v", err) } else if got, want := mt, types.OCIManifestSchema1; got != want { t.Errorf("MediaType() = %v, wanted %v", got, want) } } func TestGet(t *testing.T) { tests := []struct { name string baseLayers int appendLayers int wantError error }{ { name: "within limit", baseLayers: 1, appendLayers: 1, wantError: nil, }, { name: "base exceeds limit", baseLayers: 2000, appendLayers: 1, wantError: errors.New("number of layers (2001) exceeded the limit (1000)"), }, { name: "append exceeds limit", baseLayers: 1, appendLayers: 1300, wantError: errors.New("number of layers (1301) exceeded the limit (1000)"), }, { name: "sum exceeds limit", baseLayers: 666, appendLayers: 666, wantError: errors.New("number of layers (1332) exceeded the limit (1000)"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { sa := sigAppender{ base: &mockOCISignatures{ signatures: make([]oci.Signature, test.baseLayers), }, sigs: make([]oci.Signature, test.appendLayers), } _, err := sa.Get() if test.wantError != nil && test.wantError.Error() != err.Error() { t.Fatalf("Get() = %v, wanted %v", err, test.wantError) } if test.wantError == nil && err != nil { t.Fatalf("Get() = %v, wanted %v", err, test.wantError) } }) } } type mockOCISignatures struct { v1.Image signatures []oci.Signature } func (m *mockOCISignatures) Get() ([]oci.Signature, error) { return m.signatures, nil } cosign-2.5.0/pkg/oci/platform/000077500000000000000000000000001477503325500161725ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/platform/platform.go000066400000000000000000000070521477503325500203510ustar00rootroot00000000000000// Copyright 2023 the Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package platform import ( "fmt" "strings" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/pkg/oci" ) type List []struct { Hash v1.Hash Platform *v1.Platform } func (pl *List) String() string { r := []string{} for _, p := range *pl { r = append(r, p.Platform.String()) } return strings.Join(r, ", ") } func GetIndexPlatforms(idx oci.SignedImageIndex) (List, error) { im, err := idx.IndexManifest() if err != nil { return nil, fmt.Errorf("fetching index manifest: %w", err) } platforms := List{} for _, m := range im.Manifests { if m.Platform == nil { continue } platforms = append(platforms, struct { Hash v1.Hash Platform *v1.Platform }{m.Digest, m.Platform}) } return platforms, nil } // matchPlatform filters a list of platforms returning only those matching // a base. "Based" on ko's internal equivalent while it moves to GGCR. // https://github.com/google/ko/blob/e6a7a37e26d82a8b2bb6df991c5a6cf6b2728794/pkg/build/gobuild.go#L1020 func matchPlatform(base *v1.Platform, list List) List { ret := List{} for _, p := range list { if base.OS != "" && base.OS != p.Platform.OS { continue } if base.Architecture != "" && base.Architecture != p.Platform.Architecture { continue } if base.Variant != "" && base.Variant != p.Platform.Variant { continue } if base.OSVersion != "" && p.Platform.OSVersion != base.OSVersion { if base.OS != "windows" { continue } else { //nolint: revive if pcount, bcount := strings.Count(base.OSVersion, "."), strings.Count(p.Platform.OSVersion, "."); pcount == 2 && bcount == 3 { if base.OSVersion != p.Platform.OSVersion[:strings.LastIndex(p.Platform.OSVersion, ".")] { continue } } else { continue } } } ret = append(ret, p) } return ret } func SignedEntityForPlatform(se oci.SignedEntity, platform string) (oci.SignedEntity, error) { if platform == "" { // Copy all platforms return se, nil } idx, isIndex := se.(oci.SignedImageIndex) // We only allow --platform on multiarch indexes if !isIndex { return nil, fmt.Errorf("specified reference is not a multiarch image") } targetPlatform, err := v1.ParsePlatform(platform) if err != nil { return nil, fmt.Errorf("parsing platform: %w", err) } platforms, err := GetIndexPlatforms(idx) if err != nil { return nil, fmt.Errorf("getting available platforms: %w", err) } platforms = matchPlatform(targetPlatform, platforms) if len(platforms) == 0 { return nil, fmt.Errorf("unable to find an entity for %s", targetPlatform.String()) } if len(platforms) > 1 { return nil, fmt.Errorf( "platform spec matches more than one image architecture: %s", platforms.String(), ) } nse, err := idx.SignedImage(platforms[0].Hash) if err != nil { return nil, fmt.Errorf("searching for %s image: %w", platforms[0].Hash.String(), err) } if nse == nil { return nil, fmt.Errorf("unable to find image %s", platforms[0].Hash.String()) } return nse, nil } cosign-2.5.0/pkg/oci/remote/000077500000000000000000000000001477503325500156415ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/remote/digest.go000066400000000000000000000022571477503325500174550ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "github.com/google/go-containerregistry/pkg/name" ) // ResolveDigest returns the digest of the image at the reference. // // If the reference is by digest already, it simply extracts the digest. // Otherwise, it looks up the digest from the registry. func ResolveDigest(ref name.Reference, opts ...Option) (name.Digest, error) { o := makeOptions(ref.Context(), opts...) if d, ok := ref.(name.Digest); ok { return d, nil } desc, err := remoteGet(ref, o.ROpt...) if err != nil { return name.Digest{}, err } return ref.Context().Digest(desc.Digest.String()), nil } cosign-2.5.0/pkg/oci/remote/digest_test.go000066400000000000000000000050511477503325500205070ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "errors" "testing" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" ) func TestResolveDigest(t *testing.T) { rg := remoteGet defer func() { remoteGet = rg }() tag := name.MustParseReference("gcr.io/distroless/static:nonroot") // As of 2021-09-20: // crane digest gcr.io/distroless/static:nonroot digest := name.MustParseReference("gcr.io/distroless/static@sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4") t.Run("digest doesn't call remote.Get", func(t *testing.T) { remoteGet = func(_ name.Reference, _ ...remote.Option) (*remote.Descriptor, error) { t.Fatal("ResolveDigest should not call remote.Get.") return nil, nil } got, err := ResolveDigest(digest) if err != nil { t.Fatalf("ResolveDigest() = %v", err) } if want := digest; got != want { t.Errorf("ResolveDigest() = %v, wanted %v", got, want) } }) t.Run("tag calls remote.Get", func(t *testing.T) { remoteGet = func(_ name.Reference, _ ...remote.Option) (*remote.Descriptor, error) { return &remote.Descriptor{ Descriptor: v1.Descriptor{ Digest: v1.Hash{ Algorithm: "sha256", // As of 2021-09-20: // crane digest gcr.io/distroless/static:nonroot Hex: "be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4", }, }, }, nil } got, err := ResolveDigest(tag) if err != nil { t.Fatalf("ResolveDigest() = %v", err) } if want := digest; got != want { t.Errorf("ResolveDigest() = %v, wanted %v", got, want) } }) t.Run("remote.Get errors propagate", func(t *testing.T) { want := errors.New("we should propagate this error") remoteGet = func(_ name.Reference, _ ...remote.Option) (*remote.Descriptor, error) { return nil, want } _, got := ResolveDigest(tag) if !errors.Is(got, want) { t.Fatalf("ResolveDigest() = %v, wanted %v", got, want) } }) } cosign-2.5.0/pkg/oci/remote/image.go000066400000000000000000000043121477503325500172520ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "errors" "net/http" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/sigstore/cosign/v2/pkg/oci" ) var ErrImageNotFound = errors.New("image not found in registry") // SignedImage provides access to a remote image reference, and its signatures. func SignedImage(ref name.Reference, options ...Option) (oci.SignedImage, error) { o := makeOptions(ref.Context(), options...) ri, err := remoteImage(ref, o.ROpt...) var te *transport.Error if errors.As(err, &te) && te.StatusCode == http.StatusNotFound { return nil, ErrImageNotFound } else if err != nil { return nil, err } return &image{ Image: ri, opt: o, }, nil } type image struct { v1.Image opt *options } // The wrapped Image implements ConfigLayer, but the wrapping hides that from typechecks in pkg/v1/remote. // Make image explicitly implement ConfigLayer so that this returns a mountable config layer for pkg/v1/remote. func (i *image) ConfigLayer() (v1.Layer, error) { return partial.ConfigLayer(i.Image) } var _ oci.SignedImage = (*image)(nil) // Signatures implements oci.SignedImage func (i *image) Signatures() (oci.Signatures, error) { return signatures(i, i.opt) } // Attestations implements oci.SignedImage func (i *image) Attestations() (oci.Signatures, error) { return attestations(i, i.opt) } // Attestations implements oci.SignedImage func (i *image) Attachment(name string) (oci.File, error) { return attachment(i, name, i.opt) } cosign-2.5.0/pkg/oci/remote/image_test.go000066400000000000000000000054211477503325500203130ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "testing" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" ) func TestSignedImage(t *testing.T) { ri := remote.Image t.Cleanup(func() { remoteImage = ri }) wantLayers := int64(7) remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { // Only called for signature images return random.Image(300 /* byteSize */, wantLayers) } ref, err := name.ParseReference("gcr.io/distroless/static:nonroot") if err != nil { t.Fatalf("ParseRef() = %v", err) } si, err := SignedImage(ref) if err != nil { t.Fatalf("Signatures() = %v", err) } sigs, err := si.Signatures() if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got := int64(len(sl)); got != wantLayers { t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers) } atts, err := si.Attestations() if err != nil { t.Fatalf("Signatures() = %v", err) } if al, err := atts.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got := int64(len(al)); got != wantLayers { t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers) } } func TestSignedImageWithAttachment(t *testing.T) { ri := remote.Image t.Cleanup(func() { remoteImage = ri }) wantLayers := int64(1) // File must have a single layer remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { // Only called for signature images return random.Image(300 /* byteSize */, wantLayers) } ref, err := name.ParseReference("gcr.io/distroless/static:nonroot") if err != nil { t.Fatalf("ParseRef() = %v", err) } si, err := SignedImage(ref) if err != nil { t.Fatalf("Signatures() = %v", err) } file, err := si.Attachment("sbom") if err != nil { t.Fatalf("Signatures() = %v", err) } payload, err := file.Payload() if err != nil { t.Errorf("Payload() = %v", err) } // We check greater than because it's wrapped in a tarball with `random.Layer` if len(payload) < 300 { t.Errorf("Payload() = %d bytes, wanted %d", len(payload), 300) } } cosign-2.5.0/pkg/oci/remote/index.go000066400000000000000000000047741477503325500173130ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "errors" "net/http" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/sigstore/cosign/v2/pkg/oci" ) // SignedImageIndex provides access to a remote index reference, and its signatures. func SignedImageIndex(ref name.Reference, options ...Option) (oci.SignedImageIndex, error) { o := makeOptions(ref.Context(), options...) ri, err := remoteIndex(ref, o.ROpt...) var te *transport.Error if errors.As(err, &te) && te.StatusCode == http.StatusNotFound { return nil, errors.New("index not found in registry") } else if err != nil { return nil, err } return &index{ v1Index: ri, ref: ref, opt: o, }, nil } // We alias ImageIndex so that we can inline it without the type // name colliding with the name of a method it had to implement. type v1Index v1.ImageIndex type index struct { v1Index ref name.Reference opt *options } var _ oci.SignedImageIndex = (*index)(nil) // Signatures implements oci.SignedImageIndex func (i *index) Signatures() (oci.Signatures, error) { return signatures(i, i.opt) } // Attestations implements oci.SignedImageIndex func (i *index) Attestations() (oci.Signatures, error) { return attestations(i, i.opt) } // Attestations implements oci.SignedImage func (i *index) Attachment(name string) (oci.File, error) { return attachment(i, name, i.opt) } // SignedImage implements oci.SignedImageIndex func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) { img, err := i.Image(h) if err != nil { return nil, err } return &image{ Image: img, opt: i.opt, }, nil } // SignedImageIndex implements oci.SignedImageIndex func (i *index) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) { ii, err := i.ImageIndex(h) if err != nil { return nil, err } return &index{ v1Index: ii, opt: i.opt, }, nil } cosign-2.5.0/pkg/oci/remote/index_test.go000066400000000000000000000067631477503325500203520ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "testing" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/oci" ) func TestSignedImageIndex(t *testing.T) { ri := remote.Image rix := remote.Index t.Cleanup(func() { remoteImage = ri remoteIndex = rix }) wantLayers := int64(7) wantImages := int64(1) l1, err := random.Image(300 /* byteSize */, wantLayers) if err != nil { t.Fatalf("random.Index() = %v", err) } l2, err := random.Index(300 /* byteSize */, wantLayers, wantImages) if err != nil { t.Fatalf("random.Index() = %v", err) } l3 := mutate.AppendManifests( empty.Index, mutate.IndexAddendum{ Add: l2, }, mutate.IndexAddendum{ Add: l1, }, ) remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { // Only called for signature images return random.Image(300 /* byteSize */, wantLayers) } remoteIndex = func(_ name.Reference, _ ...remote.Option) (ii v1.ImageIndex, err error) { return l3, nil } ref, err := name.ParseReference("gcr.io/distroless/static:nonroot") if err != nil { t.Fatalf("ParseRef() = %v", err) } sii, err := SignedImageIndex(ref) if err != nil { t.Fatalf("Signatures() = %v", err) } sigs, err := sii.Signatures() if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got := int64(len(sl)); got != wantLayers { t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers) } imf, err := sii.IndexManifest() if err != nil { t.Fatalf("IndexManifest() = %v", err) } for _, desc := range imf.Manifests { var se oci.SignedEntity switch desc.MediaType { case types.OCIImageIndex, types.DockerManifestList: se, err = sii.SignedImageIndex(desc.Digest) if err != nil { t.Fatalf("SignedImage() = %v", err) } case types.OCIManifestSchema1, types.DockerManifestSchema2: se, err = sii.SignedImage(desc.Digest) if err != nil { t.Fatalf("SignedImage() = %v", err) } default: t.Fatalf("unknown mime type: %v", desc.MediaType) } sigs, err := se.Signatures() if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got := int64(len(sl)); got != wantLayers { t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers) } atts, err := se.Attestations() if err != nil { t.Fatalf("Signatures() = %v", err) } if al, err := atts.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got := int64(len(al)); got != wantLayers { t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers) } } } cosign-2.5.0/pkg/oci/remote/options.go000066400000000000000000000101511477503325500176610ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "fmt" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) const ( SignatureTagSuffix = "sig" SBOMTagSuffix = "sbom" AttestationTagSuffix = "att" CustomTagPrefix = "" RepoOverrideEnvKey = "COSIGN_REPOSITORY" ) // Option is a functional option for remote operations. type Option func(*options) type options struct { SignatureSuffix string AttestationSuffix string SBOMSuffix string TagPrefix string TargetRepository name.Repository ROpt []remote.Option NameOpts []name.Option OriginalOptions []Option } var defaultOptions = []remote.Option{ remote.WithAuthFromKeychain(authn.DefaultKeychain), // TODO(mattmoor): Incorporate user agent. } func makeOptions(target name.Repository, opts ...Option) *options { o := &options{ SignatureSuffix: SignatureTagSuffix, AttestationSuffix: AttestationTagSuffix, SBOMSuffix: SBOMTagSuffix, TagPrefix: CustomTagPrefix, TargetRepository: target, ROpt: defaultOptions, // Keep the original options around for things that want // to call something that takes options! OriginalOptions: opts, } for _, option := range opts { option(o) } return o } // WithPrefix is a functional option for overriding the default // tag prefix. func WithPrefix(prefix string) Option { return func(o *options) { o.TagPrefix = prefix } } // WithSignatureSuffix is a functional option for overriding the default // signature tag suffix. func WithSignatureSuffix(suffix string) Option { return func(o *options) { o.SignatureSuffix = suffix } } // WithAttestationSuffix is a functional option for overriding the default // attestation tag suffix. func WithAttestationSuffix(suffix string) Option { return func(o *options) { o.AttestationSuffix = suffix } } // WithSBOMSuffix is a functional option for overriding the default // SBOM tag suffix. func WithSBOMSuffix(suffix string) Option { return func(o *options) { o.SBOMSuffix = suffix } } // WithRemoteOptions is a functional option for overriding the default // remote options passed to GGCR. func WithRemoteOptions(opts ...remote.Option) Option { return func(o *options) { o.ROpt = opts } } // WithMoreRemoteOptions is a functional option for adding to the default // remote options already specified func WithMoreRemoteOptions(opts ...remote.Option) Option { return func(o *options) { o.ROpt = append(o.ROpt, opts...) } } // WithTargetRepository is a functional option for overriding the default // target repository hosting the signature and attestation tags. func WithTargetRepository(repo name.Repository) Option { return func(o *options) { o.TargetRepository = repo } } // GetEnvTargetRepository returns the Repository specified by // `os.Getenv(RepoOverrideEnvKey)`, or the empty value if not set. // Returns an error if the value is set but cannot be parsed. func GetEnvTargetRepository() (name.Repository, error) { if ro := env.Getenv(env.VariableRepository); ro != "" { repo, err := name.NewRepository(ro) if err != nil { return name.Repository{}, fmt.Errorf("parsing $"+RepoOverrideEnvKey+": %w", err) } return repo, nil } return name.Repository{}, nil } // WithNameOptions is a functional option for overriding the default // name options passed to GGCR. func WithNameOptions(opts ...name.Option) Option { return func(o *options) { o.NameOpts = opts } } cosign-2.5.0/pkg/oci/remote/options_test.go000066400000000000000000000133461477503325500207310ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "context" "errors" "os" "reflect" "testing" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" ) func TestOptions(t *testing.T) { repo, err := name.NewRepository("gcr.io/projectsigstore") if err != nil { t.Errorf("NewRepository() = %v", err) } overrideRepo, err := name.NewRepository("gcr.io/distroless") if err != nil { t.Errorf("NewRepository() = %v", err) } otherROpt := []remote.Option{ remote.WithAuthFromKeychain(authn.DefaultKeychain), // TODO(mattmoor): Incorporate user agent. } moreROpt := []remote.Option{ remote.WithContext(context.Background()), } tests := []struct { name string opts []Option want *options }{{ name: "no options", want: &options{ SignatureSuffix: SignatureTagSuffix, AttestationSuffix: AttestationTagSuffix, SBOMSuffix: SBOMTagSuffix, TargetRepository: repo, ROpt: defaultOptions, }, }, { name: "signature option", opts: []Option{WithSignatureSuffix("pig")}, want: &options{ SignatureSuffix: "pig", AttestationSuffix: AttestationTagSuffix, SBOMSuffix: SBOMTagSuffix, TargetRepository: repo, ROpt: defaultOptions, }, }, { name: "attestation option", opts: []Option{WithAttestationSuffix("pig")}, want: &options{ SignatureSuffix: SignatureTagSuffix, AttestationSuffix: "pig", SBOMSuffix: SBOMTagSuffix, TargetRepository: repo, ROpt: defaultOptions, }, }, { name: "sbom option", opts: []Option{WithSBOMSuffix("pig")}, want: &options{ SignatureSuffix: SignatureTagSuffix, AttestationSuffix: AttestationTagSuffix, SBOMSuffix: "pig", TargetRepository: repo, ROpt: defaultOptions, }, }, { name: "target repo option", opts: []Option{WithTargetRepository(overrideRepo)}, want: &options{ SignatureSuffix: SignatureTagSuffix, AttestationSuffix: AttestationTagSuffix, SBOMSuffix: SBOMTagSuffix, TargetRepository: overrideRepo, ROpt: defaultOptions, }, }, { name: "remote options option", opts: []Option{WithRemoteOptions(otherROpt...)}, want: &options{ SignatureSuffix: SignatureTagSuffix, AttestationSuffix: AttestationTagSuffix, SBOMSuffix: SBOMTagSuffix, TargetRepository: repo, ROpt: otherROpt, }, }, { name: "more remote options option", opts: []Option{WithRemoteOptions(otherROpt...), WithMoreRemoteOptions(moreROpt...)}, want: &options{ SignatureSuffix: SignatureTagSuffix, AttestationSuffix: AttestationTagSuffix, SBOMSuffix: SBOMTagSuffix, TargetRepository: repo, ROpt: append(append([]remote.Option{}, otherROpt...), moreROpt...), }, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { got := makeOptions(repo, test.opts...) test.want.OriginalOptions = test.opts if !optionsEqual(got, test.want) { t.Errorf("makeOptions() = %#v, wanted %#v", got, test.want) } }) } } func TestGetEnvTargetRepository(t *testing.T) { tests := []struct { desc string envVal string want name.Repository wantErr error }{ { desc: "good", envVal: "gcr.io/distroless", want: name.MustParseReference("gcr.io/distroless").Context(), }, { desc: "bad", envVal: "bad$repo", wantErr: errors.New("parsing $COSIGN_REPOSITORY: repository can only contain the characters `abcdefghijklmnopqrstuvwxyz0123456789_-./`: bad$repo"), }, { desc: "empty", envVal: "", want: name.Repository{}, }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { ev := os.Getenv("COSIGN_REPOSITORY") defer os.Setenv("COSIGN_REPOSITORY", ev) os.Setenv("COSIGN_REPOSITORY", tc.envVal) got, err := GetEnvTargetRepository() if !errors.Is(err, tc.wantErr) { if tc.wantErr == nil || err == nil || tc.wantErr.Error() != err.Error() { t.Fatalf("GetEnvTargetRepository() returned error %v, wanted %v", err, tc.wantErr) } return } if tc.want != got { t.Errorf("GetEnvTargetRepository() returned %#v, wanted %#v", got, tc.want) } }) } } // this is required due to the fact that reflect.DeepEquals reports // two different slices of function points, with identical length and // contents at each position as being different func optionsEqual(o1, o2 *options) bool { if (o1 == nil) != (o2 == nil) { return false } if o1 == nil { return true } if o1.AttestationSuffix != o2.AttestationSuffix { return false } if o1.SignatureSuffix != o2.SignatureSuffix { return false } if o1.SBOMSuffix != o2.SBOMSuffix { return false } if o1.TagPrefix != o2.TagPrefix { return false } if !slicesEqual(o1.ROpt, o2.ROpt) { return false } if !slicesEqual(o1.NameOpts, o2.NameOpts) { return false } if !slicesEqual(o1.OriginalOptions, o2.OriginalOptions) { return false } return true } func slicesEqual[T any](o1, o2 []T) bool { if len(o1) != len(o2) { return false } for i := range o1 { v1 := reflect.ValueOf(o1[i]) v2 := reflect.ValueOf(o2[i]) if v1 != v2 { return false } } return true } cosign-2.5.0/pkg/oci/remote/referrers.go000066400000000000000000000022741477503325500201740ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" ) // Referrers fetches references using registry options. func Referrers(d name.Digest, artifactType string, opts ...Option) (*v1.IndexManifest, error) { o := makeOptions(name.Repository{}, opts...) rOpt := o.ROpt if artifactType != "" { rOpt = append(rOpt, remote.WithFilter("artifactType", artifactType)) } idx, err := remote.Referrers(d, rOpt...) if err != nil { return nil, err } return idx.IndexManifest() } cosign-2.5.0/pkg/oci/remote/remote.go000066400000000000000000000216701477503325500174710ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "errors" "fmt" "io" "net/http" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/google/go-containerregistry/pkg/v1/types" payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size" ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci" ) // These enable mocking for unit testing without faking an entire registry. var ( remoteImage = remote.Image remoteIndex = remote.Index remoteGet = remote.Get remoteWrite = remote.Write ) // EntityNotFoundError is the error that SignedEntity returns when the // provided ref does not exist. type EntityNotFoundError struct { baseErr error } func (e *EntityNotFoundError) Error() string { return fmt.Sprintf("entity not found in registry, error: %v", e.baseErr) } func NewEntityNotFoundError(err error) error { return &EntityNotFoundError{ baseErr: err, } } // SignedEntity provides access to a remote reference, and its signatures. // The SignedEntity will be one of SignedImage or SignedImageIndex. func SignedEntity(ref name.Reference, options ...Option) (oci.SignedEntity, error) { o := makeOptions(ref.Context(), options...) got, err := remoteGet(ref, o.ROpt...) var te *transport.Error if errors.As(err, &te) && te.StatusCode == http.StatusNotFound { return nil, NewEntityNotFoundError(err) } else if err != nil { return nil, err } switch got.MediaType { case types.OCIImageIndex, types.DockerManifestList: ii, err := got.ImageIndex() if err != nil { return nil, err } return &index{ v1Index: ii, ref: ref.Context().Digest(got.Digest.String()), opt: o, }, nil case types.OCIManifestSchema1, types.DockerManifestSchema2: i, err := got.Image() if err != nil { return nil, err } return &image{ Image: i, opt: o, }, nil default: return nil, fmt.Errorf("unknown mime type: %v", got.MediaType) } } // normalize turns image digests into tags with optional prefix & suffix: // sha256:d34db33f -> [prefix]sha256-d34db33f[.suffix] func normalize(h v1.Hash, prefix string, suffix string) string { return normalizeWithSeparator(h, prefix, suffix, "-") } // normalizeWithSeparator turns image digests into tags with optional prefix & suffix: // sha256:d34db33f -> [prefix]sha256[algorithmSeparator]d34db33f[.suffix] func normalizeWithSeparator(h v1.Hash, prefix string, suffix string, algorithmSeparator string) string { if suffix == "" { return fmt.Sprint(prefix, h.Algorithm, algorithmSeparator, h.Hex) } return fmt.Sprint(prefix, h.Algorithm, algorithmSeparator, h.Hex, ".", suffix) } // SignatureTag returns the name.Tag that associated signatures with a particular digest. func SignatureTag(ref name.Reference, opts ...Option) (name.Tag, error) { o := makeOptions(ref.Context(), opts...) return suffixTag(ref, o.SignatureSuffix, "-", o) } // AttestationTag returns the name.Tag that associated attestations with a particular digest. func AttestationTag(ref name.Reference, opts ...Option) (name.Tag, error) { o := makeOptions(ref.Context(), opts...) return suffixTag(ref, o.AttestationSuffix, "-", o) } // SBOMTag returns the name.Tag that associated SBOMs with a particular digest. func SBOMTag(ref name.Reference, opts ...Option) (name.Tag, error) { o := makeOptions(ref.Context(), opts...) return suffixTag(ref, o.SBOMSuffix, "-", o) } // DigestTag returns the name.Tag that associated SBOMs with a particular digest. func DigestTag(ref name.Reference, opts ...Option) (name.Tag, error) { o := makeOptions(ref.Context(), opts...) return suffixTag(ref, "", ":", o) } // DockerContentDigest fetches the Docker-Content-Digest header for the referenced tag, // which is required to delete the object in registry API v2.3 and greater. // See https://github.com/distribution/distribution/blob/main/docs/content/spec/api.md#deleting-an-image // and https://github.com/distribution/distribution/issues/1579 func DockerContentDigest(ref name.Tag, opts ...Option) (name.Tag, error) { o := makeOptions(ref.Context(), opts...) desc, err := remoteGet(ref, o.ROpt...) if err != nil { return name.Tag{}, err } h := desc.Digest return o.TargetRepository.Tag(normalizeWithSeparator(h, o.TagPrefix, "", ":")), nil } func suffixTag(ref name.Reference, suffix string, algorithmSeparator string, o *options) (name.Tag, error) { var h v1.Hash if digest, ok := ref.(name.Digest); ok { var err error h, err = v1.NewHash(digest.DigestStr()) if err != nil { // This is effectively impossible. return name.Tag{}, err } } else { desc, err := remoteGet(ref, o.ROpt...) if err != nil { return name.Tag{}, err } h = desc.Digest } return o.TargetRepository.Tag(normalizeWithSeparator(h, o.TagPrefix, suffix, algorithmSeparator)), nil } // signatures is a shared implementation of the oci.Signed* Signatures method. func signatures(digestable oci.SignedEntity, o *options) (oci.Signatures, error) { h, err := digestable.Digest() if err != nil { return nil, err } return Signatures(o.TargetRepository.Tag(normalize(h, o.TagPrefix, o.SignatureSuffix)), o.OriginalOptions...) } // attestations is a shared implementation of the oci.Signed* Attestations method. func attestations(digestable oci.SignedEntity, o *options) (oci.Signatures, error) { h, err := digestable.Digest() if err != nil { return nil, err } return Signatures(o.TargetRepository.Tag(normalize(h, o.TagPrefix, o.AttestationSuffix)), o.OriginalOptions...) } // attachment is a shared implementation of the oci.Signed* Attachment method. func attachment(digestable oci.SignedEntity, attName string, o *options) (oci.File, error) { // Try using OCI 1.1 behavior if file, err := attachmentExperimentalOCI(digestable, attName, o); err == nil { return file, nil } h, err := digestable.Digest() if err != nil { return nil, err } img, err := SignedImage(o.TargetRepository.Tag(normalize(h, o.TagPrefix, attName)), o.OriginalOptions...) if err != nil { return nil, err } ls, err := img.Layers() if err != nil { return nil, err } if len(ls) != 1 { return nil, fmt.Errorf("expected exactly one layer in attachment, got %d", len(ls)) } return &attached{ SignedImage: img, layer: ls[0], }, nil } type attached struct { oci.SignedImage layer v1.Layer } var _ oci.File = (*attached)(nil) // FileMediaType implements oci.File func (f *attached) FileMediaType() (types.MediaType, error) { return f.layer.MediaType() } // Payload implements oci.File func (f *attached) Payload() ([]byte, error) { size, err := f.layer.Size() if err != nil { return nil, err } err = payloadsize.CheckSize(uint64(size)) if err != nil { return nil, err } // remote layers are believed to be stored // compressed, but we don't compress attachments // so use "Compressed" to access the raw byte // stream. rc, err := f.layer.Compressed() if err != nil { return nil, err } defer rc.Close() return io.ReadAll(rc) } // attachmentExperimentalOCI is a shared implementation of the oci.Signed* Attachment method (for OCI 1.1+ behavior). func attachmentExperimentalOCI(digestable oci.SignedEntity, attName string, o *options) (oci.File, error) { h, err := digestable.Digest() if err != nil { return nil, err } d := o.TargetRepository.Digest(h.String()) artifactType := ociexperimental.ArtifactType(attName) index, err := Referrers(d, artifactType, o.OriginalOptions...) if err != nil { return nil, err } results := index.Manifests numResults := len(results) if numResults == 0 { return nil, fmt.Errorf("unable to locate reference with artifactType %s", artifactType) } else if numResults > 1 { // TODO: if there is more than 1 result.. what does that even mean? // TODO: use ui.Warn fmt.Printf("WARNING: there were a total of %d references with artifactType %s\n", numResults, artifactType) } // TODO: do this smarter using "created" annotations lastResult := results[numResults-1] img, err := SignedImage(o.TargetRepository.Digest(lastResult.Digest.String()), o.OriginalOptions...) if err != nil { return nil, err } ls, err := img.Layers() if err != nil { return nil, err } if len(ls) != 1 { return nil, fmt.Errorf("expected exactly one layer in attachment, got %d", len(ls)) } return &attached{ SignedImage: img, layer: ls[0], }, nil } cosign-2.5.0/pkg/oci/remote/remote_test.go000066400000000000000000000220071477503325500205230ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "errors" "io" "strings" "testing" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/types" ) func TestTagMethods(t *testing.T) { rg := remoteGet defer func() { remoteGet = rg }() remoteGet = func(_ name.Reference, _ ...remote.Option) (*remote.Descriptor, error) { return &remote.Descriptor{ Descriptor: v1.Descriptor{ Digest: v1.Hash{ Algorithm: "sha256", // As of 2021-09-20: // crane digest gcr.io/distroless/static:nonroot Hex: "be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4", }, }, }, nil } tests := []struct { name string fn func(name.Reference, ...Option) (name.Tag, error) ref name.Reference opts []Option want name.Reference // Always a tag, but shorter to write things. }{{ name: "signature passed a tag", fn: SignatureTag, ref: name.MustParseReference("gcr.io/distroless/static:nonroot"), want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.sig"), }, { name: "signature passed a tag (w/ custom suffix)", fn: SignatureTag, ref: name.MustParseReference("gcr.io/distroless/static:nonroot"), opts: []Option{WithSignatureSuffix("snowflake")}, want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.snowflake"), }, { name: "signature passed a digest", fn: SignatureTag, ref: name.MustParseReference("gcr.io/distroless/static@sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4"), want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.sig"), }, { name: "attestation passed a tag", fn: AttestationTag, ref: name.MustParseReference("gcr.io/distroless/static:nonroot"), want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.att"), }, { name: "attestation passed a tag (w/ custom suffix)", fn: AttestationTag, ref: name.MustParseReference("gcr.io/distroless/static:nonroot"), opts: []Option{WithAttestationSuffix("snowflake")}, want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.snowflake"), }, { name: "attestation passed a digest", fn: AttestationTag, ref: name.MustParseReference("gcr.io/distroless/static@sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4"), want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.att"), }, { name: "sbom passed a tag", fn: SBOMTag, ref: name.MustParseReference("gcr.io/distroless/static:nonroot"), want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.sbom"), }, { name: "sbom passed a tag (w/ custom suffix)", fn: SBOMTag, ref: name.MustParseReference("gcr.io/distroless/static:nonroot"), opts: []Option{WithSBOMSuffix("snowflake")}, want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.snowflake"), }, { name: "sbom passed a digest", fn: SBOMTag, ref: name.MustParseReference("gcr.io/distroless/static@sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4"), want: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.sbom"), }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := test.fn(test.ref, test.opts...) if err != nil { t.Fatalf("fn() = %v", err) } if got.String() != test.want.String() { t.Errorf("fn() = %s, wanted %s", got.String(), test.want.String()) } }) } } func TestTagMethodErrors(t *testing.T) { rg := remoteGet defer func() { remoteGet = rg }() errRemoteGet := errors.New("remote.Get failure") remoteGet = func(_ name.Reference, _ ...remote.Option) (*remote.Descriptor, error) { return nil, errRemoteGet } tests := []struct { name string fn func(name.Reference, ...Option) (name.Tag, error) ref name.Reference want error }{ { name: "signature passed a tag", fn: SignatureTag, ref: name.MustParseReference("gcr.io/distroless/static:nonroot"), want: errRemoteGet, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { tag, got := test.fn(test.ref) if got == nil { t.Fatalf("fn() = %v, wanted %v", tag, test.want) } if got.Error() != test.want.Error() { t.Errorf("fn() = %v, wanted %v", got, test.want) } }) } } func TestDockercontentDigest(t *testing.T) { rg := remoteGet defer func() { remoteGet = rg }() remoteGet = func(_ name.Reference, _ ...remote.Option) (*remote.Descriptor, error) { return &remote.Descriptor{ Descriptor: v1.Descriptor{ Digest: v1.Hash{ Algorithm: "sha256", // As of 2021-09-20: // crane digest gcr.io/distroless/static:nonroot Hex: "be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4", }, }, }, nil } repo, err := name.NewRepository("gcr.io/distroless/static") if err != nil { t.Fatalf("unexpected error: %v", err) } tests := []struct { name string tag name.Tag wantTag name.Tag }{ { name: "docker content digest for tag", tag: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.sig").(name.Tag), wantTag: repo.Tag("sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4"), }, { name: "docker content digest for attestation", tag: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.att").(name.Tag), wantTag: repo.Tag("sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4"), }, { name: "docker content digest for SBOM", tag: name.MustParseReference("gcr.io/distroless/static:sha256-be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4.sbom").(name.Tag), wantTag: repo.Tag("sha256:be5d77c62dbe7fedfb0a4e5ec2f91078080800ab1f18358e5f31fcc8faa023c4"), }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { gotTag, err := DockerContentDigest(test.tag) if err != nil { t.Fatalf("fn() = %v", err) } if gotTag != test.wantTag { t.Errorf("fn() = %s, wanted %s", gotTag.String(), test.wantTag.String()) } }) } } func TestPayload(t *testing.T) { tests := []struct { name string size int64 env map[string]string wantError error }{ { name: "within default limit", size: 1000, wantError: nil, }, { name: "excceds default limit", size: 1073741824, wantError: errors.New("size of layer (1073741824) exceeded the limit (134217728)"), }, { name: "exceeds overridden limit", size: 5120, env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1KB"}, wantError: errors.New("size of layer (5120) exceeded the limit (1000)"), }, { name: "within overridden limit", size: 5120, env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "10KB"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { for k, v := range test.env { t.Setenv(k, v) } a := attached{ layer: &mockLayer{ size: test.size, }, } _, err := a.Payload() if test.wantError != nil && test.wantError.Error() != err.Error() { t.Fatalf("Payload() = %v, wanted %v", err, test.wantError) } if test.wantError == nil && err != nil { t.Fatalf("Payload() = %v, wanted %v", err, test.wantError) } }) } } type mockLayer struct { size int64 } func (m *mockLayer) Compressed() (io.ReadCloser, error) { return io.NopCloser(strings.NewReader("test payload")), nil } func (m *mockLayer) Size() (int64, error) { return m.size, nil } func (m *mockLayer) Digest() (v1.Hash, error) { panic("not implemented") } func (m *mockLayer) DiffID() (v1.Hash, error) { panic("not implemented") } func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") } func (m *mockLayer) MediaType() (types.MediaType, error) { panic("not implemented") } cosign-2.5.0/pkg/oci/remote/signatures.go000066400000000000000000000066431477503325500203650ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "errors" "io" "net/http" "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/empty" "github.com/sigstore/cosign/v2/pkg/oci/internal/signature" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" ) const maxLayers = 1000 // Signatures fetches the signatures image represented by the named reference. // If the tag is not found, this returns an empty oci.Signatures. func Signatures(ref name.Reference, opts ...Option) (oci.Signatures, error) { o := makeOptions(ref.Context(), opts...) img, err := remoteImage(ref, o.ROpt...) var te *transport.Error if errors.As(err, &te) { if te.StatusCode != http.StatusNotFound { return nil, te } return empty.Signatures(), nil } else if err != nil { return nil, err } return &sigs{ Image: img, }, nil } func Bundle(ref name.Reference, opts ...Option) (*sgbundle.Bundle, error) { o := makeOptions(ref.Context(), opts...) img, err := remoteImage(ref, o.ROpt...) if err != nil { return nil, err } layers, err := img.Layers() if err != nil { return nil, err } if len(layers) != 1 { return nil, errors.New("expected exactly one layer") } mediaType, err := layers[0].MediaType() if err != nil { return nil, err } if !strings.HasPrefix(string(mediaType), "application/vnd.dev.sigstore.bundle") { return nil, errors.New("expected bundle layer") } layer0, err := layers[0].Uncompressed() if err != nil { return nil, err } bundleBytes, err := io.ReadAll(layer0) if err != nil { return nil, err } b := &sgbundle.Bundle{} err = b.UnmarshalJSON(bundleBytes) if err != nil { return nil, err } if !b.MinVersion("v0.3") { return nil, errors.New("bundle version too old") } return b, nil } type sigs struct { v1.Image } // The wrapped Image implements ConfigLayer, but the wrapping hides that from typechecks in pkg/v1/remote. // Make sigs explicitly implement ConfigLayer so that this returns a mountable config layer for pkg/v1/remote. func (s *sigs) ConfigLayer() (v1.Layer, error) { return partial.ConfigLayer(s.Image) } var _ oci.Signatures = (*sigs)(nil) // Get implements oci.Signatures func (s *sigs) Get() ([]oci.Signature, error) { m, err := s.Manifest() if err != nil { return nil, err } numLayers := int64(len(m.Layers)) if numLayers > maxLayers { return nil, oci.NewMaxLayersExceeded(numLayers, maxLayers) } signatures := make([]oci.Signature, 0, len(m.Layers)) for _, desc := range m.Layers { layer, err := s.LayerByDigest(desc.Digest) if err != nil { return nil, err } signatures = append(signatures, signature.New(layer, desc)) } return signatures, nil } cosign-2.5.0/pkg/oci/remote/signatures_test.go000066400000000000000000000057761477503325500214320ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "errors" "net/http" "testing" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/fake" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" ) func TestSignaturesErrors(t *testing.T) { ri := remote.Image t.Cleanup(func() { remoteImage = ri }) t.Run("404 returns empty", func(t *testing.T) { remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { return nil, &transport.Error{ StatusCode: http.StatusNotFound, } } sigs, err := Signatures(name.MustParseReference("gcr.io/distroless/static:sha256-deadbeef.sig")) if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Fatalf("Get() = %v", err) } else if len(sl) != 0 { t.Fatalf("len(Get()) = %d, wanted 0", len(sl)) } }) t.Run("other transport errors propagate", func(t *testing.T) { want := &transport.Error{ StatusCode: http.StatusInternalServerError, } remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { return nil, want } _, err := Signatures(name.MustParseReference("gcr.io/distroless/static:sha256-deadbeef.sig")) if !errors.Is(err, want) { t.Fatalf("Signatures() = %v, wanted %v", err, want) } }) t.Run("other errors propagate", func(t *testing.T) { want := errors.New("it's my error, I can cry if I want to") remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { return nil, want } _, err := Signatures(name.MustParseReference("gcr.io/distroless/static:sha256-deadbeef.sig")) if !errors.Is(err, want) { t.Fatalf("Signatures() = %v, wanted %v", err, want) } }) t.Run("too many layers", func(t *testing.T) { remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { return &fake.FakeImage{ ManifestStub: func() (*v1.Manifest, error) { return &v1.Manifest{ Layers: make([]v1.Descriptor, 10000), }, nil }, }, nil } sigs, err := Signatures(name.MustParseReference("gcr.io/distroless/static:sha256-deadbeef.sig")) if err != nil { t.Fatalf("Signatures() = %v", err) } want := errors.New("number of layers (10000) exceeded the limit (1000)") _, err = sigs.Get() if err == nil || want.Error() != err.Error() { t.Fatalf("Get() = %v", err) } }) } cosign-2.5.0/pkg/oci/remote/unknown.go000066400000000000000000000034001477503325500176640ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/pkg/oci" ) // SignedUnknown provides access to signed metadata without directly accessing // the underlying entity. This can be used to access signature metadata for // digests that have not been published (yet). func SignedUnknown(digest name.Digest, options ...Option) oci.SignedEntity { o := makeOptions(digest.Context(), options...) return &unknown{ digest: digest, opt: o, } } type unknown struct { digest name.Digest opt *options } var _ oci.SignedEntity = (*unknown)(nil) // Digest implements digestable func (i *unknown) Digest() (v1.Hash, error) { return v1.NewHash(i.digest.DigestStr()) } // Signatures implements oci.SignedEntity func (i *unknown) Signatures() (oci.Signatures, error) { return signatures(i, i.opt) } // Attestations implements oci.SignedEntity func (i *unknown) Attestations() (oci.Signatures, error) { return attestations(i, i.opt) } // Attachment implements oci.SignedEntity func (i *unknown) Attachment(name string) (oci.File, error) { return attachment(i, name, i.opt) } cosign-2.5.0/pkg/oci/remote/unknown_test.go000066400000000000000000000055311477503325500207320ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "testing" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" ) func TestSignedUnknown(t *testing.T) { ri := remote.Image t.Cleanup(func() { remoteImage = ri }) wantLayers := int64(7) remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { // Only called for signature images return random.Image(300 /* byteSize */, wantLayers) } // :nonroot as of 2023/05/07 digest, err := name.NewDigest("gcr.io/distroless/static@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f") if err != nil { t.Fatalf("ParseRef() = %v", err) } si := SignedUnknown(digest) sigs, err := si.Signatures() if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got := int64(len(sl)); got != wantLayers { t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers) } atts, err := si.Attestations() if err != nil { t.Fatalf("Signatures() = %v", err) } if al, err := atts.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got := int64(len(al)); got != wantLayers { t.Errorf("len(Get()) = %d, wanted %d", got, wantLayers) } } func TestSignedUnknownWithAttachment(t *testing.T) { ri := remote.Image t.Cleanup(func() { remoteImage = ri }) wantLayers := int64(1) // File must have a single layer remoteImage = func(_ name.Reference, _ ...remote.Option) (v1.Image, error) { // Only called for signature images return random.Image(300 /* byteSize */, wantLayers) } // :nonroot as of 2023/05/07 digest, err := name.NewDigest("gcr.io/distroless/static@sha256:9ecc53c269509f63c69a266168e4a687c7eb8c0cfd753bd8bfcaa4f58a90876f") if err != nil { t.Fatalf("ParseRef() = %v", err) } si := SignedUnknown(digest) file, err := si.Attachment("sbom") if err != nil { t.Fatalf("Signatures() = %v", err) } payload, err := file.Payload() if err != nil { t.Errorf("Payload() = %v", err) } // We check greater than because it's wrapped in a tarball with `random.Layer` if len(payload) < 300 { t.Errorf("Payload() = %d bytes, wanted %d", len(payload), 300) } } cosign-2.5.0/pkg/oci/remote/write.go000066400000000000000000000240061477503325500173240ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "bytes" "encoding/json" "fmt" "os" "time" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/static" "github.com/google/go-containerregistry/pkg/v1/types" ociexperimental "github.com/sigstore/cosign/v2/internal/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci" ctypes "github.com/sigstore/cosign/v2/pkg/types" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" ) // WriteSignedImageIndexImages writes the images within the image index // This includes the signed image and associated signatures in the image index // TODO (priyawadhwa@): write the `index.json` itself to the repo as well // TODO (priyawadhwa@): write the attestations func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, opts ...Option) error { repo := ref.Context() o := makeOptions(repo, opts...) // write the image index if there is one ii, err := sii.SignedImageIndex(v1.Hash{}) if err != nil { return fmt.Errorf("signed image index: %w", err) } if ii != nil { if err := remote.WriteIndex(ref, ii, o.ROpt...); err != nil { return fmt.Errorf("writing index: %w", err) } } // write the image if there is one si, err := sii.SignedImage(v1.Hash{}) if err != nil { return fmt.Errorf("signed image: %w", err) } if si != nil { if err := remoteWrite(ref, si, o.ROpt...); err != nil { return fmt.Errorf("remote write: %w", err) } } // write the signatures sigs, err := sii.Signatures() if err != nil { return err } if sigs != nil { // will be nil if there are no associated signatures sigsTag, err := SignatureTag(ref, opts...) if err != nil { return fmt.Errorf("sigs tag: %w", err) } if err := remoteWrite(sigsTag, sigs, o.ROpt...); err != nil { return err } } // write the attestations atts, err := sii.Attestations() if err != nil { return err } if atts != nil { // will be nil if there are no associated attestations attsTag, err := AttestationTag(ref, opts...) if err != nil { return fmt.Errorf("sigs tag: %w", err) } return remoteWrite(attsTag, atts, o.ROpt...) } return nil } // WriteSignature publishes the signatures attached to the given entity // into the provided repository. func WriteSignatures(repo name.Repository, se oci.SignedEntity, opts ...Option) error { o := makeOptions(repo, opts...) // Access the signature list to publish sigs, err := se.Signatures() if err != nil { return err } // Determine the tag to which these signatures should be published. h, err := se.Digest() if err != nil { return err } tag := o.TargetRepository.Tag(normalize(h, o.TagPrefix, o.SignatureSuffix)) // Write the Signatures image to the tag, with the provided remote.Options return remoteWrite(tag, sigs, o.ROpt...) } // WriteAttestations publishes the attestations attached to the given entity // into the provided repository. func WriteAttestations(repo name.Repository, se oci.SignedEntity, opts ...Option) error { o := makeOptions(repo, opts...) // Access the signature list to publish atts, err := se.Attestations() if err != nil { return err } // Determine the tag to which these signatures should be published. h, err := se.Digest() if err != nil { return err } tag := o.TargetRepository.Tag(normalize(h, o.TagPrefix, o.AttestationSuffix)) // Write the Signatures image to the tag, with the provided remote.Options return remoteWrite(tag, atts, o.ROpt...) } // WriteSignaturesExperimentalOCI publishes the signatures attached to the given entity // into the provided repository (using OCI 1.1 methods). func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ...Option) error { o := makeOptions(d.Repository, opts...) signTarget := d.String() ref, err := name.ParseReference(signTarget, o.NameOpts...) if err != nil { return err } desc, err := remote.Head(ref, o.ROpt...) if err != nil { return err } sigs, err := se.Signatures() if err != nil { return err } // Write the signature blobs s, err := sigs.Get() if err != nil { return err } for _, v := range s { if err := remote.WriteLayer(d.Repository, v, o.ROpt...); err != nil { return err } } // Write the config configBytes, err := sigs.RawConfigFile() if err != nil { return err } var configDesc v1.Descriptor if err := json.Unmarshal(configBytes, &configDesc); err != nil { return err } configLayer := static.NewLayer(configBytes, configDesc.MediaType) if err := remote.WriteLayer(d.Repository, configLayer, o.ROpt...); err != nil { return err } // Write the manifest containing a subject b, err := sigs.RawManifest() if err != nil { return err } var m v1.Manifest if err := json.Unmarshal(b, &m); err != nil { return err } artifactType := ociexperimental.ArtifactType("sig") m.Config.MediaType = types.MediaType(artifactType) m.Subject = desc b, err = json.Marshal(&m) if err != nil { return err } digest, _, err := v1.SHA256(bytes.NewReader(b)) if err != nil { return err } targetRef, err := name.ParseReference(fmt.Sprintf("%s/%s@%s", d.RegistryStr(), d.RepositoryStr(), digest.String())) if err != nil { return err } // TODO: use ui.Infof fmt.Fprintf(os.Stderr, "Uploading signature for [%s] to [%s] with config.mediaType [%s] layers[0].mediaType [%s].\n", d.String(), targetRef.String(), artifactType, ctypes.SimpleSigningMediaType) return remote.Put(targetRef, &taggableManifest{raw: b, mediaType: m.MediaType}, o.ROpt...) } type taggableManifest struct { raw []byte mediaType types.MediaType } func (taggable taggableManifest) RawManifest() ([]byte, error) { return taggable.raw, nil } func (taggable taggableManifest) MediaType() (types.MediaType, error) { return taggable.mediaType, nil } func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicateType string, opts ...Option) error { o := makeOptions(d.Repository, opts...) signTarget := d.String() ref, err := name.ParseReference(signTarget, o.NameOpts...) if err != nil { return err } desc, err := remote.Head(ref, o.ROpt...) if err != nil { return err } // Write the empty config layer configLayer := static.NewLayer([]byte("{}"), "application/vnd.oci.image.config.v1+json") configDigest, err := configLayer.Digest() if err != nil { return fmt.Errorf("failed to calculate digest: %w", err) } configSize, err := configLayer.Size() if err != nil { return fmt.Errorf("failed to calculate size: %w", err) } err = remote.WriteLayer(d.Repository, configLayer, o.ROpt...) if err != nil { return fmt.Errorf("failed to upload layer: %w", err) } // generate bundle media type string bundleMediaType, err := sgbundle.MediaTypeString("0.3") if err != nil { return fmt.Errorf("failed to generate bundle media type string: %w", err) } // Write the bundle layer layer := static.NewLayer(bundleBytes, types.MediaType(bundleMediaType)) blobDigest, err := layer.Digest() if err != nil { return fmt.Errorf("failed to calculate digest: %w", err) } blobSize, err := layer.Size() if err != nil { return fmt.Errorf("failed to calculate size: %w", err) } err = remote.WriteLayer(d.Repository, layer, o.ROpt...) if err != nil { return fmt.Errorf("failed to upload layer: %w", err) } // Create a manifest that includes the blob as a layer manifest := referrerManifest{v1.Manifest{ SchemaVersion: 2, MediaType: types.OCIManifestSchema1, Config: v1.Descriptor{ MediaType: types.MediaType("application/vnd.oci.empty.v1+json"), ArtifactType: bundleMediaType, Digest: configDigest, Size: configSize, }, Layers: []v1.Descriptor{ { MediaType: types.MediaType(bundleMediaType), Digest: blobDigest, Size: blobSize, }, }, Subject: &v1.Descriptor{ MediaType: desc.MediaType, Digest: desc.Digest, Size: desc.Size, }, Annotations: map[string]string{ "org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339), "dev.sigstore.bundle.content": "dsse-envelope", "dev.sigstore.bundle.predicateType": predicateType, }, }, bundleMediaType} targetRef, err := manifest.targetRef(d.Repository) if err != nil { return fmt.Errorf("failed to create target reference: %w", err) } if err := remote.Put(targetRef, manifest, o.ROpt...); err != nil { return fmt.Errorf("failed to upload manifest: %w", err) } return nil } // referrerManifest implements Taggable for use in remote.Put. // This type also augments the built-in v1.Manifest with an ArtifactType field // which is part of the OCI 1.1 Image Manifest spec but is unsupported by // go-containerregistry at this time. // See https://github.com/opencontainers/image-spec/blob/v1.1.0/manifest.md#image-manifest-property-descriptions // and https://github.com/google/go-containerregistry/pull/1931 type referrerManifest struct { v1.Manifest ArtifactType string `json:"artifactType,omitempty"` } func (r referrerManifest) RawManifest() ([]byte, error) { return json.Marshal(r) } func (r referrerManifest) targetRef(repo name.Repository) (name.Reference, error) { manifestBytes, err := r.RawManifest() if err != nil { return nil, err } digest, _, err := v1.SHA256(bytes.NewReader(manifestBytes)) if err != nil { return nil, err } return name.ParseReference(fmt.Sprintf("%s/%s@%s", repo.RegistryStr(), repo.RepositoryStr(), digest.String())) } func (r referrerManifest) MediaType() (types.MediaType, error) { return types.OCIManifestSchema1, nil } cosign-2.5.0/pkg/oci/remote/write_test.go000066400000000000000000000056371477503325500203740ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package remote import ( "fmt" "testing" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/sigstore/cosign/v2/pkg/oci/mutate" "github.com/sigstore/cosign/v2/pkg/oci/signed" "github.com/sigstore/cosign/v2/pkg/oci/static" ) func TestWriteSignatures(t *testing.T) { rw := remote.Write t.Cleanup(func() { remoteWrite = rw }) i, err := random.Image(300 /* byteSize */, 7 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } si := signed.Image(i) want := 6 // Add 6 signatures for i := 0; i < want; i++ { sig, err := static.NewSignature(nil, fmt.Sprintf("%d", i)) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } si, err = mutate.AttachSignatureToImage(si, sig) if err != nil { t.Fatalf("SignEntity() = %v", err) } } ref := name.MustParseReference("gcr.io/bistroless/static:nonroot") remoteWrite = func(_ name.Reference, img v1.Image, _ ...remote.Option) error { l, err := img.Layers() if err != nil { return err } if got := len(l); got != want { t.Errorf("got %d layers, wanted %d", got, want) } return nil } if err := WriteSignatures(ref.Context(), si); err != nil { t.Fatalf("WriteSignature() = %v", err) } } func TestWriteAttestations(t *testing.T) { rw := remote.Write t.Cleanup(func() { remoteWrite = rw }) i, err := random.Image(300 /* byteSize */, 7 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } si := signed.Image(i) want := 6 // Add 6 attestations for i := 0; i < want; i++ { sig, err := static.NewAttestation([]byte(fmt.Sprintf("%d", i))) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } si, err = mutate.AttachAttestationToImage(si, sig) if err != nil { t.Fatalf("SignEntity() = %v", err) } } ref := name.MustParseReference("gcr.io/bistroless/static:nonroot") remoteWrite = func(_ name.Reference, img v1.Image, _ ...remote.Option) error { l, err := img.Layers() if err != nil { return err } if got := len(l); got != want { t.Errorf("got %d layers, wanted %d", got, want) } return nil } if err := WriteAttestations(ref.Context(), si); err != nil { t.Fatalf("WriteAttestations() = %v", err) } } cosign-2.5.0/pkg/oci/signature/000077500000000000000000000000001477503325500163475ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/signature/layer.go000066400000000000000000000075231477503325500200210ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signature import ( "crypto/x509" "encoding/base64" "encoding/json" "fmt" "io" "strings" v1 "github.com/google/go-containerregistry/pkg/v1" payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/sigstore/pkg/cryptoutils" ) const ( sigkey = "dev.cosignproject.cosign/signature" certkey = "dev.sigstore.cosign/certificate" chainkey = "dev.sigstore.cosign/chain" BundleKey = "dev.sigstore.cosign/bundle" RFC3161TimestampKey = "dev.sigstore.cosign/rfc3161timestamp" ) type sigLayer struct { v1.Layer desc v1.Descriptor } func New(l v1.Layer, desc v1.Descriptor) oci.Signature { return &sigLayer{ Layer: l, desc: desc, } } var _ oci.Signature = (*sigLayer)(nil) // Annotations implements oci.Signature func (s *sigLayer) Annotations() (map[string]string, error) { return s.desc.Annotations, nil } // Payload implements oci.Signature func (s *sigLayer) Payload() ([]byte, error) { size, err := s.Size() if err != nil { return nil, err } err = payloadsize.CheckSize(uint64(size)) if err != nil { return nil, err } // Compressed is a misnomer here, we just want the raw bytes from the registry. r, err := s.Compressed() if err != nil { return nil, err } defer r.Close() payload, err := io.ReadAll(r) if err != nil { return nil, err } return payload, nil } // Signature implements oci.Signature func (s *sigLayer) Signature() ([]byte, error) { b64sig, err := s.Base64Signature() if err != nil { return nil, err } return base64.StdEncoding.DecodeString(b64sig) } // Base64Signature implements oci.Signature func (s *sigLayer) Base64Signature() (string, error) { b64sig, ok := s.desc.Annotations[sigkey] if !ok { return "", fmt.Errorf("signature layer %s is missing %q annotation", s.desc.Digest, sigkey) } return b64sig, nil } // Cert implements oci.Signature func (s *sigLayer) Cert() (*x509.Certificate, error) { certPEM := s.desc.Annotations[certkey] if certPEM == "" { return nil, nil } certs, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(certPEM)) if err != nil { return nil, err } return certs[0], nil } // Chain implements oci.Signature func (s *sigLayer) Chain() ([]*x509.Certificate, error) { chainPEM := s.desc.Annotations[chainkey] if chainPEM == "" { return nil, nil } certs, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(chainPEM)) if err != nil { return nil, err } return certs, nil } // Bundle implements oci.Signature func (s *sigLayer) Bundle() (*bundle.RekorBundle, error) { val := s.desc.Annotations[BundleKey] if val == "" { return nil, nil } var b bundle.RekorBundle if err := json.Unmarshal([]byte(val), &b); err != nil { return nil, fmt.Errorf("unmarshaling bundle: %w", err) } return &b, nil } // RFC3161Timestamp implements oci.Signature func (s *sigLayer) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) { val := s.desc.Annotations[RFC3161TimestampKey] if val == "" { return nil, nil } var b bundle.RFC3161Timestamp if err := json.Unmarshal([]byte(val), &b); err != nil { return nil, fmt.Errorf("unmarshaling RFC3161 timestamp bundle: %w", err) } return &b, nil } cosign-2.5.0/pkg/oci/signature/layer_test.go000066400000000000000000000373041477503325500210600ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signature import ( "bytes" "encoding/base64" "errors" "fmt" "io" "strings" "testing" "github.com/google/go-cmp/cmp" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" ) func mustDecode(s string) []byte { b, err := base64.StdEncoding.DecodeString(s) if err != nil { panic(err.Error()) } return b } func TestSignature(t *testing.T) { layer, err := random.Layer(300 /* byteSize */, types.DockerLayer) if err != nil { t.Fatalf("random.Layer() = %v", err) } digest, err := layer.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } tests := []struct { name string l *sigLayer wantPayloadErr error wantSig string wantSigErr error wantCert bool wantCertErr error wantChain int wantChainErr error wantBundle *bundle.RekorBundle wantBundleErr error }{{ name: "just payload and signature", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", }, }, }, wantSig: "blah", }, { name: "with empty other keys", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", certkey: "", chainkey: "", BundleKey: "", }, }, }, wantSig: "blah", }, { name: "missing signature", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, }, }, wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey), }, { name: "min plus bad bundle", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", BundleKey: `}`, }, }, }, wantSig: "blah", wantBundleErr: errors.New(`unmarshaling bundle: invalid character '}' looking for beginning of value`), }, { name: "min plus bad cert", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", certkey: `GARBAGE`, }, }, }, wantSig: "blah", wantCertErr: errors.New(`error during PEM decoding`), }, { name: "min plus bad chain", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", chainkey: `GARBAGE`, }, }, }, wantSig: "blah", wantChainErr: errors.New(`error during PEM decoding`), }, { name: "min plus bundle", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16. // The Body has been removed for brevity BundleKey: `{"SignedEntryTimestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`, }, }, }, wantSig: "blah", wantBundle: &bundle.RekorBundle{ SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), Payload: bundle.RekorPayload{ Body: "REMOVED", IntegratedTime: 1631646761, LogIndex: 693591, LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", }, }, }, { name: "min plus good cert", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 certkey: ` -----BEGIN CERTIFICATE----- MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt 6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G 3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG 0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY= -----END CERTIFICATE----- `, }, }, }, wantSig: "blah", wantCert: true, }, { name: "min plus bad chain", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 chainkey: ` -----BEGIN CERTIFICATE----- MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== -----END CERTIFICATE----- `, }, }, }, wantSig: "blah", wantChain: 1, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { b, err := test.l.Payload() switch { case (err != nil) != (test.wantPayloadErr != nil): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error(): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) case err == nil: if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil { t.Errorf("v1.SHA256() = %v", err) } else if want := digest; want != got { t.Errorf("v1.SHA256() = %v, wanted %v", got, want) } } switch got, err := test.l.Base64Signature(); { case (err != nil) != (test.wantSigErr != nil): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error(): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) case got != test.wantSig: t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig) } switch got, err := test.l.Cert(); { case (err != nil) != (test.wantCertErr != nil): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error(): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) case (got != nil) != test.wantCert: t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert) } switch got, err := test.l.Chain(); { case (err != nil) != (test.wantChainErr != nil): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error(): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) case len(got) != test.wantChain: t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain) } switch got, err := test.l.Bundle(); { case (err != nil) != (test.wantBundleErr != nil): t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr) case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error(): t.Errorf("Bundle() = %v, wanted %v", err, test.wantBundleErr) case !cmp.Equal(got, test.wantBundle): t.Errorf("Bundle() %s", cmp.Diff(got, test.wantBundle)) } }) } } func TestSignatureWithTSAAnnotation(t *testing.T) { layer, err := random.Layer(300 /* byteSize */, types.DockerLayer) if err != nil { t.Fatalf("random.Layer() = %v", err) } digest, err := layer.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } tests := []struct { name string l *sigLayer env map[string]string wantPayloadErr error wantSig string wantSigErr error wantCert bool wantCertErr error wantChain int wantChainErr error wantBundle *bundle.RFC3161Timestamp wantBundleErr error }{{ name: "just payload and signature", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", }, }, }, wantSig: "blah", }, { name: "with empty other keys", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", certkey: "", chainkey: "", RFC3161TimestampKey: "", }, }, }, wantSig: "blah", }, { name: "missing signature", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, }, }, wantSigErr: fmt.Errorf("signature layer %s is missing %q annotation", digest, sigkey), }, { name: "min plus bad RFC3161 timestamp bundle", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", RFC3161TimestampKey: `}`, }, }, }, wantSig: "blah", wantBundleErr: errors.New(`unmarshaling RFC3161 timestamp bundle: invalid character '}' looking for beginning of value`), }, { name: "min plus bad cert", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", certkey: `GARBAGE`, }, }, }, wantSig: "blah", wantCertErr: errors.New(`error during PEM decoding`), }, { name: "min plus bad chain", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", chainkey: `GARBAGE`, }, }, }, wantSig: "blah", wantChainErr: errors.New(`error during PEM decoding`), }, { name: "min plus RFC3161 timestamp bundle", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "tsa blah", // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16. // The Body has been removed for brevity RFC3161TimestampKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`, }, }, }, wantSig: "tsa blah", wantBundle: &bundle.RFC3161Timestamp{ SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), }, }, { name: "payload size exceeds default limit", l: &sigLayer{ Layer: &mockLayer{size: 134217728 + 42}, // 128MiB + 42 bytes }, wantPayloadErr: errors.New("size of layer (134217770) exceeded the limit (134217728)"), }, { name: "payload size exceeds overridden limit", l: &sigLayer{ Layer: &mockLayer{size: 1000000000 + 42}, // 1GB + 42 bytes }, env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "1GB"}, wantPayloadErr: errors.New("size of layer (1000000042) exceeded the limit (1000000000)"), }, { name: "payload size is within overridden limit", l: &sigLayer{ Layer: layer, desc: v1.Descriptor{ Digest: digest, Annotations: map[string]string{ sigkey: "blah", }, }, }, env: map[string]string{"COSIGN_MAX_ATTACHMENT_SIZE": "5KB"}, wantSig: "blah", }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { for k, v := range test.env { t.Setenv(k, v) } b, err := test.l.Payload() switch { case (err != nil) != (test.wantPayloadErr != nil): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) case (err != nil) && (test.wantPayloadErr != nil) && err.Error() != test.wantPayloadErr.Error(): t.Errorf("Payload() = %v, wanted %v", err, test.wantPayloadErr) case err == nil: if got, _, err := v1.SHA256(bytes.NewBuffer(b)); err != nil { t.Errorf("v1.SHA256() = %v", err) } else if want := digest; want != got { t.Errorf("v1.SHA256() = %v, wanted %v", got, want) } } if err != nil { return } switch got, err := test.l.Base64Signature(); { case (err != nil) != (test.wantSigErr != nil): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) case (err != nil) && (test.wantSigErr != nil) && err.Error() != test.wantSigErr.Error(): t.Errorf("Base64Signature() = %v, wanted %v", err, test.wantSigErr) case got != test.wantSig: t.Errorf("Base64Signature() = %v, wanted %v", got, test.wantSig) } switch got, err := test.l.Cert(); { case (err != nil) != (test.wantCertErr != nil): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) case (err != nil) && (test.wantCertErr != nil) && err.Error() != test.wantCertErr.Error(): t.Errorf("Cert() = %v, wanted %v", err, test.wantCertErr) case (got != nil) != test.wantCert: t.Errorf("Cert() = %v, wanted cert? %v", got, test.wantCert) } switch got, err := test.l.Chain(); { case (err != nil) != (test.wantChainErr != nil): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) case (err != nil) && (test.wantChainErr != nil) && err.Error() != test.wantChainErr.Error(): t.Errorf("Chain() = %v, wanted %v", err, test.wantChainErr) case len(got) != test.wantChain: t.Errorf("Chain() = %v, wanted chain of length %d", got, test.wantChain) } switch got, err := test.l.RFC3161Timestamp(); { case (err != nil) != (test.wantBundleErr != nil): t.Errorf("RFC3161Timestamp() = %v, wanted %v", err, test.wantBundleErr) case (err != nil) && (test.wantBundleErr != nil) && err.Error() != test.wantBundleErr.Error(): t.Errorf("RFC3161Timestamp() = %v, wanted %v", err, test.wantBundleErr) case !cmp.Equal(got, test.wantBundle): t.Errorf("RFC3161Timestamp() %s", cmp.Diff(got, test.wantBundle)) } }) } } type mockLayer struct { size int64 } func (m *mockLayer) Size() (int64, error) { return m.size, nil } func (m *mockLayer) Compressed() (io.ReadCloser, error) { return io.NopCloser(strings.NewReader("data")), nil } func (m *mockLayer) Digest() (v1.Hash, error) { panic("not implemented") } func (m *mockLayer) DiffID() (v1.Hash, error) { panic("not implemented") } func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { panic("not implemented") } func (m *mockLayer) MediaType() (types.MediaType, error) { panic("not implemented") } cosign-2.5.0/pkg/oci/signatures.go000066400000000000000000000043451477503325500170670ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oci import ( "crypto/x509" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" ) // Signatures represents a set of signatures that are associated with a particular // v1.Image. type Signatures interface { v1.Image // The low-level representation of the signatures // Get retrieves the list of signatures stored. Get() ([]Signature, error) } // Signature holds a single image signature. type Signature interface { v1.Layer // Annotations returns the annotations associated with this layer. Annotations() (map[string]string, error) // Payload fetches the opaque data that is being signed. // This will always return data when there is no error. Payload() ([]byte, error) // Signature fetches the raw signature // of the payload. This will always return data when // there is no error. Signature() ([]byte, error) // Base64Signature fetches the base64 encoded signature // of the payload. This will always return data when // there is no error. Base64Signature() (string, error) // Cert fetches the optional public key from the key pair that // was used to sign the payload. Cert() (*x509.Certificate, error) // Chain fetches the optional "full certificate chain" rooted // at a Fulcio CA, the leaf of which was used to sign the // payload. Chain() ([]*x509.Certificate, error) // Bundle fetches the optional metadata that records the ephemeral // Fulcio key in the transparency log. Bundle() (*bundle.RekorBundle, error) // RFC3161Timestamp() fetches the optional metadata that records a // RFC3161 signed timestamp. RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) } cosign-2.5.0/pkg/oci/signed/000077500000000000000000000000001477503325500156175ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/signed/image.go000066400000000000000000000026251477503325500172350ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signed import ( "errors" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/empty" ) // Image returns an oci.SignedImage form of the v1.Image with no signatures. func Image(i v1.Image) oci.SignedImage { return &image{ Image: i, } } type image struct { v1.Image } var _ oci.SignedImage = (*image)(nil) // Signatures implements oci.SignedImage func (*image) Signatures() (oci.Signatures, error) { return empty.Signatures(), nil } // Attestations implements oci.SignedImage func (*image) Attestations() (oci.Signatures, error) { return empty.Signatures(), nil } // Attestations implements oci.SignedImage func (*image) Attachment(name string) (oci.File, error) { //nolint: revive return nil, errors.New("unimplemented") } cosign-2.5.0/pkg/oci/signed/image_test.go000066400000000000000000000025641477503325500202760ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signed import ( "testing" "github.com/google/go-containerregistry/pkg/v1/random" ) func TestImage(t *testing.T) { i, err := random.Image(300 /* bytes */, 5 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } si := Image(i) sigs, err := si.Signatures() if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got, want := len(sl), 0; got != want { t.Errorf("len(Get()) = %d, wanted %d", got, want) } atts, err := si.Attestations() if err != nil { t.Fatalf("Attestations() = %v", err) } if al, err := atts.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got, want := len(al), 0; got != want { t.Errorf("len(Get()) = %d, wanted %d", got, want) } } cosign-2.5.0/pkg/oci/signed/index.go000066400000000000000000000036131477503325500172600ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signed import ( "errors" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/empty" ) // ImageIndex returns an oci.SignedImageIndex form of the v1.ImageIndex with // no signatures. func ImageIndex(i v1.ImageIndex) oci.SignedImageIndex { return &index{ v1Index: i, } } type v1Index v1.ImageIndex type index struct { v1Index } var _ oci.SignedImageIndex = (*index)(nil) // SignedImage implements oci.SignedImageIndex func (ii *index) SignedImage(h v1.Hash) (oci.SignedImage, error) { i, err := ii.Image(h) if err != nil { return nil, err } return Image(i), nil } // SignedImageIndex implements oci.SignedImageIndex func (ii *index) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) { i, err := ii.ImageIndex(h) if err != nil { return nil, err } return ImageIndex(i), nil } // Signatures implements oci.SignedImageIndex func (*index) Signatures() (oci.Signatures, error) { return empty.Signatures(), nil } // Attestations implements oci.SignedImageIndex func (*index) Attestations() (oci.Signatures, error) { return empty.Signatures(), nil } // Attestations implements oci.SignedImage func (*index) Attachment(name string) (oci.File, error) { //nolint: revive return nil, errors.New("unimplemented") } cosign-2.5.0/pkg/oci/signed/index_test.go000066400000000000000000000054011477503325500203140ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signed import ( "testing" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/oci" ) func TestImageIndex(t *testing.T) { ii, err := random.Index(300 /* bytes */, 5 /* layers */, 3 /* images */) if err != nil { t.Fatalf("random.Image() = %v", err) } ni, err := random.Index(300 /* bytes */, 5 /* layers */, 3 /* images */) if err != nil { t.Fatalf("random.Image() = %v", err) } ni = mutate.AppendManifests(ni, mutate.IndexAddendum{ Add: ii, }) im, err := ni.IndexManifest() if err != nil { t.Fatalf("IndexManifest() = %v", err) } sii := ImageIndex(ni) sel := make([]oci.SignedEntity, 0, len(im.Manifests)+1) sel = append(sel, sii) for _, desc := range im.Manifests { switch desc.MediaType { case types.OCIImageIndex, types.DockerManifestList: se, err := sii.SignedImageIndex(desc.Digest) if err != nil { t.Fatalf("SignedImageIndex() = %v", err) } sel = append(sel, se) case types.OCIManifestSchema1, types.DockerManifestSchema2: se, err := sii.SignedImage(desc.Digest) if err != nil { t.Fatalf("SignedImage() = %v", err) } sel = append(sel, se) default: t.Errorf("Unsupported media type: %v", desc.MediaType) } } if se, err := sii.SignedImageIndex(v1.Hash{}); err == nil { t.Errorf("SignedImageIndex() = %#v, wanted error", se) } if se, err := sii.SignedImage(v1.Hash{}); err == nil { t.Errorf("SignedImage() = %#v, wanted error", se) } for _, se := range sel { sigs, err := se.Signatures() if err != nil { t.Fatalf("Signatures() = %v", err) } if sl, err := sigs.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got, want := len(sl), 0; got != want { t.Errorf("len(Get()) = %d, wanted %d", got, want) } atts, err := se.Attestations() if err != nil { t.Fatalf("Attestations() = %v", err) } if al, err := atts.Get(); err != nil { t.Errorf("Get() = %v", err) } else if got, want := len(al), 0; got != want { t.Errorf("len(Get()) = %d, wanted %d", got, want) } } } cosign-2.5.0/pkg/oci/static/000077500000000000000000000000001477503325500156355ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/static/file.go000066400000000000000000000047301477503325500171070ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package static import ( "io" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/types" payloadsize "github.com/sigstore/cosign/v2/internal/pkg/cosign/payload/size" "github.com/sigstore/cosign/v2/internal/pkg/now" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/signed" ) // NewFile constructs a new v1.Image with the provided payload. func NewFile(payload []byte, opts ...Option) (oci.File, error) { o, err := makeOptions(opts...) if err != nil { return nil, err } base := mutate.MediaType(empty.Image, types.OCIManifestSchema1) base = mutate.ConfigMediaType(base, o.ConfigMediaType) layer := &staticLayer{ b: payload, opts: o, } img, err := mutate.Append(base, mutate.Addendum{ Layer: layer, }) if err != nil { return nil, err } // Add annotations from options img = mutate.Annotations(img, o.Annotations).(v1.Image) if o.RecordCreationTimestamp { t, err := now.Now() if err != nil { return nil, err } // Set the Created date to time of execution img, err = mutate.CreatedAt(img, v1.Time{Time: t}) if err != nil { return nil, err } } return &file{ SignedImage: signed.Image(img), layer: layer, }, nil } type file struct { oci.SignedImage layer v1.Layer } var _ oci.File = (*file)(nil) // FileMediaType implements oci.File func (f *file) FileMediaType() (types.MediaType, error) { return f.layer.MediaType() } // Payload implements oci.File func (f *file) Payload() ([]byte, error) { size, err := f.layer.Size() if err != nil { return nil, err } err = payloadsize.CheckSize(uint64(size)) if err != nil { return nil, err } rc, err := f.layer.Uncompressed() if err != nil { return nil, err } defer rc.Close() return io.ReadAll(rc) } cosign-2.5.0/pkg/oci/static/file_test.go000066400000000000000000000121401477503325500201400ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package static import ( "errors" "io" "strings" "testing" "github.com/google/go-cmp/cmp" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" ) func TestNewFile(t *testing.T) { payload := "this is the content!" f, err := NewFile([]byte(payload), WithLayerMediaType("foo"), WithAnnotations(map[string]string{"foo": "bar"})) if err != nil { t.Fatalf("NewFile() = %v", err) } timestampedFile, err := NewFile([]byte(payload), WithLayerMediaType("foo"), WithAnnotations(map[string]string{"foo": "bar"}), WithRecordCreationTimestamp(true)) if err != nil { t.Fatalf("NewFile() = %v", err) } layers, err := f.Layers() if err != nil { t.Fatalf("Layers() = %v", err) } else if got, want := len(layers), 1; got != want { t.Fatalf("len(Layers()) = %d, wanted %d", got, want) } l := layers[0] t.Run("check size", func(t *testing.T) { wantSize := int64(len(payload)) gotSize, err := l.Size() if err != nil { t.Fatalf("Size() = %v", err) } if gotSize != wantSize { t.Errorf("Size() = %d, wanted %d", gotSize, wantSize) } }) t.Run("check media type", func(t *testing.T) { wantMT := types.MediaType("foo") gotMT, err := f.FileMediaType() if err != nil { t.Fatalf("MediaType() = %v", err) } if gotMT != wantMT { t.Errorf("MediaType() = %s, wanted %s", gotMT, wantMT) } }) t.Run("check hashes", func(t *testing.T) { wantHash, _, err := v1.SHA256(strings.NewReader(payload)) if err != nil { t.Fatalf("SHA256() = %v", err) } gotDigest, err := l.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } if !cmp.Equal(gotDigest, wantHash) { t.Errorf("Digest = %s", cmp.Diff(gotDigest, wantHash)) } gotDiffID, err := l.DiffID() if err != nil { t.Fatalf("DiffID() = %v", err) } if !cmp.Equal(gotDiffID, wantHash) { t.Errorf("DiffID = %s", cmp.Diff(gotDiffID, wantHash)) } }) t.Run("check content", func(t *testing.T) { comp, err := l.Compressed() if err != nil { t.Fatalf("Compressed() = %v", err) } defer comp.Close() compContent, err := io.ReadAll(comp) if err != nil { t.Fatalf("ReadAll() = %v", err) } if got, want := string(compContent), payload; got != want { t.Errorf("Compressed() = %s, wanted %s", got, want) } uncomp, err := l.Uncompressed() if err != nil { t.Fatalf("Uncompressed() = %v", err) } defer uncomp.Close() uncompContent, err := io.ReadAll(uncomp) if err != nil { t.Fatalf("ReadAll() = %v", err) } if got, want := string(uncompContent), payload; got != want { t.Errorf("Uncompressed() = %s, wanted %s", got, want) } gotPayload, err := f.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if got, want := string(gotPayload), payload; got != want { t.Errorf("Payload() = %s, wanted %s", got, want) } }) t.Run("check date", func(t *testing.T) { fileCfg, err := f.ConfigFile() if err != nil { t.Fatalf("ConfigFile() = %v", err) } if !fileCfg.Created.IsZero() { t.Errorf("Date of Signature was not Zero") } tsCfg, err := timestampedFile.ConfigFile() if err != nil { t.Fatalf("ConfigFile() = %v", err) } if tsCfg.Created.IsZero() { t.Errorf("Date of Signature was Zero") } }) t.Run("check annotations", func(t *testing.T) { m, err := f.Manifest() if err != nil { t.Fatalf("Manifest() = %v", err) } gotAnnotations := m.Annotations if got, want := gotAnnotations["foo"], "bar"; got != want { t.Errorf("Annotations = %s, wanted %s", got, want) } }) t.Run("huge file payload", func(t *testing.T) { // default limit f := file{ layer: &mockLayer{200000000}, } want := errors.New("size of layer (200000000) exceeded the limit (134217728)") _, err = f.Payload() if err == nil || want.Error() != err.Error() { t.Errorf("Payload() = %v, wanted %v", err, want) } // override limit t.Setenv("COSIGN_MAX_ATTACHMENT_SIZE", "512MiB") _, err = f.Payload() if err != nil { t.Errorf("Payload() = %v, wanted nil", err) } }) } type mockLayer struct { size int64 } func (m *mockLayer) Size() (int64, error) { return m.size, nil } func (m *mockLayer) Uncompressed() (io.ReadCloser, error) { return io.NopCloser(strings.NewReader("data")), nil } func (m *mockLayer) Digest() (v1.Hash, error) { panic("not implemented") } func (m *mockLayer) DiffID() (v1.Hash, error) { panic("not implemented") } func (m *mockLayer) Compressed() (io.ReadCloser, error) { panic("not implemented") } func (m *mockLayer) MediaType() (types.MediaType, error) { panic("not implemented") } cosign-2.5.0/pkg/oci/static/options.go000066400000000000000000000062551477503325500176670ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package static import ( "encoding/json" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" ctypes "github.com/sigstore/cosign/v2/pkg/types" ) // Option is a functional option for customizing static signatures. type Option func(*options) type options struct { LayerMediaType types.MediaType ConfigMediaType types.MediaType Bundle *bundle.RekorBundle RFC3161Timestamp *bundle.RFC3161Timestamp Cert []byte Chain []byte Annotations map[string]string RecordCreationTimestamp bool } func makeOptions(opts ...Option) (*options, error) { o := &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: types.OCIConfigJSON, Annotations: make(map[string]string), } for _, opt := range opts { opt(o) } if o.Cert != nil { o.Annotations[CertificateAnnotationKey] = string(o.Cert) o.Annotations[ChainAnnotationKey] = string(o.Chain) } if o.Bundle != nil { b, err := json.Marshal(o.Bundle) if err != nil { return nil, err } o.Annotations[BundleAnnotationKey] = string(b) } if o.RFC3161Timestamp != nil { b, err := json.Marshal(o.RFC3161Timestamp) if err != nil { return nil, err } o.Annotations[RFC3161TimestampAnnotationKey] = string(b) } return o, nil } // WithLayerMediaType sets the media type of the signature. func WithLayerMediaType(mt types.MediaType) Option { return func(o *options) { o.LayerMediaType = mt } } // WithConfigMediaType sets the media type of the signature. func WithConfigMediaType(mt types.MediaType) Option { return func(o *options) { o.ConfigMediaType = mt } } // WithAnnotations sets the annotations that will be associated. func WithAnnotations(ann map[string]string) Option { return func(o *options) { o.Annotations = ann } } // WithBundle sets the bundle to attach to the signature func WithBundle(b *bundle.RekorBundle) Option { return func(o *options) { o.Bundle = b } } // WithRFC3161Timestamp sets the time-stamping bundle to attach to the signature func WithRFC3161Timestamp(b *bundle.RFC3161Timestamp) Option { return func(o *options) { o.RFC3161Timestamp = b } } // WithCertChain sets the certificate chain for this signature. func WithCertChain(cert, chain []byte) Option { return func(o *options) { o.Cert = cert o.Chain = chain } } // WithRecordCreationTimestamp sets the feature flag to honor the creation timestamp to time of running func WithRecordCreationTimestamp(rct bool) Option { return func(o *options) { o.RecordCreationTimestamp = rct } } cosign-2.5.0/pkg/oci/static/options_test.go000066400000000000000000000106631477503325500207240ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package static import ( "reflect" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-containerregistry/pkg/v1/types" cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle" ctypes "github.com/sigstore/cosign/v2/pkg/types" ) func TestOptions(t *testing.T) { bundle := &cbundle.RekorBundle{} rfc3161Timestamp := &cbundle.RFC3161Timestamp{} tests := []struct { name string opts []Option want *options }{{ name: "no options", want: &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: types.OCIConfigJSON, Annotations: make(map[string]string), }, }, { name: "with layer media type", opts: []Option{WithLayerMediaType("foo")}, want: &options{ LayerMediaType: "foo", ConfigMediaType: types.OCIConfigJSON, Annotations: make(map[string]string), }, }, { name: "with config media type", opts: []Option{WithConfigMediaType("bar")}, want: &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: "bar", Annotations: make(map[string]string), }, }, { name: "with annotations", opts: []Option{WithAnnotations(map[string]string{ "foo": "bar", })}, want: &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ "foo": "bar", }, }, }, { name: "with cert chain", opts: []Option{WithCertChain([]byte("a"), []byte("b"))}, want: &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ CertificateAnnotationKey: "a", ChainAnnotationKey: "b", }, Cert: []byte("a"), Chain: []byte("b"), }, }, { name: "with bundle", opts: []Option{WithBundle(bundle)}, want: &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ BundleAnnotationKey: "{\"SignedEntryTimestamp\":null,\"Payload\":{\"body\":null,\"integratedTime\":0,\"logIndex\":0,\"logID\":\"\"}}", }, Bundle: bundle, }, }, { name: "with RFC3161 timestamp bundle", opts: []Option{WithRFC3161Timestamp(rfc3161Timestamp)}, want: &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ RFC3161TimestampAnnotationKey: "{\"SignedRFC3161Timestamp\":null}", }, RFC3161Timestamp: rfc3161Timestamp, }, }, { name: "with RFC3161Timestamp and Rekor bundle", opts: []Option{WithRFC3161Timestamp(rfc3161Timestamp), WithBundle(bundle)}, want: &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ RFC3161TimestampAnnotationKey: "{\"SignedRFC3161Timestamp\":null}", BundleAnnotationKey: "{\"SignedEntryTimestamp\":null,\"Payload\":{\"body\":null,\"integratedTime\":0,\"logIndex\":0,\"logID\":\"\"}}", }, RFC3161Timestamp: rfc3161Timestamp, Bundle: bundle, }, }, { name: "with RFC3161Timestamp and Rekor bundle", opts: []Option{WithRFC3161Timestamp(rfc3161Timestamp), WithBundle(bundle)}, want: &options{ LayerMediaType: ctypes.SimpleSigningMediaType, ConfigMediaType: types.OCIConfigJSON, Annotations: map[string]string{ RFC3161TimestampAnnotationKey: "{\"SignedRFC3161Timestamp\":null}", BundleAnnotationKey: "{\"SignedEntryTimestamp\":null,\"Payload\":{\"body\":null,\"integratedTime\":0,\"logIndex\":0,\"logID\":\"\"}}", }, RFC3161Timestamp: rfc3161Timestamp, Bundle: bundle, }, }} for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, err := makeOptions(test.opts...) if err != nil { t.Fatalf("makeOptions() = %v", err) } if !reflect.DeepEqual(got, test.want) { t.Errorf("makeOptions() = %s", cmp.Diff(got, test.want)) } }) } } cosign-2.5.0/pkg/oci/static/signature.go000066400000000000000000000130631477503325500201700ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package static import ( "bytes" "crypto/x509" "encoding/base64" "io" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/sigstore/pkg/cryptoutils" ) const ( SignatureAnnotationKey = "dev.cosignproject.cosign/signature" CertificateAnnotationKey = "dev.sigstore.cosign/certificate" ChainAnnotationKey = "dev.sigstore.cosign/chain" BundleAnnotationKey = "dev.sigstore.cosign/bundle" RFC3161TimestampAnnotationKey = "dev.sigstore.cosign/rfc3161timestamp" ) // NewSignature constructs a new oci.Signature from the provided options. func NewSignature(payload []byte, b64sig string, opts ...Option) (oci.Signature, error) { o, err := makeOptions(opts...) if err != nil { return nil, err } return &staticLayer{ b: payload, b64sig: b64sig, opts: o, }, nil } // NewAttestation constructs a new oci.Signature from the provided options. // Since Attestation is treated just like a Signature but the actual signature // is baked into the payload, the Signature does not actually have // the Base64Signature. func NewAttestation(payload []byte, opts ...Option) (oci.Signature, error) { return NewSignature(payload, "", opts...) } // Copy constructs a new oci.Signature from the provided one. func Copy(sig oci.Signature) (oci.Signature, error) { payload, err := sig.Payload() if err != nil { return nil, err } b64sig, err := sig.Base64Signature() if err != nil { return nil, err } var opts []Option mt, err := sig.MediaType() if err != nil { return nil, err } opts = append(opts, WithLayerMediaType(mt)) ann, err := sig.Annotations() if err != nil { return nil, err } opts = append(opts, WithAnnotations(ann)) bundle, err := sig.Bundle() if err != nil { return nil, err } opts = append(opts, WithBundle(bundle)) rfc3161Timestamp, err := sig.RFC3161Timestamp() if err != nil { return nil, err } opts = append(opts, WithRFC3161Timestamp(rfc3161Timestamp)) cert, err := sig.Cert() if err != nil { return nil, err } if cert != nil { rawCert, err := cryptoutils.MarshalCertificateToPEM(cert) if err != nil { return nil, err } chain, err := sig.Chain() if err != nil { return nil, err } rawChain, err := cryptoutils.MarshalCertificatesToPEM(chain) if err != nil { return nil, err } opts = append(opts, WithCertChain(rawCert, rawChain)) } return NewSignature(payload, b64sig, opts...) } type staticLayer struct { b []byte b64sig string opts *options } var _ v1.Layer = (*staticLayer)(nil) var _ oci.Signature = (*staticLayer)(nil) // Annotations implements oci.Signature func (l *staticLayer) Annotations() (map[string]string, error) { m := make(map[string]string, len(l.opts.Annotations)+1) for k, v := range l.opts.Annotations { m[k] = v } m[SignatureAnnotationKey] = l.b64sig return m, nil } // Payload implements oci.Signature func (l *staticLayer) Payload() ([]byte, error) { return l.b, nil } // Signature implements oci.Signature func (l *staticLayer) Signature() ([]byte, error) { b64sig, err := l.Base64Signature() if err != nil { return nil, err } return base64.StdEncoding.DecodeString(b64sig) } // Base64Signature implements oci.Signature func (l *staticLayer) Base64Signature() (string, error) { return l.b64sig, nil } // Cert implements oci.Signature func (l *staticLayer) Cert() (*x509.Certificate, error) { certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(l.opts.Cert)) if err != nil { return nil, err } if len(certs) == 0 { return nil, nil } return certs[0], nil } // Chain implements oci.Signature func (l *staticLayer) Chain() ([]*x509.Certificate, error) { certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(l.opts.Chain)) if err != nil { return nil, err } return certs, nil } // Bundle implements oci.Signature func (l *staticLayer) Bundle() (*bundle.RekorBundle, error) { return l.opts.Bundle, nil } // RFC3161Timestamp implements oci.Signature func (l *staticLayer) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) { return l.opts.RFC3161Timestamp, nil } // Digest implements v1.Layer func (l *staticLayer) Digest() (v1.Hash, error) { h, _, err := v1.SHA256(bytes.NewReader(l.b)) return h, err } // DiffID implements v1.Layer func (l *staticLayer) DiffID() (v1.Hash, error) { h, _, err := v1.SHA256(bytes.NewReader(l.b)) return h, err } // Compressed implements v1.Layer func (l *staticLayer) Compressed() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(l.b)), nil } // Uncompressed implements v1.Layer func (l *staticLayer) Uncompressed() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(l.b)), nil } // Size implements v1.Layer func (l *staticLayer) Size() (int64, error) { return int64(len(l.b)), nil } // MediaType implements v1.Layer func (l *staticLayer) MediaType() (types.MediaType, error) { return l.opts.LayerMediaType, nil } cosign-2.5.0/pkg/oci/static/signature_test.go000066400000000000000000000472711477503325500212370ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package static import ( "encoding/base64" "io" "strings" "testing" "github.com/google/go-cmp/cmp" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" ) func TestNewSignatureBasic(t *testing.T) { payload := "this is the content!" b64sig := "b64 content==" l, err := NewSignature([]byte(payload), b64sig, WithLayerMediaType("foo")) if err != nil { t.Fatalf("NewSignature() = %v", err) } t.Run("check size", func(t *testing.T) { wantSize := int64(len(payload)) gotSize, err := l.Size() if err != nil { t.Fatalf("Size() = %v", err) } if gotSize != wantSize { t.Errorf("Size() = %d, wanted %d", gotSize, wantSize) } }) t.Run("check media type", func(t *testing.T) { wantMT := types.MediaType("foo") gotMT, err := l.MediaType() if err != nil { t.Fatalf("MediaType() = %v", err) } if gotMT != wantMT { t.Errorf("MediaType() = %s, wanted %s", gotMT, wantMT) } }) t.Run("check hashes", func(t *testing.T) { wantHash, _, err := v1.SHA256(strings.NewReader(payload)) if err != nil { t.Fatalf("SHA256() = %v", err) } gotDigest, err := l.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } if !cmp.Equal(gotDigest, wantHash) { t.Errorf("Digest = %s", cmp.Diff(gotDigest, wantHash)) } gotDiffID, err := l.DiffID() if err != nil { t.Fatalf("DiffID() = %v", err) } if !cmp.Equal(gotDiffID, wantHash) { t.Errorf("DiffID = %s", cmp.Diff(gotDiffID, wantHash)) } }) t.Run("check content", func(t *testing.T) { comp, err := l.Compressed() if err != nil { t.Fatalf("Compressed() = %v", err) } defer comp.Close() compContent, err := io.ReadAll(comp) if err != nil { t.Fatalf("ReadAll() = %v", err) } if got, want := string(compContent), payload; got != want { t.Errorf("Compressed() = %s, wanted %s", got, want) } uncomp, err := l.Uncompressed() if err != nil { t.Fatalf("Uncompressed() = %v", err) } defer uncomp.Close() uncompContent, err := io.ReadAll(uncomp) if err != nil { t.Fatalf("ReadAll() = %v", err) } if got, want := string(uncompContent), payload; got != want { t.Errorf("Uncompressed() = %s, wanted %s", got, want) } gotPayload, err := l.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if got, want := string(gotPayload), payload; got != want { t.Errorf("Payload() = %s, wanted %s", got, want) } gotSig, err := l.Base64Signature() if err != nil { t.Fatalf("Base64Signature() = %v", err) } if got, want := gotSig, b64sig; got != want { t.Errorf("Base64Signature() = %s, wanted %s", got, want) } gotBundle, err := l.Bundle() if err != nil { t.Fatalf("Bundle() = %v", err) } if gotBundle != nil { t.Errorf("Bundle() = %#v, wanted nil", gotBundle) } }) t.Run("check annotations", func(t *testing.T) { want := map[string]string{ SignatureAnnotationKey: b64sig, } got, err := l.Annotations() if err != nil { t.Fatalf("Annotations() = %v", err) } if !cmp.Equal(got, want) { t.Errorf("Annotations = %s", cmp.Diff(got, want)) } }) t.Run("check signature accessors", func(t *testing.T) { gotPayload, err := l.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if got, want := string(gotPayload), payload; got != want { t.Errorf("Payload() = %s, wanted %s", got, want) } gotSig, err := l.Base64Signature() if err != nil { t.Fatalf("Base64Signature() = %v", err) } if got, want := gotSig, b64sig; got != want { t.Errorf("Base64Signature() = %s, wanted %s", got, want) } if got, err := l.Bundle(); err != nil { t.Fatalf("Bundle() = %v", err) } else if got != nil { t.Errorf("Bundle() = %#v, wanted nil", got) } if got, err := l.Cert(); err != nil { t.Fatalf("Cert() = %v", err) } else if got != nil { t.Errorf("Cert() = %#v, wanted nil", got) } if got, err := l.Chain(); err != nil { t.Fatalf("Chain() = %v", err) } else if len(got) != 0 { t.Errorf("len(Chain()) = %d, wanted 0", len(got)) } }) } func TestNewAttestationBasic(t *testing.T) { payload := "this is the content!" l, err := NewAttestation([]byte(payload), WithLayerMediaType("foo")) if err != nil { t.Fatalf("NewSignature() = %v", err) } t.Run("check size", func(t *testing.T) { wantSize := int64(len(payload)) gotSize, err := l.Size() if err != nil { t.Fatalf("Size() = %v", err) } if gotSize != wantSize { t.Errorf("Size() = %d, wanted %d", gotSize, wantSize) } }) t.Run("check media type", func(t *testing.T) { wantMT := types.MediaType("foo") gotMT, err := l.MediaType() if err != nil { t.Fatalf("MediaType() = %v", err) } if gotMT != wantMT { t.Errorf("MediaType() = %s, wanted %s", gotMT, wantMT) } }) t.Run("check hashes", func(t *testing.T) { wantHash, _, err := v1.SHA256(strings.NewReader(payload)) if err != nil { t.Fatalf("SHA256() = %v", err) } gotDigest, err := l.Digest() if err != nil { t.Fatalf("Digest() = %v", err) } if !cmp.Equal(gotDigest, wantHash) { t.Errorf("Digest = %s", cmp.Diff(gotDigest, wantHash)) } gotDiffID, err := l.DiffID() if err != nil { t.Fatalf("DiffID() = %v", err) } if !cmp.Equal(gotDiffID, wantHash) { t.Errorf("DiffID = %s", cmp.Diff(gotDiffID, wantHash)) } }) t.Run("check content", func(t *testing.T) { comp, err := l.Compressed() if err != nil { t.Fatalf("Compressed() = %v", err) } defer comp.Close() compContent, err := io.ReadAll(comp) if err != nil { t.Fatalf("ReadAll() = %v", err) } if got, want := string(compContent), payload; got != want { t.Errorf("Compressed() = %s, wanted %s", got, want) } uncomp, err := l.Uncompressed() if err != nil { t.Fatalf("Uncompressed() = %v", err) } defer uncomp.Close() uncompContent, err := io.ReadAll(uncomp) if err != nil { t.Fatalf("ReadAll() = %v", err) } if got, want := string(uncompContent), payload; got != want { t.Errorf("Uncompressed() = %s, wanted %s", got, want) } gotPayload, err := l.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if got, want := string(gotPayload), payload; got != want { t.Errorf("Payload() = %s, wanted %s", got, want) } gotSig, err := l.Base64Signature() if err != nil { t.Fatalf("Base64Signature() = %v", err) } if got, want := gotSig, ""; got != want { t.Errorf("Base64Signature() = %s, wanted %s", got, want) } gotBundle, err := l.Bundle() if err != nil { t.Fatalf("Bundle() = %v", err) } if gotBundle != nil { t.Errorf("Bundle() = %#v, wanted nil", gotBundle) } }) t.Run("check annotations", func(t *testing.T) { want := map[string]string{ SignatureAnnotationKey: "", } got, err := l.Annotations() if err != nil { t.Fatalf("Annotations() = %v", err) } if !cmp.Equal(got, want) { t.Errorf("Annotations = %s", cmp.Diff(got, want)) } }) t.Run("check signature accessors", func(t *testing.T) { gotPayload, err := l.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if got, want := string(gotPayload), payload; got != want { t.Errorf("Payload() = %s, wanted %s", got, want) } gotSig, err := l.Base64Signature() if err != nil { t.Fatalf("Base64Signature() = %v", err) } if got, want := gotSig, ""; got != want { t.Errorf("Base64Signature() = %s, wanted %s", got, want) } if got, err := l.Bundle(); err != nil { t.Fatalf("Bundle() = %v", err) } else if got != nil { t.Errorf("Bundle() = %#v, wanted nil", got) } if got, err := l.Cert(); err != nil { t.Fatalf("Cert() = %v", err) } else if got != nil { t.Errorf("Cert() = %#v, wanted nil", got) } if got, err := l.Chain(); err != nil { t.Fatalf("Chain() = %v", err) } else if len(got) != 0 { t.Errorf("len(Chain()) = %d, wanted 0", len(got)) } }) } func TestNewSignatureCertChainAndBundle(t *testing.T) { payload := "this is the other content!" b64sig := "b64 content=" // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 var ( cert = []byte(` -----BEGIN CERTIFICATE----- MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt 6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G 3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG 0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY= -----END CERTIFICATE----- `) chain = []byte(` -----BEGIN CERTIFICATE----- MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== -----END CERTIFICATE----- `) b = &bundle.RekorBundle{ SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), Payload: bundle.RekorPayload{ Body: "REMOVED", IntegratedTime: 1631646761, LogIndex: 693591, LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", }, } ) l, err := NewSignature([]byte(payload), b64sig, WithCertChain(cert, chain), WithBundle(b)) if err != nil { t.Fatalf("NewSignature() = %v", err) } t.Run("check signature accessors", func(t *testing.T) { gotPayload, err := l.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if got, want := string(gotPayload), payload; got != want { t.Errorf("Payload() = %s, wanted %s", got, want) } gotSig, err := l.Base64Signature() if err != nil { t.Fatalf("Base64Signature() = %v", err) } if got, want := gotSig, b64sig; got != want { t.Errorf("Base64Signature() = %s, wanted %s", got, want) } if got, err := l.Bundle(); err != nil { t.Fatalf("Bundle() = %v", err) } else if got != b { t.Errorf("Bundle() = %#v, wanted %#v", got, b) } if got, err := l.Cert(); err != nil { t.Fatalf("Cert() = %v", err) } else if got == nil { t.Error("Cert() = nil, wanted non-nil") } if got, err := l.Chain(); err != nil { t.Fatalf("Chain() = %v", err) } else if len(got) != 1 { t.Errorf("len(Chain()) = %d, wanted 1", len(got)) } }) t.Run("check annotations", func(t *testing.T) { want := map[string]string{ SignatureAnnotationKey: b64sig, CertificateAnnotationKey: string(cert), ChainAnnotationKey: string(chain), // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16. // The Body has been removed for brevity BundleAnnotationKey: `{"SignedEntryTimestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`, } got, err := l.Annotations() if err != nil { t.Fatalf("Annotations() = %v", err) } if !cmp.Equal(got, want) { t.Errorf("Annotations = %s", cmp.Diff(got, want)) } }) } func TestNewSignatureCertChainAndRekorRFC3161Timestamp(t *testing.T) { payload := "this is the other content!" b64sig := "b64 content=" // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 var ( cert = []byte(` -----BEGIN CERTIFICATE----- MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49 AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt 6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G 3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG 0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY= -----END CERTIFICATE----- `) chain = []byte(` -----BEGIN CERTIFICATE----- MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== -----END CERTIFICATE----- `) b = &bundle.RFC3161Timestamp{ SignedRFC3161Timestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), } rekorBundle = &bundle.RekorBundle{ SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="), Payload: bundle.RekorPayload{ Body: "REMOVED", IntegratedTime: 1631646761, LogIndex: 693591, LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", }, } ) l, err := NewSignature([]byte(payload), b64sig, WithCertChain(cert, chain), WithRFC3161Timestamp(b)) if err != nil { t.Fatalf("NewSignature() = %v", err) } t.Run("check tsa signature accessors", func(t *testing.T) { gotPayload, err := l.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if got, want := string(gotPayload), payload; got != want { t.Errorf("Payload() = %s, wanted %s", got, want) } gotSig, err := l.Base64Signature() if err != nil { t.Fatalf("Base64Signature() = %v", err) } if got, want := gotSig, b64sig; got != want { t.Errorf("Base64Signature() = %s, wanted %s", got, want) } if got, err := l.RFC3161Timestamp(); err != nil { t.Fatalf("RFC3161Timestamp() = %v", err) } else if got != b { t.Errorf("RFC3161Timestamp() = %#v, wanted %#v", got, b) } if got, err := l.Cert(); err != nil { t.Fatalf("Cert() = %v", err) } else if got == nil { t.Error("Cert() = nil, wanted non-nil") } if got, err := l.Chain(); err != nil { t.Fatalf("Chain() = %v", err) } else if len(got) != 1 { t.Errorf("len(Chain()) = %d, wanted 1", len(got)) } }) t.Run("check tsa annotations", func(t *testing.T) { want := map[string]string{ SignatureAnnotationKey: b64sig, CertificateAnnotationKey: string(cert), ChainAnnotationKey: string(chain), RFC3161TimestampAnnotationKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="}`, } got, err := l.Annotations() if err != nil { t.Fatalf("Annotations() = %v", err) } if !cmp.Equal(got, want) { t.Errorf("Annotations = %s", cmp.Diff(got, want)) } }) newSig, err := NewSignature([]byte(payload), b64sig, WithCertChain(cert, chain), WithRFC3161Timestamp(b), WithBundle(rekorBundle)) if err != nil { t.Fatalf("NewSignature() = %v", err) } t.Run("check signature accessors", func(t *testing.T) { gotPayload, err := newSig.Payload() if err != nil { t.Fatalf("Payload() = %v", err) } if got, want := string(gotPayload), payload; got != want { t.Errorf("Payload() = %s, wanted %s", got, want) } gotSig, err := newSig.Base64Signature() if err != nil { t.Fatalf("Base64Signature() = %v", err) } if got, want := gotSig, b64sig; got != want { t.Errorf("Base64Signature() = %s, wanted %s", got, want) } if got, err := newSig.Bundle(); err != nil { t.Fatalf("Bundle() = %v", err) } else if got != rekorBundle { t.Errorf("Bundle() = %#v, wanted %#v", got, b) } if got, err := newSig.RFC3161Timestamp(); err != nil { t.Fatalf("RFC3161Timestamp() = %v", err) } else if got != b { t.Errorf("RFC3161Timestamp() = %#v, wanted %#v", got, b) } if got, err := newSig.Cert(); err != nil { t.Fatalf("Cert() = %v", err) } else if got == nil { t.Error("Cert() = nil, wanted non-nil") } if got, err := newSig.Chain(); err != nil { t.Fatalf("Chain() = %v", err) } else if len(got) != 1 { t.Errorf("len(Chain()) = %d, wanted 1", len(got)) } }) t.Run("check rekor and tsa annotations", func(t *testing.T) { want := map[string]string{ SignatureAnnotationKey: b64sig, CertificateAnnotationKey: string(cert), ChainAnnotationKey: string(chain), RFC3161TimestampAnnotationKey: `{"SignedRFC3161Timestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="}`, BundleAnnotationKey: `{"SignedEntryTimestamp":"MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE=","Payload":{"body":"REMOVED","integratedTime":1631646761,"logIndex":693591,"logID":"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d"}}`, } got, err := newSig.Annotations() if err != nil { t.Fatalf("Annotations() = %v", err) } if !cmp.Equal(got, want) { t.Errorf("Annotations = %s", cmp.Diff(got, want)) } }) } func TestNewSignatureBadCertChain(t *testing.T) { payload := "this is the other content!" b64sig := "b64 content=" // This was extracted from gcr.io/distroless/static:nonroot on 2021/09/16 var ( cert = []byte(` -----BEGIN CERTIFICATE----- garbage in -----END CERTIFICATE----- `) chain = []byte(` -----BEGIN CERTIFICATE----- garbage out -----END CERTIFICATE----- `) ) l, err := NewSignature([]byte(payload), b64sig, WithCertChain(cert, chain)) if err != nil { t.Fatalf("NewSignature() = %v", err) } t.Run("check signature accessors", func(t *testing.T) { if got, err := l.Cert(); err == nil { t.Fatalf("Cert() = %#v, wanted error", got) } if got, err := l.Chain(); err == nil { t.Fatalf("Chain() = %#v, wanted error", got) } }) } func mustDecode(s string) []byte { b, err := base64.StdEncoding.DecodeString(s) if err != nil { panic(err.Error()) } return b } cosign-2.5.0/pkg/oci/walk/000077500000000000000000000000001477503325500153045ustar00rootroot00000000000000cosign-2.5.0/pkg/oci/walk/walk.go000066400000000000000000000027171477503325500166000ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package walk import ( "context" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ) // Fn is the signature of the callback supplied to SignedEntity. // The oci.SignedEntity is either an oci.SignedImageIndex or an oci.SignedImage. // This callback is called on oci.SignedImageIndex *before* its children. type Fn func(context.Context, oci.SignedEntity) error // SignedEntity calls `fn` on the signed entity and each of its constituent entities // (`SignedImageIndex` or `SignedImage`) transitively. // Any errors returned by an `fn` are returned by `Walk`. func SignedEntity(ctx context.Context, parent oci.SignedEntity, fn Fn) error { _, err := mutate.Map(ctx, parent, func(ctx context.Context, se oci.SignedEntity) (oci.SignedEntity, error) { if err := fn(ctx, se); err != nil { return nil, err } return se, nil }) return err } cosign-2.5.0/pkg/oci/walk/walk_test.go000066400000000000000000000045501477503325500176340ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package walk import ( "context" "errors" "testing" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/signed" ) func TestMapImage(t *testing.T) { i, err := random.Image(300 /* bytes */, 3 /* layers */) if err != nil { t.Fatalf("random.Image() = %v", err) } si := signed.Image(i) t.Run("walk image, no errors", func(t *testing.T) { calls := 0 err := SignedEntity(context.Background(), si, func(_ context.Context, _ oci.SignedEntity) error { calls++ return nil }) if err != nil { t.Fatalf("Map() = %v", err) } if calls != 1 { t.Fatalf("Map called %d times, wanted 1", calls) } }) t.Run("error propagates", func(t *testing.T) { want := errors.New("this is the error I expect") got := SignedEntity(context.Background(), si, func(_ context.Context, _ oci.SignedEntity) error { return want }) if !errors.Is(got, want) { t.Fatalf("Map() = %v, wanted %v", got, want) } }) } func TestMapImageIndex(t *testing.T) { ii, err := random.Index(300 /* bytes */, 3 /* layers */, 2 /* images */) if err != nil { t.Fatalf("random.Image() = %v", err) } ii2, err := random.Index(300 /* bytes */, 3 /* layers */, 2 /* images */) if err != nil { t.Fatalf("random.Image() = %v", err) } sii := signed.ImageIndex(mutate.AppendManifests(ii, mutate.IndexAddendum{ Add: ii2, })) t.Run("six calls to identity mutator", func(t *testing.T) { calls := 0 err := SignedEntity(context.Background(), sii, func(_ context.Context, _ oci.SignedEntity) error { calls++ return nil }) if err != nil { t.Fatalf("Map() = %v", err) } if calls != 6 { t.Fatalf("Map called %d times, wanted 6", calls) } }) } cosign-2.5.0/pkg/policy/000077500000000000000000000000001477503325500150735ustar00rootroot00000000000000cosign-2.5.0/pkg/policy/attestation.go000066400000000000000000000141601477503325500177630ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy import ( "context" "encoding/base64" "encoding/json" "errors" "fmt" "github.com/in-toto/in-toto-golang/in_toto" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign/attestation" "github.com/sigstore/cosign/v2/pkg/oci" ) // PayloadProvider is a subset of oci.Signature that only provides the // Payload() method. type PayloadProvider interface { // Payload fetches the opaque data that is being signed. // This will always return data when there is no error. Payload() ([]byte, error) } // Assert that oci.Signature implements PayloadProvider var _ PayloadProvider = (oci.Signature)(nil) // AttestationToPayloadJSON takes in a verified Attestation (oci.Signature) and // marshals it into a JSON depending on the payload that's then consumable // by policy engine like cue, rego, etc. // // Anything fed here must have been validated with either // `VerifyLocalImageAttestations` or `VerifyImageAttestations` // // If there's no error, and payload is empty means the predicateType did not // match the attestation. // Returns the attestation type (PredicateType) if the payload was decoded // before the error happened, or in the case the predicateType that was // requested does not match. This is useful for callers to be able to provide // better error messages. For example, if there's a typo in the predicateType, // or the predicateType is not the one they are looking for. Without returning // this, it's hard for users to know which attestations/predicateTypes were // inspected. func AttestationToPayloadJSON(_ context.Context, predicateType string, verifiedAttestation PayloadProvider) ([]byte, string, error) { if predicateType == "" { return nil, "", errors.New("missing predicate type") } predicateURI, ok := options.PredicateTypeMap[predicateType] if !ok { // Not a custom one, use it as is. predicateURI = predicateType } var payloadData map[string]interface{} p, err := verifiedAttestation.Payload() if err != nil { return nil, "", fmt.Errorf("getting payload: %w", err) } err = json.Unmarshal(p, &payloadData) if err != nil { return nil, "", fmt.Errorf("unmarshaling payload data") } var decodedPayload []byte if val, ok := payloadData["payload"]; ok { decodedPayload, err = base64.StdEncoding.DecodeString(val.(string)) if err != nil { return nil, "", fmt.Errorf("decoding payload: %w", err) } } else { return nil, "", fmt.Errorf("could not find payload in payload data") } // Only apply the policy against the requested predicate type var statement in_toto.Statement if err := json.Unmarshal(decodedPayload, &statement); err != nil { return nil, "", fmt.Errorf("unmarshal in-toto statement: %w", err) } if statement.PredicateType != predicateURI { // This is not the predicate we're looking for, so skip it. return nil, statement.PredicateType, nil } // NB: In many (all?) of these cases, we could just return the // 'json.Marshal', but we check for errors here to decorate them // with more meaningful error message. var payload []byte switch predicateType { case options.PredicateCustom: payload, err = json.Marshal(statement) if err != nil { return nil, statement.PredicateType, fmt.Errorf("generating CosignStatement: %w", err) } case options.PredicateLink: var linkStatement in_toto.LinkStatement if err := json.Unmarshal(decodedPayload, &linkStatement); err != nil { return nil, statement.PredicateType, fmt.Errorf("unmarshaling LinkStatement: %w", err) } payload, err = json.Marshal(linkStatement) if err != nil { return nil, statement.PredicateType, fmt.Errorf("marshaling LinkStatement: %w", err) } case options.PredicateSLSA: var slsaProvenanceStatement in_toto.ProvenanceStatementSLSA02 if err := json.Unmarshal(decodedPayload, &slsaProvenanceStatement); err != nil { return nil, statement.PredicateType, fmt.Errorf("unmarshaling ProvenanceStatementSLSA02): %w", err) } payload, err = json.Marshal(slsaProvenanceStatement) if err != nil { return nil, statement.PredicateType, fmt.Errorf("marshaling ProvenanceStatementSLSA02: %w", err) } case options.PredicateSPDX, options.PredicateSPDXJSON: var spdxStatement in_toto.SPDXStatement if err := json.Unmarshal(decodedPayload, &spdxStatement); err != nil { return nil, statement.PredicateType, fmt.Errorf("unmarshaling SPDXStatement: %w", err) } payload, err = json.Marshal(spdxStatement) if err != nil { return nil, statement.PredicateType, fmt.Errorf("marshaling SPDXStatement: %w", err) } case options.PredicateCycloneDX: var cyclonedxStatement in_toto.CycloneDXStatement if err := json.Unmarshal(decodedPayload, &cyclonedxStatement); err != nil { return nil, statement.PredicateType, fmt.Errorf("unmarshaling CycloneDXStatement: %w", err) } payload, err = json.Marshal(cyclonedxStatement) if err != nil { return nil, statement.PredicateType, fmt.Errorf("marshaling CycloneDXStatement: %w", err) } case options.PredicateVuln: var vulnStatement attestation.CosignVulnStatement if err := json.Unmarshal(decodedPayload, &vulnStatement); err != nil { return nil, statement.PredicateType, fmt.Errorf("unmarshaling CosignVulnStatement: %w", err) } payload, err = json.Marshal(vulnStatement) if err != nil { return nil, statement.PredicateType, fmt.Errorf("marshaling CosignVulnStatement: %w", err) } default: // Valid URI type reaches here. payload, err = json.Marshal(statement) if err != nil { return nil, statement.PredicateType, fmt.Errorf("generating Statement: %w", err) } } return payload, statement.PredicateType, nil } cosign-2.5.0/pkg/policy/attestation_test.go000066400000000000000000000177421477503325500210330ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy import ( "bytes" "context" "crypto/x509" "encoding/json" "fmt" "io" "os" "strings" "testing" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/in-toto/in-toto-golang/in_toto" "github.com/sigstore/cosign/v2/pkg/cosign/attestation" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/oci" "github.com/sigstore/cosign/v2/pkg/oci/static" ) type failingAttestation struct { } func (fa *failingAttestation) Payload() ([]byte, error) { return nil, fmt.Errorf("inducing test failure") } func (fa *failingAttestation) Annotations() (map[string]string, error) { return nil, fmt.Errorf("unimplemented") } func (fa *failingAttestation) Signature() ([]byte, error) { return nil, fmt.Errorf("unimplemented") } func (fa *failingAttestation) Base64Signature() (string, error) { return "", fmt.Errorf("unimplemented") } func (fa *failingAttestation) Cert() (*x509.Certificate, error) { return nil, fmt.Errorf("unimplemented") } func (fa *failingAttestation) Chain() ([]*x509.Certificate, error) { return nil, fmt.Errorf("unimplemented") } func (fa *failingAttestation) Bundle() (*bundle.RekorBundle, error) { return nil, fmt.Errorf("unimplemented") } func (fa *failingAttestation) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) { return nil, fmt.Errorf("unimplemented") } func (fa *failingAttestation) Digest() (v1.Hash, error) { return v1.Hash{}, fmt.Errorf("unimplemented") } func (fa *failingAttestation) DiffID() (v1.Hash, error) { return v1.Hash{}, fmt.Errorf("unimplemented") } func (fa *failingAttestation) Compressed() (io.ReadCloser, error) { return nil, fmt.Errorf("unimplemented") } func (fa *failingAttestation) Uncompressed() (io.ReadCloser, error) { return nil, fmt.Errorf("unimplemented") } func (fa *failingAttestation) Size() (int64, error) { return 0, fmt.Errorf("unimplemented") } func (fa *failingAttestation) MediaType() (types.MediaType, error) { return types.DockerConfigJSON, fmt.Errorf("unimplemented") } var _ oci.Signature = (*failingAttestation)(nil) const ( // Result of "echo 'nottotostatement' | base64" // invalidTotoStatement = "bm90dG90b3N0YXRlbWVudAo=" invalidTotoStatement = `{"payloadType":"application/vnd.in-toto+json","payload":"bm90dG90b3N0YXRlbWVudAo"}` ) func checkFailure(t *testing.T, want string, err error) { t.Helper() if err == nil { t.Fatalf("Expected error, got none") } if !strings.Contains(err.Error(), want) { t.Errorf("Failed to get the expected error of %q, got: %s", want, err) } } func TestFailures(t *testing.T) { tests := []struct { payload string predicateType string wantErrSubstring string }{{payload: "", wantErrSubstring: "unmarshaling payload data"}, {payload: "{badness", wantErrSubstring: "unmarshaling payload data"}, {payload: `{"payloadType":"notmarshallable}`, wantErrSubstring: "unmarshaling payload data"}, {payload: `{"payload":"shou!ln'twork"}`, wantErrSubstring: "decoding payload"}, {payload: `{"payloadType":"finebutnopayload"}`, wantErrSubstring: "could not find payload"}, {payload: invalidTotoStatement, wantErrSubstring: "decoding payload: illegal base64"}, } for _, tc := range tests { att, err := static.NewSignature([]byte(tc.payload), "") if err != nil { t.Fatal("Failed to create static.NewSignature: ", err) } predicateType := tc.predicateType if predicateType == "" { predicateType = "custom" } _, _, err = AttestationToPayloadJSON(context.TODO(), predicateType, att) checkFailure(t, tc.wantErrSubstring, err) } } // TestMalformedPayload tests various non-predicate specific failures that // are done even before we start processing the payload. // This just stands alone since didn't want to complicate above tests with // constructing different attestations there. func TestErroringPayload(t *testing.T) { // Payload() call fails _, _, err := AttestationToPayloadJSON(context.TODO(), "custom", &failingAttestation{}) checkFailure(t, "inducing test failure", err) } func TestAttestationToPayloadJson(t *testing.T) { dir := "valid" files := getDirFiles(t, dir) for _, fileName := range files { bytes := readAttestationFromTestFile(t, dir, fileName) ociSig, err := static.NewSignature(bytes, "") if err != nil { t.Fatal("Failed to create static.NewSignature: ", err) } jsonBytes, gotPredicateType, err := AttestationToPayloadJSON(context.TODO(), fileName, ociSig) if err != nil { t.Fatalf("Failed to convert : %s", err) } switch fileName { case "custom": var intoto in_toto.Statement if err := json.Unmarshal(jsonBytes, &intoto); err != nil { t.Fatalf("[%s] Wanted custom statement, can't unmarshal to it: %v", fileName, err) } checkPredicateType(t, attestation.CosignCustomProvenanceV01, intoto.PredicateType) checkPredicateType(t, gotPredicateType, intoto.PredicateType) case "vuln": var vulnStatement attestation.CosignVulnStatement if err := json.Unmarshal(jsonBytes, &vulnStatement); err != nil { t.Fatalf("[%s] Wanted vuln statement, can't unmarshal to it: %v", fileName, err) } checkPredicateType(t, attestation.CosignVulnProvenanceV01, vulnStatement.PredicateType) checkPredicateType(t, gotPredicateType, vulnStatement.PredicateType) case "default": t.Fatal("non supported predicate file") } } } type myPayloadProvider struct { payload []byte } func (m *myPayloadProvider) Payload() ([]byte, error) { return m.payload, nil } // assert that myPayloadProvider implements PayloadProvider var _ PayloadProvider = &myPayloadProvider{} // TestPayloadProvider tests that the PayloadProvider interface is working as expected. func TestPayloadProvider(t *testing.T) { // Control: oci.Signature attestationBytes := readAttestationFromTestFile(t, "valid", "vuln") ociSig, err := static.NewSignature(attestationBytes, "") if err != nil { t.Fatal("Failed to create static.NewSignature: ", err) } jsonBytes, gotPredicateType, err := AttestationToPayloadJSON(context.TODO(), "vuln", ociSig) if err != nil { t.Fatalf("Failed to convert : %s", err) } if len(jsonBytes) == 0 { t.Fatalf("Failed to get jsonBytes") } if gotPredicateType != attestation.CosignVulnProvenanceV01 { t.Fatalf("Did not get expected predicateType, want: %s got: %s", attestation.CosignVulnProvenanceV01, gotPredicateType) } // Test: myPayloadProvider provider := &myPayloadProvider{payload: attestationBytes} jsonBytes2, gotPredicateType2, err := AttestationToPayloadJSON(context.TODO(), "vuln", provider) if err != nil { t.Fatalf("Failed to convert : %s", err) } if !bytes.Equal(jsonBytes, jsonBytes2) { t.Fatalf("Expected same jsonBytes, got different") } if gotPredicateType != gotPredicateType2 { t.Fatalf("Expected same predicateType, got different") } } func checkPredicateType(t *testing.T, want, got string) { t.Helper() if want != got { t.Errorf("Did not get expected predicateType, want: %s got: %s", want, got) } } func readAttestationFromTestFile(t *testing.T, dir, name string) []byte { t.Helper() b, err := os.ReadFile(fmt.Sprintf("testdata/%s/%s", dir, name)) if err != nil { t.Fatalf("Failed to read file : %s ReadFile() = %s", name, err) } return b } func getDirFiles(t *testing.T, dir string) []string { files, err := os.ReadDir(fmt.Sprintf("testdata/%s", dir)) if err != nil { t.Fatalf("Failed to read dir : %s ReadFile() = %s", dir, err) } ret := []string{} for _, file := range files { ret = append(ret, file.Name()) } return ret } cosign-2.5.0/pkg/policy/errors.go000066400000000000000000000014301477503325500167340ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy type EvaluationFailure struct { err error } func (e *EvaluationFailure) Error() string { return e.err.Error() } func (e *EvaluationFailure) Unwrap() error { return e.err } cosign-2.5.0/pkg/policy/eval.go000066400000000000000000000056241477503325500163600ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy import ( "context" "fmt" "cuelang.org/go/cue/cuecontext" "github.com/sigstore/cosign/v2/pkg/cosign/rego" ) // EvaluatePolicyAgainstJson is used to run a policy engine against JSON bytes. // These bytes can be for example Attestations, or ClusterImagePolicy result // types. // name - which attestation are we evaluating // policyType - cue|rego // policyBody - String representing either cue or rego language // jsonBytes - Bytes to evaluate against the policyBody in the given language func EvaluatePolicyAgainstJSON(ctx context.Context, name, policyType string, policyBody string, jsonBytes []byte) (warnings error, errors error) { switch policyType { case "cue": cueValidationErr := evaluateCue(ctx, jsonBytes, policyBody) if cueValidationErr != nil { return nil, &EvaluationFailure{ fmt.Errorf("failed evaluating cue policy for %s: %w", name, cueValidationErr), } } case "rego": regoValidationWarn, regoValidationErr := evaluateRego(ctx, jsonBytes, policyBody) if regoValidationErr != nil { return regoValidationWarn, &EvaluationFailure{ fmt.Errorf("failed evaluating rego policy for type %s: %w", name, regoValidationErr), } } // It is possible to return warning messages when the policy is compliant return regoValidationWarn, regoValidationErr default: return nil, fmt.Errorf("sorry Type %s is not supported yet", policyType) } return nil, nil } // evaluateCue evaluates a cue policy `evaluator` against `attestation` func evaluateCue(_ context.Context, attestation []byte, evaluator string) error { cueCtx := cuecontext.New() cueEvaluator := cueCtx.CompileString(evaluator) if cueEvaluator.Err() != nil { return fmt.Errorf("failed to compile the cue policy with error: %w", cueEvaluator.Err()) } cueAtt := cueCtx.CompileBytes(attestation) if cueAtt.Err() != nil { return fmt.Errorf("failed to compile the attestation data with error: %w", cueAtt.Err()) } result := cueEvaluator.Unify(cueAtt) if err := result.Validate(); err != nil { return fmt.Errorf("failed to evaluate the policy with error: %w", err) } return nil } // evaluateRego evaluates a rego policy `evaluator` against `attestation` func evaluateRego(_ context.Context, attestation []byte, evaluator string) (warnings error, errors error) { return rego.ValidateJSONWithModuleInput(attestation, evaluator) } cosign-2.5.0/pkg/policy/eval_test.go000066400000000000000000000231421477503325500174120ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy import ( "context" "strings" "testing" ) const ( customAttestation = ` { "_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://cosign.sigstore.dev/attestation/v1", "subject": [ { "name": "registry.local:5000/policy-controller/demo", "digest": { "sha256": "416cc82c76114b1744ea58bcbf2f411a0f2de4b0456703bf1bb83d33656951bc" } } ], "predicate": { "Data": "foobar e2e test", "Timestamp": "2022-04-20T18:17:19Z" } }` vulnAttestation = ` { "_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1", "subject": [ { "name": "registry.local:5000/policy-controller/demo", "digest": { "sha256": "416cc82c76114b1744ea58bcbf2f411a0f2de4b0456703bf1bb83d33656951bc" } } ], "predicate": { "invocation": { "parameters": null, "uri": "invocation.example.com/cosign-testing", "event_id": "", "builder.id": "" }, "scanner": { "uri": "fakescanner.example.com/cosign-testing", "version": "", "db": { "uri": "", "version": "" }, "result": null }, "metadata": { "scanStartedOn": "2022-04-12T00:00:00Z", "scanFinishedOn": "2022-04-12T00:10:00Z" } } }` cipAttestation = "{\"authorityMatches\":{\"keyatt\":{\"signatures\":null,\"attestations\":{\"vuln-key\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}},\"keysignature\":{\"signatures\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}],\"attestations\":null},\"keylessatt\":{\"signatures\":null,\"attestations\":{\"custom-keyless\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}}}}" ) func TestEvalPolicy(t *testing.T) { // TODO(vaikas): Consider moving the attestations/cue files into testdata // directory. tests := []struct { name string json string policyType string policyFile string wantErr bool wantErrSub string wantWarnSub string }{{ name: "custom attestation, mismatched predicateType", json: customAttestation, policyType: "cue", policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1"`, wantErr: true, wantErrSub: `conflicting values "https://cosign.sigstore.dev/attestation/v1" and "https://cosign.sigstore.dev/attestation/vuln/v1"`, }, { name: "custom attestation, predicateType and data checks out", json: customAttestation, policyType: "cue", policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/v1" predicate: Data: "foobar e2e test"`, }, { name: "custom attestation, data mismatch", json: customAttestation, policyType: "cue", policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/v1" predicate: Data: "invalid data here"`, wantErr: true, wantErrSub: `predicate.Data: conflicting values "foobar e2e test" and "invalid data here"`, }, { name: "vuln attestation, wrong invocation url", json: vulnAttestation, policyType: "cue", policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1" predicate: invocation: uri: "invocation.example.com/wrong-url-here"`, wantErr: true, wantErrSub: `conflicting values "invocation.example.com/cosign-testing" and "invocation.example.com/wrong-url-here"`, }, { name: "vuln attestation, checks out", json: vulnAttestation, policyType: "cue", policyFile: `predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1" predicate: invocation: uri: "invocation.example.com/cosign-testing"`, }, { name: "cluster image policy main policy, checks out", json: cipAttestation, policyType: "cue", policyFile: `package sigstore import "struct" import "list" authorityMatches: { keyatt: { attestations: struct.MaxFields(1) & struct.MinFields(1) }, keysignature: { signatures: list.MaxItems(1) & list.MinItems(1) }, keylessatt: { attestations: struct.MaxFields(1) & struct.MinFields(1) }, keylesssignature: { signatures: list.MaxItems(1) & list.MinItems(1) } }`, }, { name: "cluster image policy main policy, fails", json: cipAttestation, policyType: "cue", wantErr: true, wantErrSub: `failed evaluating cue policy for cluster image policy main policy, fails: failed to evaluate the policy with error: authorityMatches.keylessattMinAttestations: conflicting values 2 and "Error" (mismatched types int and string)`, policyFile: `package sigstore import "struct" import "list" authorityMatches: { keyatt: { attestations: struct.MaxFields(1) & struct.MinFields(1) }, keysignature: { signatures: list.MaxItems(1) & list.MinItems(1) }, if( len(authorityMatches.keylessatt.attestations) < 2) { keylessattMinAttestations: 2 keylessattMinAttestations: "Error" }, keylesssignature: { signatures: list.MaxItems(1) & list.MinItems(1) } }`}, { name: "Rego cluster image policy main policy, checks out", json: cipAttestation, policyType: "rego", policyFile: `package sigstore default isCompliant = false isCompliant { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations count(attestationsKeylessATT) == 1 attestationsKeyATT := input.authorityMatches.keyatt.attestations count(attestationsKeyATT) == 1 keySignature := input.authorityMatches.keysignature.signatures count(keySignature) == 1 }`, }, { name: "Rego cluster image policy main policy, fails", json: cipAttestation, policyType: "rego", wantErr: true, wantErrSub: `failed evaluating rego policy for type Rego cluster image policy main policy, fails: policy is not compliant for query 'isCompliant = data.sigstore.isCompliant'`, policyFile: `package sigstore default isCompliant = false isCompliant { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations count(attestationsKeylessATT) == 2 attestationsKeyATT := input.authorityMatches.keyatt.attestations count(attestationsKeyATT) == 1 keySignature := input.authorityMatches.keysignature.signatures count(keySignature) == 1 }`, }, { name: "Rego cluster image policy main policy succeed with empty error msg", json: cipAttestation, policyType: "rego", policyFile: `package sigstore isCompliant[response] { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations result = (count(attestationsKeylessATT) == 1) attestationsKeyATT := input.authorityMatches.keyatt.attestations result = (count(attestationsKeyATT) == 1) keySignature := input.authorityMatches.keysignature.signatures result = (count(keySignature) == 1) errorMsg = "" warnMsg = "" response := { "result" : result, "error" : errorMsg, "warning" : warnMsg } }`, }, { name: "Rego cluster image policy main policy, fails with custom error msg", json: cipAttestation, policyType: "rego", wantErr: true, wantErrSub: `Not found expected list of attestations`, policyFile: `package sigstore isCompliant[response] { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations result = (count(attestationsKeylessATT) == 1000) errorMsg = "Not found expected list of attestations" warnMsg = "" response := { "result" : result, "error" : errorMsg, "warning" : warnMsg } }`, }, { name: "Rego cluster image policy main policy, returns a custom warning msg", json: cipAttestation, policyType: "rego", wantErr: false, wantWarnSub: `Throw warning error even if succeeded`, policyFile: `package sigstore isCompliant[response] { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations result = (count(attestationsKeylessATT) == 1) attestationsKeyATT := input.authorityMatches.keyatt.attestations result = (count(attestationsKeyATT) == 1) keySignature := input.authorityMatches.keysignature.signatures result = (count(keySignature) == 1) errorMsg = "" warnMsg = "Throw warning error even if succeeded" response := { "result" : result, "error" : errorMsg, "warning" : warnMsg } }`, }} for _, tc := range tests { ctx := context.Background() warn, err := EvaluatePolicyAgainstJSON(ctx, tc.name, tc.policyType, tc.policyFile, []byte(tc.json)) if tc.wantErr { if err == nil { t.Errorf("Did not get an error, wanted %s", tc.wantErrSub) } else if !strings.Contains(err.Error(), tc.wantErrSub) { t.Errorf("Unexpected error, want: %s got: %s", tc.wantErrSub, err.Error()) } if tc.wantWarnSub != "" && !strings.Contains(warn.Error(), tc.wantWarnSub) { t.Errorf("Unexpected warning, want: %s got: %s", tc.wantErrSub, err.Error()) } } else { if !tc.wantErr && err != nil { t.Errorf("Unexpected error, wanted none, got: %s", err.Error()) } if tc.wantWarnSub != "" && !strings.Contains(warn.Error(), tc.wantWarnSub) { t.Errorf("Unexpected warning, want: %s got: %s", tc.wantWarnSub, warn.Error()) } } } } cosign-2.5.0/pkg/policy/fuzz_test.go000066400000000000000000000027321477503325500174630ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package policy /* disabling since this effectively just fuzzes rego & cue vs something in cosign import ( "context" "runtime" "testing" ) var policyTypes = []string{"cue", "rego"} func catchPanics() { if r := recover(); r != nil { var errStr string switch err := r.(type) { case string: errStr = err case runtime.Error: errStr = err.Error() case error: errStr = err.Error() } switch { case errStr == "freeNode: nodeContext out of sync": return case errStr == "unreachable": return default: panic(errStr) } } } func FuzzEvaluatePolicyAgainstJSON(f *testing.F) { f.Fuzz(func(_ *testing.T, name, policyBody string, jsonBytes []byte, policyType uint8) { defer catchPanics() choosePolicyType := policyTypes[int(policyType)%len(policyTypes)] EvaluatePolicyAgainstJSON(context.Background(), name, choosePolicyType, policyBody, jsonBytes) }) } */ cosign-2.5.0/pkg/policy/testdata/000077500000000000000000000000001477503325500167045ustar00rootroot00000000000000cosign-2.5.0/pkg/policy/testdata/malformed/000077500000000000000000000000001477503325500206525ustar00rootroot00000000000000cosign-2.5.0/pkg/policy/testdata/malformed/custom000066400000000000000000000011611477503325500221060ustar00rootroot00000000000000{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6InJlZ2lzdHJ5LmxvY2FsOjUwMDAva25hdGl2ZS9kZW1vIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjZjNmZkNmE0MTE1YzZlOTk4ZmYzNTdjZDkxNDY4MDkzMWJiOWE2YzFhN2NkNWY1Y2IyZjVlMWMwOTMyYWI2ZWQifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImZvb2JhciB0ZXN0IGF0dGVzdGF0aW9uIiwiVGltZXN0YW1wIjoiMjAyMi0wNC0wN1QxOToyMjoyNVoifX0=","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]} cosign-2.5.0/pkg/policy/testdata/malformed/vuln000066400000000000000000000015351477503325500215650ustar00rootroot00000000000000{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3Z1bG4vdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicmVnaXN0cnkubG9jYWw6NTAwMC9rbmF0aXZlL2RlbW8iLCJkaWdlc3QiOnsic2hhMjU2IjoiM2MxOWFhOTgwYTljNTcwOWEyYzk2YzJkMDc3OWZlYmY2ZTVlNDUzYjkyYjE3MmNlODRjYjg1ZmRhZjY5NTM3MyJ9fV0sInByZWRpY2F0ZSI6eyJpbnZvY2F0aW9uIjp7InBhcmFtZXRlcnMiOm51bGwsInVyaSI6IiIsImV2ZW50X2lkIjoiIiwiYnVpbGRlci5pZCI6IiJ9LCJzY2FubmVyIjp7InVyaSI6IiIsInZlcnNpb24iOiIiLCJkYiI6eyJ1cmkiOiIiLCJ2ZXJzaW9uIjoiIn0sInJlc3VsdCI6bnVsbH0sIm1ldGFkYXRhIjp7InNjYW5TdGFydGVkT24iOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiIsInNjYW5GaW5pc2hlZE9uIjoiMDAwMS0wMS0wMVQwMDowMDowMFoifX19","signatures":[{"keyid":"","sig":"MEUCIHE9QkUy+d6uFwae0LSH2Fgy99na3jQvaYMU6qj5dzbFAiEA0uKmqGY1ZHoQZsd0BR4Ug0c8d+sHT0hPcxA61o4DKlM="}]} cosign-2.5.0/pkg/policy/testdata/valid/000077500000000000000000000000001477503325500200035ustar00rootroot00000000000000cosign-2.5.0/pkg/policy/testdata/valid/custom000066400000000000000000000011751477503325500212440ustar00rootroot00000000000000{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2Nvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicmVnaXN0cnkubG9jYWw6NTAwMC9rbmF0aXZlL2RlbW8iLCJkaWdlc3QiOnsic2hhMjU2IjoiNmM2ZmQ2YTQxMTVjNmU5OThmZjM1N2NkOTE0NjgwOTMxYmI5YTZjMWE3Y2Q1ZjVjYjJmNWUxYzA5MzJhYjZlZCJ9fV0sInByZWRpY2F0ZSI6eyJEYXRhIjoiZm9vYmFyIHRlc3QgYXR0ZXN0YXRpb24iLCJUaW1lc3RhbXAiOiIyMDIyLTA0LTA3VDE5OjIyOjI1WiJ9fQ==","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]} cosign-2.5.0/pkg/policy/testdata/valid/vuln000066400000000000000000000015511477503325500207140ustar00rootroot00000000000000{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2Nvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdnVsbi92MSIsInN1YmplY3QiOlt7Im5hbWUiOiJyZWdpc3RyeS5sb2NhbDo1MDAwL2tuYXRpdmUvZGVtbyIsImRpZ2VzdCI6eyJzaGEyNTYiOiIzYzE5YWE5ODBhOWM1NzA5YTJjOTZjMmQwNzc5ZmViZjZlNWU0NTNiOTJiMTcyY2U4NGNiODVmZGFmNjk1MzczIn19XSwicHJlZGljYXRlIjp7Imludm9jYXRpb24iOnsicGFyYW1ldGVycyI6bnVsbCwidXJpIjoiIiwiZXZlbnRfaWQiOiIiLCJidWlsZGVyLmlkIjoiIn0sInNjYW5uZXIiOnsidXJpIjoiIiwidmVyc2lvbiI6IiIsImRiIjp7InVyaSI6IiIsInZlcnNpb24iOiIifSwicmVzdWx0IjpudWxsfSwibWV0YWRhdGEiOnsic2NhblN0YXJ0ZWRPbiI6IjAwMDEtMDEtMDFUMDA6MDA6MDBaIiwic2NhbkZpbmlzaGVkT24iOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiJ9fX0=","signatures":[{"keyid":"","sig":"MEUCIHE9QkUy+d6uFwae0LSH2Fgy99na3jQvaYMU6qj5dzbFAiEA0uKmqGY1ZHoQZsd0BR4Ug0c8d+sHT0hPcxA61o4DKlM="}]} cosign-2.5.0/pkg/providers/000077500000000000000000000000001477503325500156115ustar00rootroot00000000000000cosign-2.5.0/pkg/providers/all/000077500000000000000000000000001477503325500163615ustar00rootroot00000000000000cosign-2.5.0/pkg/providers/all/all.go000066400000000000000000000027061477503325500174650ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package all import ( "github.com/sigstore/cosign/v2/pkg/providers" // Link in all of the providers. // Link the GitHub one first, since we might be running in a GitHub self-hosted // runner running in one of the other environments, and we should prefer GitHub // credentials if we can find them. _ "github.com/sigstore/cosign/v2/pkg/providers/github" // Link in the rest of the providers. _ "github.com/sigstore/cosign/v2/pkg/providers/buildkite" _ "github.com/sigstore/cosign/v2/pkg/providers/envvar" _ "github.com/sigstore/cosign/v2/pkg/providers/filesystem" _ "github.com/sigstore/cosign/v2/pkg/providers/google" _ "github.com/sigstore/cosign/v2/pkg/providers/spiffe" ) // Alias these methods, so that folks can import this to get all providers. var ( Enabled = providers.Enabled Provide = providers.Provide ProvideFrom = providers.ProvideFrom ) cosign-2.5.0/pkg/providers/buildkite/000077500000000000000000000000001477503325500175655ustar00rootroot00000000000000cosign-2.5.0/pkg/providers/buildkite/buildkite.go000066400000000000000000000042631477503325500220750ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package buildkite import ( "context" "fmt" "net/http" "os" "github.com/buildkite/agent/v3/api" "github.com/buildkite/agent/v3/logger" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/cosign/v2/pkg/providers" ) func init() { providers.Register("buildkite-agent", &buildkiteAgent{}) } type buildkiteAgent struct{} var _ providers.Interface = (*buildkiteAgent)(nil) // Enabled implements providers.Interface func (ba *buildkiteAgent) Enabled(_ context.Context) bool { return env.Getenv(env.VariableBuildkiteAgentAccessToken) != "" } // Provide implements providers.Interface func (ba *buildkiteAgent) Provide(ctx context.Context, audience string) (string, error) { agentToken := env.Getenv(env.VariableBuildkiteAgentAccessToken) endpoint := env.Getenv(env.VariableBuildkiteAgentEndpoint) if endpoint == "" { endpoint = "https://agent.buildkite.com/v3" } jobID := env.Getenv(env.VariableBuildkiteJobID) logLevel := env.Getenv(env.VariableBuildkiteAgentLogLevel) if logLevel == "" { logLevel = "notice" } l := logger.NewConsoleLogger(logger.NewTextPrinter(os.Stderr), os.Exit) level, err := logger.LevelFromString(logLevel) if err != nil { return "", err } l.SetLevel(level) client := api.NewClient(l, api.Config{Token: agentToken, Endpoint: endpoint}) token, response, err := client.OIDCToken(ctx, &api.OIDCTokenRequest{Audience: audience, Job: jobID}) if err != nil { return "", err } if response != nil && response.StatusCode != http.StatusOK { return "", fmt.Errorf("buildkite agent request failed with status: %s", response.Status) } return token.Token, nil } cosign-2.5.0/pkg/providers/buildkite/doc.go000066400000000000000000000011511477503325500206570ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package buildkite cosign-2.5.0/pkg/providers/doc.go000066400000000000000000000014101477503325500167010ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package providers defines the APIs for providers to detect their relevance // and register themselves to furnish OIDC tokens within a given environment. package providers cosign-2.5.0/pkg/providers/envvar/000077500000000000000000000000001477503325500171125ustar00rootroot00000000000000cosign-2.5.0/pkg/providers/envvar/env.go000066400000000000000000000022401477503325500202270ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package envvar import ( "context" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/cosign/v2/pkg/providers" ) func init() { providers.Register("envvar", &envvar{}) } type envvar struct{} var _ providers.Interface = (*envvar)(nil) // Enabled implements providers.Interface func (p *envvar) Enabled(context.Context) bool { _, ok := env.LookupEnv(env.VariableSigstoreIDToken) return ok } // Provide implements providers.Interface func (p *envvar) Provide(context.Context, string) (string, error) { return env.Getenv(env.VariableSigstoreIDToken), nil } cosign-2.5.0/pkg/providers/envvar/env_test.go000066400000000000000000000027101477503325500212700ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package envvar import ( "context" "fmt" "testing" "github.com/sigstore/cosign/v2/pkg/cosign/env" ) func TestEnvVar(t *testing.T) { ctx := context.Background() token := "tacocat" for _, tc := range []struct { envmap map[string]string want bool }{ { envmap: map[string]string{ env.VariableSigstoreIDToken.String(): token, }, want: true, }, { want: false, }, } { t.Run(fmt.Sprint(tc.want), func(t *testing.T) { for k, v := range tc.envmap { t.Setenv(k, v) } e := &envvar{} if enabled := e.Enabled(ctx); enabled != tc.want { t.Errorf("Enabled: want %t, got %t", tc.want, enabled) } got, err := e.Provide(ctx, "") if err != nil { t.Fatalf("Provide: %v", err) } want := "" if tc.want { want = token } if got != want { t.Fatalf("Provide: want %s, got %s", want, got) } }) } } cosign-2.5.0/pkg/providers/filesystem/000077500000000000000000000000001477503325500177755ustar00rootroot00000000000000cosign-2.5.0/pkg/providers/filesystem/doc.go000066400000000000000000000016501477503325500210730ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package filesystem defines an implementation of the providers.Interface // that reads identity tokens from a well-known filesystem location. // This is intended for use with Kubernetes Service Account Projected Volumes, // but nothing is stopping other systems from placing identity tokens in // the same place. package filesystem cosign-2.5.0/pkg/providers/filesystem/filesystem.go000066400000000000000000000027211477503325500225120ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package filesystem import ( "context" "os" "github.com/sigstore/cosign/v2/pkg/providers" ) func init() { providers.Register("filesystem", &filesystem{}) } type filesystem struct{} var _ providers.Interface = (*filesystem)(nil) const ( // FilesystemTokenPath is the path to where we read an OIDC // token from the filesystem. // nolint FilesystemTokenPath = "/var/run/sigstore/cosign/oidc-token" ) // Enabled implements providers.Interface func (ga *filesystem) Enabled(_ context.Context) bool { // If we can stat the file without error then this is enabled. _, err := os.Stat(FilesystemTokenPath) return err == nil } // Provide implements providers.Interface func (ga *filesystem) Provide(ctx context.Context, audience string) (string, error) { //nolint: revive b, err := os.ReadFile(FilesystemTokenPath) if err != nil { return "", err } return string(b), nil } cosign-2.5.0/pkg/providers/github/000077500000000000000000000000001477503325500170735ustar00rootroot00000000000000cosign-2.5.0/pkg/providers/github/doc.go000066400000000000000000000012671477503325500201750ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package github defines a github implementation of the providers.Interface. package github cosign-2.5.0/pkg/providers/github/github.go000066400000000000000000000054671477503325500207200ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package github import ( "context" "encoding/json" "fmt" "net/http" "os" "strings" "time" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/cosign/v2/pkg/providers" ) const ( // Deprecated: use `env.VariableGitHubRequestToken` instead RequestTokenEnvKey = env.VariableGitHubRequestToken // Deprecated: use `env.VariableGitHubRequestURL` instead RequestURLEnvKey = env.VariableGitHubRequestURL ) func init() { providers.Register("github-actions", &githubActions{}) } type githubActions struct{} var _ providers.Interface = (*githubActions)(nil) // Enabled implements providers.Interface func (ga *githubActions) Enabled(_ context.Context) bool { if env.Getenv(env.VariableGitHubRequestToken) == "" { return false } if env.Getenv(env.VariableGitHubRequestURL) == "" { return false } return true } // Provide implements providers.Interface func (ga *githubActions) Provide(ctx context.Context, audience string) (string, error) { url := env.Getenv(env.VariableGitHubRequestURL) + "&audience=" + audience req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return "", err } // May be replaced by a different client if we hit HTTP_1_1_REQUIRED. client := http.DefaultClient // Retry up to 3 times. for i := 0; ; i++ { req.Header.Add("Authorization", "bearer "+env.Getenv(env.VariableGitHubRequestToken)) resp, err := client.Do(req) if err != nil { if i == 2 { return "", err } // This error isn't exposed by net/http, and retrying this with the // DefaultClient will fail because it will just use HTTP2 again. // I don't know why go doesn't do this for us. if strings.Contains(err.Error(), "HTTP_1_1_REQUIRED") { http1transport := http.DefaultTransport.(*http.Transport).Clone() http1transport.ForceAttemptHTTP2 = false client = &http.Client{ Transport: http1transport, } } fmt.Fprintf(os.Stderr, "error fetching GitHub OIDC token (will retry): %v\n", err) time.Sleep(time.Second) continue } defer resp.Body.Close() var payload struct { Value string `json:"value"` } decoder := json.NewDecoder(resp.Body) if err := decoder.Decode(&payload); err != nil { return "", err } return payload.Value, nil } } cosign-2.5.0/pkg/providers/google/000077500000000000000000000000001477503325500170655ustar00rootroot00000000000000cosign-2.5.0/pkg/providers/google/doc.go000066400000000000000000000012671477503325500201670ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package google defines a google implementation of the providers.Interface. package google cosign-2.5.0/pkg/providers/google/google.go000066400000000000000000000056601477503325500206770ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package google import ( "context" "os" "strings" "google.golang.org/api/idtoken" "google.golang.org/api/impersonate" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/cosign/v2/pkg/providers" ) func init() { providers.Register("google-workload-identity", &googleWorkloadIdentity{}) providers.Register("google-impersonate", &googleImpersonate{}) } type googleWorkloadIdentity struct{} var _ providers.Interface = (*googleWorkloadIdentity)(nil) // gceProductNameFile is the product file path that contains the cloud service name. // This is a variable instead of a const to enable testing. var gceProductNameFile = "/sys/class/dmi/id/product_name" // Enabled implements providers.Interface // This is based on k8s.io/kubernetes/pkg/credentialprovider/gcp func (gwi *googleWorkloadIdentity) Enabled(ctx context.Context) bool { data, err := os.ReadFile(gceProductNameFile) if err != nil { return false } name := strings.TrimSpace(string(data)) if name == "Google" || name == "Google Compute Engine" { // Just because we're on Google, does not mean workload identity is available. // TODO(mattmoor): do something better than this. _, err := gwi.Provide(ctx, "garbage") return err == nil } return false } // Provide implements providers.Interface func (gwi *googleWorkloadIdentity) Provide(ctx context.Context, audience string) (string, error) { ts, err := idtoken.NewTokenSource(ctx, audience) if err != nil { return "", err } tok, err := ts.Token() if err != nil { return "", err } return tok.AccessToken, nil } type googleImpersonate struct{} var _ providers.Interface = (*googleImpersonate)(nil) // Enabled implements providers.Interface func (gi *googleImpersonate) Enabled(_ context.Context) bool { // The "impersonate" method requires a target service account to impersonate. return env.Getenv(env.VariableGoogleServiceAccountName) != "" } // Provide implements providers.Interface func (gi *googleImpersonate) Provide(ctx context.Context, audience string) (string, error) { target := env.Getenv(env.VariableGoogleServiceAccountName) ts, err := impersonate.IDTokenSource(ctx, impersonate.IDTokenConfig{ Audience: audience, TargetPrincipal: target, IncludeEmail: true, }) if err != nil { return "", err } tok, err := ts.Token() if err != nil { return "", err } return tok.AccessToken, nil } cosign-2.5.0/pkg/providers/interface.go000066400000000000000000000053431477503325500201050ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package providers import ( "context" "errors" "fmt" "sync" ) var ( m sync.Mutex providers []ProviderEntry ) type ProviderEntry struct { Name string Provider Interface } // Interface is what providers need to implement to participate in furnishing OIDC tokens. type Interface interface { // Enabled returns true if the provider is enabled. Enabled(ctx context.Context) bool // Provide returns an OIDC token scoped to the provided audience. Provide(ctx context.Context, audience string) (string, error) } // Register is used by providers to participate in furnishing OIDC tokens. func Register(name string, p Interface) { m.Lock() defer m.Unlock() for _, pe := range providers { if pe.Name == name { panic(fmt.Sprintf("duplicate provider for name %q, %T and %T", name, pe.Provider, p)) } } providers = append(providers, ProviderEntry{Name: name, Provider: p}) } // Enabled checks whether any of the registered providers are enabled in this execution context. func Enabled(ctx context.Context) bool { m.Lock() defer m.Unlock() for _, pe := range providers { if pe.Provider.Enabled(ctx) { return true } } return false } // Provide fetches an OIDC token from one of the active providers. func Provide(ctx context.Context, audience string) (string, error) { m.Lock() defer m.Unlock() var id string var err error for _, pe := range providers { p := pe.Provider if !p.Enabled(ctx) { continue } id, err = p.Provide(ctx, audience) if err == nil { return id, nil } } // return the last id/err combo, unless there wasn't an error in // which case provider.Enabled() wasn't checked. if err == nil { err = errors.New("no providers are enabled, check providers.Enabled() before providers.Provide()") } return id, err } // ProvideFrom fetches the specified provider func ProvideFrom(_ context.Context, provider string) (Interface, error) { m.Lock() defer m.Unlock() for _, p := range providers { if p.Name == provider { return p.Provider, nil } } return nil, fmt.Errorf("%s is not a valid provider", provider) } func Providers() []ProviderEntry { m.Lock() defer m.Unlock() return providers } cosign-2.5.0/pkg/providers/spiffe/000077500000000000000000000000001477503325500170655ustar00rootroot00000000000000cosign-2.5.0/pkg/providers/spiffe/doc.go000066400000000000000000000012751477503325500201660ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package spiffe defines a SPIFFE/SPIRE implementation of the providers.Interface. package spiffe cosign-2.5.0/pkg/providers/spiffe/spiffe.go000066400000000000000000000042341477503325500206730ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package spiffe import ( "context" "os" "github.com/spiffe/go-spiffe/v2/svid/jwtsvid" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/cosign/v2/pkg/providers" "github.com/spiffe/go-spiffe/v2/workloadapi" ) func init() { providers.Register("spiffe", &spiffe{}) } type spiffe struct{} var _ providers.Interface = (*spiffe)(nil) const ( // defaultSocketPath is the path to where we read an OIDC // token from the spiffe by default. // nolint defaultSocketPath = "/tmp/spire-agent/public/api.sock" ) // getSocketPath gets which Spiffe socket to use. Either default // or the one specified by environment variable. func getSocketPath() string { if env := env.Getenv(env.VariableSPIFFEEndpointSocket); env != "" { return env } return defaultSocketPath } // Enabled implements providers.Interface func (ga *spiffe) Enabled(_ context.Context) bool { // If we can stat the file without error then this is enabled. _, err := os.Stat(getSocketPath()) return err == nil } // Provide implements providers.Interface func (ga *spiffe) Provide(ctx context.Context, audience string) (string, error) { // Creates a new Workload API client, connecting to provided socket path // Environment variable `SPIFFE_ENDPOINT_SOCKET` is used if given and // defaultSocketPath if not. client, err := workloadapi.New(ctx, workloadapi.WithAddr("unix://"+getSocketPath())) if err != nil { return "", err } defer client.Close() svid, err := client.FetchJWTSVID(ctx, jwtsvid.Params{ Audience: audience, }) if err != nil { return "", err } return svid.Marshal(), nil } cosign-2.5.0/pkg/providers/spiffe/spiffe_test.go000066400000000000000000000017401477503325500217310ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package spiffe import ( "os" "testing" ) const nonDefault = "/run/sockets/spire" func TestGetSocketPath(t *testing.T) { if got := getSocketPath(); got != defaultSocketPath { t.Errorf("Expected %s got %s", defaultSocketPath, got) } os.Setenv("SPIFFE_ENDPOINT_SOCKET", nonDefault) if got := getSocketPath(); got != nonDefault { t.Errorf("Expected %s got %s", nonDefault, got) } } cosign-2.5.0/pkg/signature/000077500000000000000000000000001477503325500155755ustar00rootroot00000000000000cosign-2.5.0/pkg/signature/annotations.go000066400000000000000000000023061477503325500204620ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signature import ( _ "crypto/sha256" // for `crypto.SHA256` "fmt" "strings" ) type AnnotationsMap struct { Annotations map[string]interface{} } func (a *AnnotationsMap) Set(s string) error { if a.Annotations == nil { a.Annotations = map[string]interface{}{} } kvp := strings.SplitN(s, "=", 2) if len(kvp) != 2 { return fmt.Errorf("invalid flag: %s, expected key=value", s) } a.Annotations[kvp[0]] = kvp[1] return nil } func (a *AnnotationsMap) String() string { s := []string{} for k, v := range a.Annotations { s = append(s, fmt.Sprintf("%s=%s", k, v)) } return strings.Join(s, ",") } cosign-2.5.0/pkg/signature/keys.go000066400000000000000000000156751477503325500171150ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signature import ( "context" "crypto" "errors" "fmt" "strings" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/git" "github.com/sigstore/cosign/v2/pkg/cosign/git/gitlab" "github.com/sigstore/cosign/v2/pkg/cosign/kubernetes" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" ) // LoadPublicKey is a wrapper for VerifierForKeyRef, hardcoding SHA256 as the hash algorithm func LoadPublicKey(ctx context.Context, keyRef string) (verifier signature.Verifier, err error) { return VerifierForKeyRef(ctx, keyRef, crypto.SHA256) } // VerifierForKeyRef parses the given keyRef, loads the key and returns an appropriate // verifier using the provided hash algorithm func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (verifier signature.Verifier, err error) { // The key could be plaintext, in a file, at a URL, or in KMS. var perr *kms.ProviderNotFoundError kmsKey, err := kms.Get(ctx, keyRef, hashAlgorithm) switch { case err == nil: // KMS specified return kmsKey, nil case errors.As(err, &perr): // We can ignore ProviderNotFoundError; that just means the keyRef // didn't match any of the KMS schemes. default: // But other errors indicate something more insidious; pass those // through. return nil, err } raw, err := blob.LoadFileOrURL(keyRef) if err != nil { return nil, err } // PEM encoded file. pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(raw) if err != nil { return nil, fmt.Errorf("pem to public key: %w", err) } return signature.LoadVerifier(pubKey, hashAlgorithm) } func loadKey(keyPath string, pf cosign.PassFunc) (signature.SignerVerifier, error) { kb, err := blob.LoadFileOrURL(keyPath) if err != nil { return nil, err } pass := []byte{} if pf != nil { pass, err = pf(false) if err != nil { return nil, err } } return cosign.LoadPrivateKey(kb, pass) } // LoadPublicKeyRaw loads a verifier from a PEM-encoded public key func LoadPublicKeyRaw(raw []byte, hashAlgorithm crypto.Hash) (signature.Verifier, error) { pub, err := cryptoutils.UnmarshalPEMToPublicKey(raw) if err != nil { return nil, err } return signature.LoadVerifier(pub, hashAlgorithm) } func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) { return SignerVerifierFromKeyRef(ctx, keyRef, pf) } func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.SignerVerifier, error) { switch { case strings.HasPrefix(keyRef, pkcs11key.ReferenceScheme): pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig() err := pkcs11UriConfig.Parse(keyRef) if err != nil { return nil, fmt.Errorf("parsing pkcs11 uri: %w", err) } // Since we'll be signing, we need to set askForPinIsNeeded to true // because we need access to the private key. sk, err := pkcs11key.GetKeyWithURIConfig(pkcs11UriConfig, true) if err != nil { return nil, fmt.Errorf("opening pkcs11 token key: %w", err) } sv, err := sk.SignerVerifier() if err != nil { return nil, fmt.Errorf("initializing pkcs11 token signer verifier: %w", err) } return sv, nil case strings.HasPrefix(keyRef, kubernetes.KeyReference): s, err := kubernetes.GetKeyPairSecret(ctx, keyRef) if err != nil { return nil, err } if len(s.Data) > 0 { return cosign.LoadPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"]) } case strings.HasPrefix(keyRef, gitlab.ReferenceScheme): split := strings.Split(keyRef, "://") if len(split) < 2 { return nil, errors.New("could not parse scheme, use :// format") } provider, targetRef := split[0], split[1] pk, err := git.GetProvider(provider).GetSecret(ctx, targetRef, "COSIGN_PRIVATE_KEY") if err != nil { return nil, err } pass, err := git.GetProvider(provider).GetSecret(ctx, targetRef, "COSIGN_PASSWORD") if err != nil { return nil, err } return cosign.LoadPrivateKey([]byte(pk), []byte(pass)) } if strings.Contains(keyRef, "://") { sv, err := kms.Get(ctx, keyRef, crypto.SHA256) if err == nil { return sv, nil } var e *kms.ProviderNotFoundError if !errors.As(err, &e) { return nil, fmt.Errorf("kms get: %w", err) } // ProviderNotFoundError is okay; loadKey handles other URL schemes } return loadKey(keyRef, pf) } func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier, error) { return PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, crypto.SHA256) } func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (signature.Verifier, error) { if strings.HasPrefix(keyRef, kubernetes.KeyReference) { s, err := kubernetes.GetKeyPairSecret(ctx, keyRef) if err != nil { return nil, err } if len(s.Data) > 0 { return LoadPublicKeyRaw(s.Data["cosign.pub"], hashAlgorithm) } } if strings.HasPrefix(keyRef, pkcs11key.ReferenceScheme) { pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig() err := pkcs11UriConfig.Parse(keyRef) if err != nil { return nil, fmt.Errorf("parsing pkcs11 uri): %w", err) } // Since we'll be verifying a signature, we do not need to set askForPinIsNeeded to true // because we only need access to the public key. sk, err := pkcs11key.GetKeyWithURIConfig(pkcs11UriConfig, false) if err != nil { return nil, fmt.Errorf("opening pkcs11 token key: %w", err) } v, err := sk.Verifier() if err != nil { return nil, fmt.Errorf("initializing pkcs11 token verifier: %w", err) } return v, nil } else if strings.HasPrefix(keyRef, gitlab.ReferenceScheme) { split := strings.Split(keyRef, "://") if len(split) < 2 { return nil, errors.New("could not parse scheme, use :// format") } provider, targetRef := split[0], split[1] pubKey, err := git.GetProvider(provider).GetSecret(ctx, targetRef, "COSIGN_PUBLIC_KEY") if err != nil { return nil, err } if len(pubKey) > 0 { return LoadPublicKeyRaw([]byte(pubKey), hashAlgorithm) } } return VerifierForKeyRef(ctx, keyRef, hashAlgorithm) } func PublicKeyPem(key signature.PublicKeyProvider, pkOpts ...signature.PublicKeyOption) ([]byte, error) { pub, err := key.PublicKey(pkOpts...) if err != nil { return nil, err } return cryptoutils.MarshalPublicKeyToPEM(pub) } cosign-2.5.0/pkg/signature/keys_test.go000066400000000000000000000112071477503325500201370ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package signature import ( "context" "crypto" "errors" "os" "testing" "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" sigsignature "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" ) func generateKeyFile(t *testing.T, tmpDir string, pf cosign.PassFunc) (privFile, pubFile string) { t.Helper() tmpPrivFile, err := os.CreateTemp(tmpDir, "cosign_test_*.key") if err != nil { t.Fatalf("failed to create temp key file: %v", err) } defer tmpPrivFile.Close() tmpPubFile, err := os.CreateTemp(tmpDir, "cosign_test_*.pub") if err != nil { t.Fatalf("failed to create temp pub file: %v", err) } defer tmpPubFile.Close() // Generate a valid keypair. keys, err := cosign.GenerateKeyPair(pf) if err != nil { t.Fatalf("failed to generate keypair: %v", err) } if _, err := tmpPrivFile.Write(keys.PrivateBytes); err != nil { t.Fatalf("failed to write key file: %v", err) } if _, err := tmpPubFile.Write(keys.PublicBytes); err != nil { t.Fatalf("failed to write pub file: %v", err) } return tmpPrivFile.Name(), tmpPubFile.Name() } func TestSignerFromPrivateKeyFileRef(t *testing.T) { t.Parallel() tmpDir := t.TempDir() ctx := context.Background() testCases := []struct { desc string writePw cosign.PassFunc readPw cosign.PassFunc expectErr bool }{{ desc: "good password", writePw: pass("hello"), readPw: pass("hello"), }, { desc: "bad password", writePw: pass("hello"), readPw: pass("something else"), expectErr: true, }} for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { tc := tc t.Parallel() testFile, _ := generateKeyFile(t, tmpDir, tc.writePw) signer, err := SignerFromKeyRef(ctx, testFile, tc.readPw) if err != nil { if tc.expectErr { // Task failed successfully return } t.Fatalf("SignerFromKeyRef returned error: %v", err) } if tc.expectErr { t.Fatalf("SignerFromKeyRef should have returned error, got: %v", signer) } }) } } func TestPublicKeyFromFileRef(t *testing.T) { t.Parallel() tmpDir := t.TempDir() ctx := context.Background() _, testFile := generateKeyFile(t, tmpDir, pass("whatever")) if _, err := PublicKeyFromKeyRef(ctx, testFile); err != nil { t.Fatalf("PublicKeyFromKeyRef returned error: %v", err) } } func TestPublicKeyFromEnvVar(t *testing.T) { keys, err := cosign.GenerateKeyPair(pass("whatever")) if err != nil { t.Fatalf("failed to generate keypair: %v", err) } ctx := context.Background() os.Setenv("MY_ENV_VAR", string(keys.PublicBytes)) defer os.Unsetenv("MY_ENV_VAR") if _, err := PublicKeyFromKeyRef(ctx, "env://MY_ENV_VAR"); err != nil { t.Fatalf("PublicKeyFromKeyRef returned error: %v", err) } } func TestSignerVerifierFromEnvVar(t *testing.T) { passFunc := pass("whatever") keys, err := cosign.GenerateKeyPair(passFunc) if err != nil { t.Fatalf("failed to generate keypair: %v", err) } ctx := context.Background() os.Setenv("MY_ENV_VAR", string(keys.PrivateBytes)) defer os.Unsetenv("MY_ENV_VAR") if _, err := SignerVerifierFromKeyRef(ctx, "env://MY_ENV_VAR", passFunc); err != nil { t.Fatalf("SignerVerifierFromKeyRef returned error: %v", err) } } func TestVerifierForKeyRefError(t *testing.T) { kms.AddProvider("errorkms://", func(_ context.Context, _ string, _ crypto.Hash, _ ...sigsignature.RPCOption) (kms.SignerVerifier, error) { return nil, errors.New("bad") }) var uerr *blob.UnrecognizedSchemeError ctx := context.Background() _, err := PublicKeyFromKeyRef(ctx, "errorkms://bad") if err == nil { t.Fatalf("PublicKeyFromKeyRef didn't return any error") } else if errors.As(err, &uerr) { t.Fatalf("PublicKeyFromKeyRef returned UnrecognizedSchemeError: %v", err) } _, err = PublicKeyFromKeyRef(ctx, "badscheme://bad") if err == nil { t.Fatalf("PublicKeyFromKeyRef didn't return any error") } else if !errors.As(err, &uerr) { t.Fatalf("PublicKeyFromKeyRef didn't return UnrecognizedSchemeError: %v", err) } } func pass(s string) cosign.PassFunc { return func(_ bool) ([]byte, error) { return []byte(s), nil } } cosign-2.5.0/pkg/types/000077500000000000000000000000001477503325500147405ustar00rootroot00000000000000cosign-2.5.0/pkg/types/media.go000066400000000000000000000022331477503325500163460ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types const ( JSONInputFormat = "json" XMLInputFormat = "xml" TextInputFormat = "text" ) const ( CycloneDXXMLMediaType = "application/vnd.cyclonedx+xml" CycloneDXJSONMediaType = "application/vnd.cyclonedx+json" SyftMediaType = "application/vnd.syft+json" SimpleSigningMediaType = "application/vnd.dev.cosign.simplesigning.v1+json" SPDXMediaType = "text/spdx" SPDXJSONMediaType = "text/spdx+json" WasmLayerMediaType = "application/vnd.wasm.content.layer.v1+wasm" WasmConfigMediaType = "application/vnd.wasm.config.v1+json" ) cosign-2.5.0/pkg/types/payload.go000066400000000000000000000013441477503325500167220ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package types const ( DssePayloadType = "application/vnd.dsse.envelope.v1+json" IntotoPayloadType = "application/vnd.in-toto+json" ) cosign-2.5.0/release/000077500000000000000000000000001477503325500144335ustar00rootroot00000000000000cosign-2.5.0/release/README.md000066400000000000000000000051141477503325500157130ustar00rootroot00000000000000# Release This directory contain the files and scripts to run a cosign release. # Cutting a Cosign Release 1. Release notes: Create a PR to update and review release notes in CHANGELOG.md. Check merged pull requests since the last release and make sure enhancements, bug fixes, and authors are reflected in the notes. You can get a list of pull requests since the last release by substituting in the date of the last release and running: ``` git log --pretty="* %s" --after="YYYY-MM-DD" ``` and a list of authors by running: ``` git log --pretty="* %an" --after="YYYY-MM-DD" | sort -u ``` 2. Open a Pull Request to update CHANGELOG.md 3. Tag the repository **WARNING**: Tags should not be updated to a new ref or deleted/recreated after creation. Go provides a [checksum database](https://sum.golang.org/) that persists an immutable mapping between version and ref, and updating the tag will break clients that have already downloaded the release. ```shell $ export RELEASE_TAG= $ git tag -s ${RELEASE_TAG} -m "${RELEASE_TAG}" $ git push upstream ${RELEASE_TAG} ``` Note that `upstream` should be the upstream `sigstore/cosign` repository. You may have to change this if you've configured remotes. 4. Then go to the `Actions` tab and click on the [Cut Release workflow](https://github.com/sigstore/cosign/actions/workflows/cut-release.yml). Note you need to be in [this list](https://github.com/sigstore/sigstore/blob/main/.github/workflows/reusable-release.yml#L45) to trigger this workflow. Click to run a workflow and insert the following parameters: - `Release tag`: the tag that use pushed for the release - `Key ring for cosign key`: the value is `release-cosign` - `Key name for cosign key`: the value is `cosign` That will trigger a CloudBuild job and will run the release using `goreleaser`, which will publish images to `gcr.io` and `ghcr.io`, and the binaries will be available in the GitHub release. If you have permissions to access the project, you can follow the CloudBuild job in the `projectsigstore`(https://console.cloud.google.com/cloud-build/builds?project=projectsigstore) GCP Project. As the last step of the CloudBuild job, `goreleaser` will create a `draft release` in GitHub. 5. Navigate to the `Draft Release` in the Github repository. Click the `Publish Release` button to make the Release available. You might want/need to add any extra notes/breaking changes notices, upgrade paths. 6. Post on the `#general` and `#cosign` Slack channels. 7. If it's a significant release, send an announcement email to sigstore-dev@googlegroups.com mailing list. cosign-2.5.0/release/cloudbuild.yaml000066400000000000000000000075341477503325500174560ustar00rootroot00000000000000# # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. timeout: 3600s steps: - name: gcr.io/cloud-builders/git dir: "go/src/sigstore" args: - "clone" - "https://github.com/${_TOOL_ORG}/${_TOOL_REPO}" - name: gcr.io/cloud-builders/git entrypoint: "bash" dir: "go/src/sigstore/cosign" args: - '-c' - | git fetch echo "Checking out ${_GIT_TAG}" git checkout ${_GIT_TAG} - name: 'ghcr.io/sigstore/cosign/cosign:v2.4.3-dev@sha256:ed76c008e733aa64d257f754a02eb07b251525ea8dc08f40974baec317dea8c9' dir: "go/src/sigstore/cosign" env: - TUF_ROOT=/tmp args: - 'verify' - 'ghcr.io/gythialy/golang-cross:v1.24.1-0@sha256:dcc12fa4a35c7cda162cfbaa3f612b4209d5e2e78093e7c3fd7168d258432706' - '--certificate-oidc-issuer' - "https://token.actions.githubusercontent.com" - '--certificate-identity' - "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.24.1-0" # maybe we can build our own image and use that to be more in a safe side - name: ghcr.io/gythialy/golang-cross:v1.24.1-0@sha256:dcc12fa4a35c7cda162cfbaa3f612b4209d5e2e78093e7c3fd7168d258432706 entrypoint: /bin/sh dir: "go/src/sigstore/cosign" env: - "GOPATH=/workspace/go" - "GOBIN=/workspace/bin" - PROJECT_ID=${PROJECT_ID} - KEY_LOCATION=${_KEY_LOCATION} - KEY_RING=${_KEY_RING} - KEY_NAME=${_KEY_NAME} - KEY_VERSION=${_KEY_VERSION} - GIT_TAG=${_GIT_TAG} - GOOGLE_SERVICE_ACCOUNT_NAME=keyless@${PROJECT_ID}.iam.gserviceaccount.com - COSIGN_YES=true - KO_PREFIX=gcr.io/${PROJECT_ID} secretEnv: - GITHUB_TOKEN args: - '-c' - | gcloud auth configure-docker \ && make release - name: ghcr.io/gythialy/golang-cross:v1.24.1-0@sha256:dcc12fa4a35c7cda162cfbaa3f612b4209d5e2e78093e7c3fd7168d258432706 entrypoint: 'bash' dir: "go/src/sigstore/cosign" env: - "GOPATH=/workspace/go" - "GOBIN=/workspace/bin" - PROJECT_ID=${PROJECT_ID} - KEY_LOCATION=${_KEY_LOCATION} - KEY_RING=${_KEY_RING} - KEY_NAME=${_KEY_NAME} - KEY_VERSION=${_KEY_VERSION} - GIT_TAG=${_GIT_TAG} - KO_PREFIX=gcr.io/${PROJECT_ID} - COSIGN_YES=true - GOOGLE_SERVICE_ACCOUNT_NAME=keyless@${PROJECT_ID}.iam.gserviceaccount.com - GITHUB_USER=${_GITHUB_USER} - LATEST_TAG=",latest" secretEnv: - GITHUB_TOKEN args: - '-c' - | echo $$GITHUB_TOKEN | docker login ghcr.io -u $$GITHUB_USER --password-stdin \ && make sign-release-images && make copy-signed-release-to-ghcr || true availableSecrets: secretManager: - versionName: projects/${PROJECT_NUMBER}/secrets/GITHUB_TOKEN/versions/latest env: GITHUB_TOKEN artifacts: objects: location: 'gs://${_STORAGE_LOCATION}/${_GIT_TAG}' paths: - "go/src/sigstore/cosign/dist/*" - "go/src/sigstore/cosign/release/release-cosign.pub" options: machineType: E2_HIGHCPU_32 tags: - cosign-release - ${_GIT_TAG} - ${_TOOL_ORG} - ${_TOOL_REPO} substitutions: _GIT_TAG: 'v1.23.45' _TOOL_ORG: 'honk' _TOOL_REPO: 'honk-repo' _STORAGE_LOCATION: 'honk' _KEY_RING: 'honk-ring' _KEY_NAME: 'honk-crypto' _KEY_VERSION: '1' _KEY_LOCATION: 'global' _GITHUB_USER: 'placeholder' cosign-2.5.0/release/ko-sign-release-images.sh000077500000000000000000000040411477503325500212210ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2022 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License""; # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail : "${GIT_HASH:?Environment variable empty or not defined.}" : "${GIT_VERSION:?Environment variable empty or not defined.}" : "${PROJECT_ID:?Environment variable empty or not defined.}" : "${KEY_LOCATION:?Environment variable empty or not defined.}" : "${KEY_RING:?Environment variable empty or not defined.}" : "${KEY_NAME:?Environment variable empty or not defined.}" : "${KEY_VERSION:?Environment variable empty or not defined.}" if [[ ! -f cosignImagerefs ]]; then echo "cosignImagerefs not found" exit 1 fi if [[ ! -f cosignDevImagerefs ]]; then echo "cosignDevImagerefs not found" exit 1 fi echo "Signing cosign images with GCP KMS Key..." cosign sign --yes --key "gcpkms://projects/$PROJECT_ID/locations/$KEY_LOCATION/keyRings/$KEY_RING/cryptoKeys/$KEY_NAME/versions/$KEY_VERSION" -a GIT_HASH="$GIT_HASH" -a GIT_VERSION="$GIT_VERSION" $(cat cosignImagerefs) echo "Signing images with Keyless..." cosign sign --yes -a GIT_HASH="$GIT_HASH" -a GIT_VERSION="$GIT_VERSION" $(cat cosignImagerefs) echo "Signing cosign images with GCP KMS Key..." cosign sign --yes --key "gcpkms://projects/$PROJECT_ID/locations/$KEY_LOCATION/keyRings/$KEY_RING/cryptoKeys/$KEY_NAME/versions/$KEY_VERSION" -a GIT_HASH="$GIT_HASH" -a GIT_VERSION="$GIT_VERSION" $(cat cosignDevImagerefs) echo "Signing images with Keyless..." cosign sign --yes -a GIT_HASH="$GIT_HASH" -a GIT_VERSION="$GIT_VERSION" $(cat cosignDevImagerefs) cosign-2.5.0/release/release-cosign.pub000066400000000000000000000002621477503325500200430ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhyQCx0E9wQWSFI9ULGwy3BuRklnt IqozONbbdbqz11hlRJy9c7SG+hdcFl9jE9uE/dwtuwU2MqU9T/cN0YkWww== -----END PUBLIC KEY----- cosign-2.5.0/release/release.mk000066400000000000000000000016121477503325500164040ustar00rootroot00000000000000################## # release section ################## # used when releasing together with GCP CloudBuild .PHONY: release release: LDFLAGS="$(LDFLAGS)" goreleaser release --parallelism 1 --clean --timeout 120m ###################### # sign section ###################### .PHONY: sign-release-images sign-release-images: ko GIT_HASH=$(GIT_HASH) GIT_VERSION=$(GIT_VERSION) \ ./release/ko-sign-release-images.sh # used when need to validate the goreleaser .PHONY: snapshot snapshot: LDFLAGS="$(LDFLAGS)" goreleaser release --skip=sign,publish --snapshot --clean --timeout 120m --parallelism 1 #################### # copy image to GHCR #################### .PHONY: copy-signed-release-to-ghcr copy-signed-release-to-ghcr: cosign copy $(KO_PREFIX)/cosign:$(GIT_VERSION) $(GHCR_PREFIX)/cosign:$(GIT_VERSION) cosign copy $(KO_PREFIX)/cosign:$(GIT_VERSION)-dev $(GHCR_PREFIX)/cosign:$(GIT_VERSION)-dev cosign-2.5.0/scripts/000077500000000000000000000000001477503325500145025ustar00rootroot00000000000000cosign-2.5.0/scripts/sign-images-ci.sh000077500000000000000000000021531477503325500176360ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2022 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License""; # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail : "${GIT_HASH:?Environment variable empty or not defined.}" : "${GITHUB_RUN_ID:?Environment variable empty or not defined.}" : "${GITHUB_RUN_ATTEMPT:?Environment variable empty or not defined.}" COSIGN_CLI=./cosign if [[ ! -f cosignImagerefs ]]; then echo "cosignImagerefs not found" exit 1 fi echo "Signing cosign images using Keyless..." $COSIGN_CLI sign -y -a sha="$GIT_HASH" -a run_id="$GITHUB_RUN_ID" -a run_attempt="$GITHUB_RUN_ATTEMPT" $(cat cosignImagerefs) cosign-2.5.0/specs/000077500000000000000000000000001477503325500141305ustar00rootroot00000000000000cosign-2.5.0/specs/ATTESTATION_SPEC.md000066400000000000000000000050061477503325500170640ustar00rootroot00000000000000# Cosign Attestation Specifications This document aims to describe how `cosign` attaches `Attestations` to container images. The goal is to specify the behavior well enough to promote other implementations and enable interoperability. Attestations attached with `cosign` should be retrievable in other tools, and vice-versa. This document focuses on the layout of attestations within an [OCI Image Manifest V1](https://github.com/opencontainers/image-spec/blob/master/manifest.md) object. This document assumes you are using the In-Toto [Attestation](https://github.com/in-toto/attestation) format, serialized as a `DSSE` envelope Other formats can be used, and the `mediaType` property should describe the format of a particular attestation, but implementations may not understand them. The DSSE envelope format is defined [here](https://github.com/secure-systems-lab/dsse/blob/master/envelope.md#dsse-envelope) and uses the `mediaType`: `application/vnd.dsse.envelope.v1+json`. Multiple Attestations may be "attached" to one image. Each Attestation may refer to the entire image, or to a specific part of that image. This is indicated via the `subject` field of the `Statement` inside the `Attestation`. Attestations attached to a container image are generally assumed to refer to that image in some way. ## Overall Layout An `Attestation` object is represented as an [OCI Image Manifest V1](https://github.com/opencontainers/image-spec/blob/master/manifest.md). Each individual `Attestation` is represented as a `layer`, using a standard `descriptor`. The `layers` list is ordered, but no order is assumed or important for the `Attestations`. Here is an example manifest containing one `Attestation`: ```json { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 233, "digest": "sha256:83bd5fb5b39f65f28e50a86d48fa79c07880befc292d92eebdc18531054b070c" }, "layers": [ { "mediaType": "application/vnd.dsse.envelope.v1+json", "size": 246, "digest": "sha256:ed3ad03d3b87843b5419d7dce9d50a3e0f45554b2ba93bf378611cae6b450cff", } ] } ``` ## Subject Verification `Attestations` MAY refer to multiple `subjects`. When verifying an attestation for a container image, implementations MUST verify the relationship between the `subject` field and the container image. Attestations MAY reference the entire container image or a portion of it. Implementations MUST support `Attestations` that reference the entire container image, other relationship types are optional. cosign-2.5.0/specs/BUNDLE_SPEC.md000066400000000000000000000240411477503325500162360ustar00rootroot00000000000000# Cosign Bundle Specification This document aims to describe how `cosign` attaches Sigstore attestation [bundles](https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto) to container images. The goal is to specify the behavior well enough to promote other implementations and enable interoperability. Attestations attached with `cosign` should be retrievable in other tools, and vice-versa. This document focuses on the layout of attestations within an [OCI Image Manifest V1.1](https://github.com/opencontainers/image-spec/blob/v1.1.0/manifest.md) object. This document makes no assumptions about the contents of the Sigstore bundle. Any attestation which can be represented as a Sigstore bundle (message signatures, DSSE-wrapped in-toto statements, etc) can be attached to a container image stored in an OCI registry. Multiple Attestations may be "attached" to one image. Attestations attached to a container image are generally assumed to refer to that image in some way. ## Storage The approach for storing Sigstore bundles in an OCI registry follows the [guidelines for artifact usage](https://github.com/opencontainers/image-spec/blob/main/manifest.md#guidelines-for-artifact-usage) in the OCI [image spec](https://github.com/opencontainers/image-spec/blob/main/README.md). ### Publishing First, the bundle itself is stored in its JSON-serialized form as a blob in the registry: ``` POST /v2/foo/blobs/uploads/?digest=cafed00d... Content-Type: application/octet-stream {"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", ...} ``` In this example “foo” is the name of the repository within the registry to which the artifact is being uploaded. The digest included as part of the POST is the hex-encoded SHA-256 digest of the raw bytes of the bundle itself. Once the blob has been created, the next step is to create a manifest that associates the bundle blob with the image it describes: ``` PUT /v2/foo/manifests/sha256:badf00d... Content-Type: application/vnd.oci.image.manifest.v1+json { "mediaType": "application/vnd.oci.image.manifest.v1+json", "schemaVersion": 2, "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json", "config": { "mediaType": "application/vnd.oci.empty.v1+json", "digest": "sha256:44136fa3...", "size": 2 }, "layers": [ { "digest": "sha256:cafed00d...", "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "size": 4971 } ], "subject": { "digest": "sha256:c00010ff...", "mediaType": "application/vnd.oci.image.index.v1+json" } } ``` The manifest must have an `artifactType` field which identifies the type of the artifact being referenced -- in this case, it's the Sigstore bundle media type. The `layers` collection will have a single entry that points to the bundle's blob by referencing its size, digest and media type. The `subject` field associates this artifact with some other artifact which already exists in this repository (in this case, an image with the digest `c00010ff`) Sigstore bundles don't require any additional configuration data, so the `config` field references the [empty descriptor](https://github.com/opencontainers/image-spec/blob/f5f87016de46439ccf91b5381cf76faaae2bc28f/manifest.md#guidance-for-an-empty-descriptor). At this point, any registry which supports the [referrers API](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers) will automatically associate this manifest with the listed subject and make it available in the referrers index for that subject. If the registry DOES NOT support the referrers API, a referrers list must be manually created/updated using the [referrers tag scheme](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#referrers-tag-schema). ``` PUT /v2/foo/manifests/sha256-c00010ff... Content-Type: application/vnd.oci.image.index.v1+json { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:badf00d..", "size": 779, "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json" } ] } ``` This index is uploaded with a tag that references the digest of the image to which all of the listed artifacts are associated. Each of the items in the `manifests` collection points to some other related artifact. ### Retrieval When a client wants to locate Sigstore bundles which may be associated with a given image, they would first make a request to [referrers API](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers) with the image's digest: ``` GET /v2/foo/referrers/sha256:c000100ff... ``` A `404 Not Found` response indicates that the registry does not support the referrers API and the referrers tag scheme should be used as a fallback: ``` GET /v2/foo/manifests/sha256-c000100ff... ``` A `404` here would indicate that there are no artifacts associated with the image. Assuming there are artifacts present, one of the two above calls will return an image index listing the artifacts which have been associated with the specified image: ``` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:badf00d..", "size": 779, "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json" } ] } ``` From this the client can identify any Sigstore bundles by looking at the `artifactType` field. Using the `digest` listed in the image index, the next step is to retrieve the manifest for the bundle: ``` GET /v2/foo/manifests/sha256:badf00d.. ``` ``` { "mediaType": "application/vnd.oci.image.manifest.v1+json", "schemaVersion": 2, "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json", "config": { "mediaType": "application/vnd.oci.empty.v1+json", "digest": "sha256:44136fa3...", "size": 2 }, "layers": [ { "digest": "sha256:cafed00d...", "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "size": 4971 } ], "subject": { "digest": "sha256:c00010ff...", "mediaType": "application/vnd.oci.image.index.v1+json" } } ``` The final step is to use the `digest` from the first of the `layers` to retrieve the bundle blob: ``` GET /v2/foo/blobs/uploads/?digest=cafed00d... ``` ``` { "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {...}, "messageSignature": {...} } ``` ## Annotations For any given image, there may be any number of attached attestation bundles. When there are multiple Sigstore bundles associated with an image it may be difficult to identify which artifact is which in the image index: ```json { "mediaType": "application/vnd.oci.image.index.v1+json", "schemaVersion": 2, "manifests": [ { "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json", "digest": "sha256:facefeed", "mediaType": "application/vnd.oci.image.manifest.v1+json" }, { "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json", "digest": "sha256:d0d0caca", "mediaType": "application/vnd.oci.image.manifest.v1+json" } ] } ``` To help disambiguate attestations, clients may add annotations to the items in the `manifests` list which indicate what is contained within each bundle and when it was created: - `dev.sigstore.bundle.content` - Must be one "message-signature" or "dsse-envelope" and should match the type of content embedded in the Sigstore bundle. - `dev.sigstore.bundle.predicateType` - When the bundle contains a DSSE-wrapped in-toto statement, the statement's predicate can be reflected here. - `org.opencontainers.image.created` - Date and time when the attestation bundle was created, conforming to [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6) (this is one of the pre-defined annotation keys identified in the [OCI spec](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys)). These annotations should be included as part of the bundle manifest: ```json { "mediaType": "application/vnd.oci.image.manifest.v1+json", "schemaVersion": 2, "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json", "annotations": { "dev.sigstore.bundle.content": "dsse-envelope", "dev.sigstore.bundle.predicateType": "https://slsa.dev/provenance/v1", "org.opencontainers.image.created": "2024-03-08T18:18:20.406Z" }, "config": { "mediaType": "application/vnd.oci.empty.v1+json", "digest": "sha256:44136fa3...", "size": 2 }, "layers": [ { "digest": "sha256:cafed00d...", "mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "size": 4971 } ], "subject": { "digest": "sha256:c00010ff...", "mediaType": "application/vnd.oci.image.index.v1+json" } } ``` Registries which support the referrers API will automatically propagate any annotations on the referring manifest to the index. For registries which do NOT support the referrers API, the annotations should be added to the index when it is updated manually. In either case, the end result should look something like the following: ```json { "mediaType": "application/vnd.oci.image.index.v1+json", "schemaVersion": 2, "manifests": [ { "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json", "digest": "sha256:facefeed", "mediaType": "application/vnd.oci.image.manifest.v1+json", "annotations": { "dev.sigstore.bundle.content": "message-signature", "org.opencontainers.image.created": "2024-03-07T18:17:38.000Z" } }, { "artifactType": "application/vnd.dev.sigstore.bundle.v0.3+json", "digest": "sha256:d0d0caca", "mediaType": "application/vnd.oci.image.manifest.v1+json", "annotations": { "dev.sigstore.bundle.content": "dsse-envelope", "dev.sigstore.bundle.predicateType": "https://slsa.dev/provenance/v1", "org.opencontainers.image.created": "2024-03-08T18:18:20.406Z" } } ] } ``` cosign-2.5.0/specs/COSIGN_PREDICATE_SPEC.md000066400000000000000000000020061477503325500176240ustar00rootroot00000000000000# Cosign Generic Predicate Specification `Cosign` supports working with [In-Toto Attestations](https://github.com/in-toto/attestation) using the predicate model. Several well-known predicates are supported natively, but `cosign` also supports a simple, generic, format for data that doesn't fit well into other types. The format for this is defined as follows: `data`: Raw data to place in the attestation. This is a base64-encoded string of bytes. `timestamp`: The timestamp the attestation was generated at in the RFC3339 format in the UTC timezone. Here is an example attestation containing a data file containing `foo`: ```json { "_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://cosign.sigstore.dev/attestation/v1", "subject": [ { "name": "us.gcr.io/dlorenc-vmtest2/demo", "digest": { "sha256": "124e1fdee94fe5c5f902bc94da2d6e2fea243934c74e76c2368acdc8d3ac7155" } } ], "predicate": { "Data": "foo\n", "Timestamp": "2021-08-11T14:51:09Z" } } ```cosign-2.5.0/specs/COSIGN_VULN_ATTESTATION_SPEC.md000066400000000000000000000417541477503325500210040ustar00rootroot00000000000000# Cosign Vulnerability Scan Record Attestation Specification `Cosign` is heavily using [In-toto Attestations](https://github.com/in-toto/attestation) predicate models in its own codebase. But this is not the only option you have while working with predicates in cosign. `Cosign` already defines its own predicates: [Generic Predicate Specification](COSIGN_PREDICATE_SPEC.md). This `Vulnerability Scan` attestation is one of them. Let's talk a bit about the history of this specification. We first mentioned this idea in [in-toto attestation](https://github.com/in-toto/attestation/issues/58) repository. So many people interested in this issue, and shared ideas about which parts are necessary which parts are not to make that specification well-purposed. There is an also cross [issue](https://github.com/sigstore/cosign/issues/442) on cosign side that we discussed on it. And the final format for this is defined as follows: ```json { "_type": "https://in-toto.io/Statement/v0.1", "subject": [ { ... } ], // Predicate: "predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1", "predicate": { "invocation": { "parameters": [], // [ "--format=json", "--skip-db-update" ] "uri": "", // https://github.com/developer-guy/alpine/actions/runs/1071875574 "event_id": "", // 1071875574 "builder.id": "" // GitHub Actions }, "scanner": { "uri": "", // pkg:github/aquasecurity/trivy@244fd47e07d1004f0aed9 "version": "", // 0.19.2 "db": { "uri": "", // pkg:github/aquasecurity/trivy-db/commit/4c76bb580b2736d67751410fa4ab66d2b6b9b27d "version": "" // "v1-2021080612" }, "result": {} }, "metadata": { "scanStartedOn": "", // 2021-08-06T17:45:50.52Z "scanFinishedOn": "" // 2021-08-06T17:50:50.52Z } } } ``` ## Fields **scanner** > There are lots of container image scanners such as Trivy, Grype, Clair, etc. > This field describes which scanner is used while performing a container image scan, > as well as version information and which Vulnerability DB is used. **scanner.uri** string (ResourceURI), optional > > URI indicating the identity of the source of the scanner. **scanner.version** string (ResourceURI), optional > The version of the scanner. **scanner.db.uri** string (ResourceURI), optional > URI indicating the identity of the source of the Vulnerability DB. **scanner.db.version** string, optional > The version of the Vulnerability DB. **scanner.result** object > This is the most important part of this field because it'll store the scan result as a whole. So, people might want > to use this field to take decisions based on them by making use of Policy Engines tooling whether allow or deny these images. **metadata.scanStartedOn string (Timestamp), required** > The timestamp of when the scan started. **metadata.scanFinishedOn string (Timestamp), required** > The timestamp of when the scan completed. ```shell $ trivy image -f json alpine:3.12 ```
Scan Result ```json { "SchemaVersion": 2, "ArtifactName": "alpine:3.12", "ArtifactType": "container_image", "Metadata": { "OS": { "Family": "alpine", "Name": "3.12.9" }, "ImageID": "sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507", "DiffIDs": [ "sha256:eb4bde6b29a6746e0779f80a09ca6f0806de61475059f7d56d6e20f6cc2e15f7" ], "RepoTags": [ "alpine:3.12" ], "RepoDigests": [ "alpine@sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280" ], "ImageConfig": { "architecture": "amd64", "container": "385e1cc96cc7482dfb6847e293bb24baecd3f48a49791b9b45e297204b160287", "created": "2021-11-12T17:20:08.442217528Z", "docker_version": "20.10.7", "history": [ { "created": "2021-11-12T17:20:08.190319702Z", "created_by": "/bin/sh -c #(nop) ADD file:8f5bc5ce64ef781adadca88e4004e17affc72e6f20dbd08b9c478def12fe1dd3 in / " }, { "created": "2021-11-12T17:20:08.442217528Z", "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", "empty_layer": true } ], "os": "linux", "rootfs": { "type": "layers", "diff_ids": [ "sha256:eb4bde6b29a6746e0779f80a09ca6f0806de61475059f7d56d6e20f6cc2e15f7" ] }, "config": { "Cmd": [ "/bin/sh" ], "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Image": "sha256:7d1c1e4b291dc9519b43a2b9c9330655927f6dfde90d36ef5fd16b2ae0f28bbc" } } }, "Results": [ { "Target": "alpine:3.12 (alpine 3.12.9)", "Class": "os-pkgs", "Type": "alpine", "Vulnerabilities": [ { "VulnerabilityID": "CVE-2021-28831", "PkgName": "busybox", "InstalledVersion": "1.31.1-r21", "FixedVersion": "1.32.1-r4", "Layer": { "Digest": "sha256:8572bc8fb8a32061648dd183b2c0451c82be1bd053a4ea8fae991436b92faebb", "DiffID": "sha256:eb4bde6b29a6746e0779f80a09ca6f0806de61475059f7d56d6e20f6cc2e15f7" }, "SeveritySource": "nvd", "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-28831", "Title": "busybox: invalid free or segmentation fault via malformed gzip data", "Description": "decompress_gunzip.c in BusyBox through 1.32.1 mishandles the error bit on the huft_build result pointer, with a resultant invalid free or segmentation fault, via malformed gzip data.", "Severity": "HIGH", "CweIDs": [ "CWE-755" ], "CVSS": { "nvd": { "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "V2Score": 5, "V3Score": 7.5 }, "redhat": { "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "V3Score": 7.5 } }, "References": [ "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-28831", "https://git.busybox.net/busybox/commit/?id=f25d254dfd4243698c31a4f3153d4ac72aa9e9bd", "https://lists.debian.org/debian-lts-announce/2021/04/msg00001.html", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3UDQGJRECXFS5EZVDH2OI45FMO436AC4/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Z7ZIFKPRR32ZYA3WAA2NXFA3QHHOU6FJ/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZASBW7QRRLY5V2R44MQ4QQM4CZIDHM2U/", "https://security.gentoo.org/glsa/202105-09", "https://ubuntu.com/security/notices/USN-5179-1" ], "PublishedDate": "2021-03-19T05:15:00Z", "LastModifiedDate": "2021-05-26T10:15:00Z" }, { "VulnerabilityID": "CVE-2021-28831", "PkgName": "ssl_client", "InstalledVersion": "1.31.1-r21", "FixedVersion": "1.32.1-r4", "Layer": { "Digest": "sha256:8572bc8fb8a32061648dd183b2c0451c82be1bd053a4ea8fae991436b92faebb", "DiffID": "sha256:eb4bde6b29a6746e0779f80a09ca6f0806de61475059f7d56d6e20f6cc2e15f7" }, "SeveritySource": "nvd", "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-28831", "Title": "busybox: invalid free or segmentation fault via malformed gzip data", "Description": "decompress_gunzip.c in BusyBox through 1.32.1 mishandles the error bit on the huft_build result pointer, with a resultant invalid free or segmentation fault, via malformed gzip data.", "Severity": "HIGH", "CweIDs": [ "CWE-755" ], "CVSS": { "nvd": { "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "V2Score": 5, "V3Score": 7.5 }, "redhat": { "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "V3Score": 7.5 } }, "References": [ "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-28831", "https://git.busybox.net/busybox/commit/?id=f25d254dfd4243698c31a4f3153d4ac72aa9e9bd", "https://lists.debian.org/debian-lts-announce/2021/04/msg00001.html", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3UDQGJRECXFS5EZVDH2OI45FMO436AC4/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Z7ZIFKPRR32ZYA3WAA2NXFA3QHHOU6FJ/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZASBW7QRRLY5V2R44MQ4QQM4CZIDHM2U/", "https://security.gentoo.org/glsa/202105-09", "https://ubuntu.com/security/notices/USN-5179-1" ], "PublishedDate": "2021-03-19T05:15:00Z", "LastModifiedDate": "2021-05-26T10:15:00Z" } ] } ] } ```
Here is an example predicate containing a vulnerability scan result above: ```json { "predicate": { "invocation": { "parameters": [ "--format=json" ], "uri": "https://github.com/developer-guy/alpine/actions/runs/1071875574", "event_id": "1071875574", "builder.id": "github actions" }, "scanner": { "uri": "pkg:github/aquasecurity/trivy@244fd47e07d1004f0aed9", "version": "0.19.2", "db": { "uri": "pkg:github/aquasecurity/trivy-db/commit/4c76bb580b2736d67751410fa4ab66d2b6b9b27d", "version": "v1-2021080612" }, "result": { "SchemaVersion": 2, "ArtifactName": "alpine:3.12", "ArtifactType": "container_image", "Metadata": { "OS": { "Family": "alpine", "Name": "3.12.9" }, "ImageID": "sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507", "DiffIDs": [ "sha256:eb4bde6b29a6746e0779f80a09ca6f0806de61475059f7d56d6e20f6cc2e15f7" ], "RepoTags": [ "alpine:3.12" ], "RepoDigests": [ "alpine@sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280" ], "ImageConfig": { "architecture": "amd64", "container": "385e1cc96cc7482dfb6847e293bb24baecd3f48a49791b9b45e297204b160287", "created": "2021-11-12T17:20:08.442217528Z", "docker_version": "20.10.7", "history": [ { "created": "2021-11-12T17:20:08.190319702Z", "created_by": "/bin/sh -c #(nop) ADD file:8f5bc5ce64ef781adadca88e4004e17affc72e6f20dbd08b9c478def12fe1dd3 in / " }, { "created": "2021-11-12T17:20:08.442217528Z", "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", "empty_layer": true } ], "os": "linux", "rootfs": { "type": "layers", "diff_ids": [ "sha256:eb4bde6b29a6746e0779f80a09ca6f0806de61475059f7d56d6e20f6cc2e15f7" ] }, "config": { "Cmd": [ "/bin/sh" ], "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Image": "sha256:7d1c1e4b291dc9519b43a2b9c9330655927f6dfde90d36ef5fd16b2ae0f28bbc" } } }, "Results": [ { "Target": "alpine:3.12 (alpine 3.12.9)", "Class": "os-pkgs", "Type": "alpine", "Vulnerabilities": [ { "VulnerabilityID": "CVE-2021-28831", "PkgName": "busybox", "InstalledVersion": "1.31.1-r21", "FixedVersion": "1.32.1-r4", "Layer": { "Digest": "sha256:8572bc8fb8a32061648dd183b2c0451c82be1bd053a4ea8fae991436b92faebb", "DiffID": "sha256:eb4bde6b29a6746e0779f80a09ca6f0806de61475059f7d56d6e20f6cc2e15f7" }, "SeveritySource": "nvd", "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-28831", "Title": "busybox: invalid free or segmentation fault via malformed gzip data", "Description": "decompress_gunzip.c in BusyBox through 1.32.1 mishandles the error bit on the huft_build result pointer, with a resultant invalid free or segmentation fault, via malformed gzip data.", "Severity": "HIGH", "CweIDs": [ "CWE-755" ], "CVSS": { "nvd": { "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "V2Score": 5, "V3Score": 7.5 }, "redhat": { "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "V3Score": 7.5 } }, "References": [ "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-28831", "https://git.busybox.net/busybox/commit/?id=f25d254dfd4243698c31a4f3153d4ac72aa9e9bd", "https://lists.debian.org/debian-lts-announce/2021/04/msg00001.html", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3UDQGJRECXFS5EZVDH2OI45FMO436AC4/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Z7ZIFKPRR32ZYA3WAA2NXFA3QHHOU6FJ/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZASBW7QRRLY5V2R44MQ4QQM4CZIDHM2U/", "https://security.gentoo.org/glsa/202105-09", "https://ubuntu.com/security/notices/USN-5179-1" ], "PublishedDate": "2021-03-19T05:15:00Z", "LastModifiedDate": "2021-05-26T10:15:00Z" }, { "VulnerabilityID": "CVE-2021-28831", "PkgName": "ssl_client", "InstalledVersion": "1.31.1-r21", "FixedVersion": "1.32.1-r4", "Layer": { "Digest": "sha256:8572bc8fb8a32061648dd183b2c0451c82be1bd053a4ea8fae991436b92faebb", "DiffID": "sha256:eb4bde6b29a6746e0779f80a09ca6f0806de61475059f7d56d6e20f6cc2e15f7" }, "SeveritySource": "nvd", "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-28831", "Title": "busybox: invalid free or segmentation fault via malformed gzip data", "Description": "decompress_gunzip.c in BusyBox through 1.32.1 mishandles the error bit on the huft_build result pointer, with a resultant invalid free or segmentation fault, via malformed gzip data.", "Severity": "HIGH", "CweIDs": [ "CWE-755" ], "CVSS": { "nvd": { "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "V2Score": 5, "V3Score": 7.5 }, "redhat": { "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", "V3Score": 7.5 } }, "References": [ "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-28831", "https://git.busybox.net/busybox/commit/?id=f25d254dfd4243698c31a4f3153d4ac72aa9e9bd", "https://lists.debian.org/debian-lts-announce/2021/04/msg00001.html", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3UDQGJRECXFS5EZVDH2OI45FMO436AC4/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Z7ZIFKPRR32ZYA3WAA2NXFA3QHHOU6FJ/", "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZASBW7QRRLY5V2R44MQ4QQM4CZIDHM2U/", "https://security.gentoo.org/glsa/202105-09", "https://ubuntu.com/security/notices/USN-5179-1" ], "PublishedDate": "2021-03-19T05:15:00Z", "LastModifiedDate": "2021-05-26T10:15:00Z" } ] } ] } }, "metadata": { "scanStartedOn": "2021-08-06T17:45:50.52Z", "scanFinishedOn": "2021-08-06T17:50:50.52Z" } } } ``` cosign-2.5.0/specs/SBOM_SPEC.md000066400000000000000000000127731477503325500160360ustar00rootroot00000000000000# Cosign SBOM Specifications **WARNING**: SBOM attachments are deprecated and support will be removed in a Cosign release soon after 2024-02-22 (see [sigstore/cosign#2755](https://github.com/sigstore/cosign/issues/2755)). Instead, please use SBOM [attestations](./ATTESTATION_SPEC.md). This document aims to describe how `cosign` attaches SBOM (Software Bill of Materials) documents to containers. The goal is to specify the behavior well enough to promote other implementations and enable interoperability. SBOMs attached with `cosign` should be retrievable in other tools, and vice-versa. This document focuses on the layout of an SBOM within an [OCI Image Manifest V1](https://github.com/opencontainers/image-spec/blob/master/manifest.md) object. This document does not prescribe any specific SBOM format. Multiple formats can be used, and the `mediaType` property should describe the format of a particular SBOM document. Multiple SBOMs may be "attached" to one image. Each SBOM may refer to the entire image, or to a specific part of that image. Exactly what the SBOM refers to is called the "scope" of that SBOM. SBOMs stored in an OCI registry are generally assumed to refer to other objects stored in that same registry. This document does not specify how these "links" are created. A naming convention or the [in-progress OCI `references` API](https://github.com/opencontainers/image-spec/issues/827) are viable options. This document does not specify how clients should behave when multiple SBOMs are present for an image. Clients may list all the SBOMs, or may provide tooling to filter based on SBOM type or scope. ## Overall Layout An SBOM object is represented an [OCI Image Manifest V1](https://github.com/opencontainers/image-spec/blob/master/manifest.md). Each individual SBOM is represented as a `layer`, using a standard `descriptor`. The `layers` list is ordered, but no order is assumed or important for the SBOM documents. Here is an example manifest containing one SBOM, in the [SPDX](https://spdx.org) format: ```json { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 233, "digest": "sha256:83bd5fb5b39f65f28e50a86d48fa79c07880befc292d92eebdc18531054b070c" }, "layers": [ { "mediaType": "text/spdx", "size": 246, "digest": "sha256:ed3ad03d3b87843b5419d7dce9d50a3e0f45554b2ba93bf378611cae6b450cff", } ] } ``` Multiple SBOMs may be attached, using multiple formats. This example shows two SBOMs, one in the SPDX format and one in the [CycloneDX](https://cyclonedx.org) format: ```json { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 233, "digest": "sha256:83bd5fb5b39f65f28e50a86d48fa79c07880befc292d92eebdc18531054b070c" }, "layers": [ { "mediaType": "text/spdx", "size": 246, "digest": "sha256:ed3ad03d3b87843b5419d7dce9d50a3e0f45554b2ba93bf378611cae6b450cff", }, { "mediaType": "application/vnd.cyclonedx", "size": 462, "digest": "sha256:e0851a4aa13657fc8dcd01e0e5e08cb817123ccb82e2c604b34f9ec9c1755e3f", } ] } ``` Each individual SBOM may also be "scoped" to a part of the object it refers to. This is indicated via an annotation on the descriptor. In this example, the SBOM only refers to a single layer: ```json { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "size": 233, "digest": "sha256:83bd5fb5b39f65f28e50a86d48fa79c07880befc292d92eebdc18531054b070c" }, "layers": [ { "mediaType": "text/spdx", "size": 246, "digest": "sha256:ed3ad03d3b87843b5419d7dce9d50a3e0f45554b2ba93bf378611cae6b450cff", "annotations": { "dev.sigstore.sbom.scope": "layer=sha256:a69d803ab2179a570eda27135989ee850de53bbd98efc8f0284f13700a94149f", } } ] } ``` ## MediaTypes The SBOM formats supported by cosign are [SPDX](https://spdx.org), [CycloneDX](https://cyclonedx.org/) and [syft](https://github.com/anchore/syft). The `mediaTypes` for these should be indicated in the `descriptor` for each `layer`. The `mediaTypes` are: * `application/vnd.cyclonedx` * `text/spdx` * `application/vnd.syft+json` (`syft` is a JSON only format) These `mediaTypes` can contain format-specific suffixes as well. For example: * `application/vnd.cyclonedx+xml` * `application/vnd.cyclonedx+json` * `text/spdx+xml` * `text/spdx+json` ## Scopes SBOMs may refer to an entire object, or to a specific part of that object. This is called the `scope` of the SBOM. The `scope` should be indicated via an annotation on the `Descriptor`, with the key of `dev.sigstore.sbom.scope`. A descriptor with no scope is assumed to refer to the entire object. This is the same as the scope of `all`. Well-known scopes include: * `all`: the SBOM refers to the entire object. * `layer=sha256:$DIGEST`: the SBOM refers to the layer with the appropriate digest. * `path=`: the SBOM refers to file at path `foo` in the flattened image. Scopes may be repeated, and are separated by the `,` character. This scope refers to two layers: `layer=sha256:$DIGEST,layer=sha256:$OTHERDIGEST` ## Relationship While SBOMs typically relate directly to the contents of the object they refer to, in certain circumstances they may instead relate to the object indirectly. One example here is that the SBOM could describe the environment the object was built in, rather than the contents of the object itself. This type of relationship will be tracked by this spec somehow, but we're not sure exactly how yet. cosign-2.5.0/specs/SIGNATURE_SPEC.md000066400000000000000000000504031477503325500166270ustar00rootroot00000000000000# Cosign Signature Specifications This document aims to describe how `cosign` signs containers. The goal is to specify the behavior well enough to promote other implementations and enable interoperability. Container signatures generated with `cosign` should be verifiable in other tools, and vice-versa. This document is broken up into a few parts: * [Properties](#properties) details the individual components that are used to create and verify signatures. * [Storage](#storage) details how signatures are stored and discovered in an OCI registry. * [Payload](#payload) details the format of to-be-signed payloads. * [Signature](#signature) details the signature schemes supported. ## Properties This section describes the REQUIRED and OPTIONAL properties used to sign and verify an image. Their layout in an OCI object is described below. * `payload` bytes This REQUIRED property contains the contents of signed data in byte-form. Because signatures are __detached__, the payload MUST contain the digest of the image it references, in a well-known location. This location is dependent on the [payload format](#payloads). * `mediaType` string This REQUIRED property contains the media type of the payload. * `signature` string This REQUIRED property contains the base64-encoded signature. This signature MUST be generated by a supported scheme. For more details on supported schemes, see the [`Signature`](#signature) section below. Example `signature`: `MEYCIQDXmXWj59naoPFlLnCADIPLKgLG3LyFtKrbjpnkYiGNGgIhAJ/eNx5zr/l1MJKSFpFMjPKKr4fjh5RHEtT2DhMamZuT` * `certificate` string This OPTIONAL property contains a [PEM-encoded](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) x509 certificate. If present, this certificate MUST embed the public key that can be used to verify the signature. Example `certificate`: ``` -----BEGIN CERTIFICATE----- MIICrjCCAjSgAwIBAgIUAM4mURWUSkg06fmHmFfTmerYKaUwCgYIKoZIzj0EAwMw KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y MTA0MDExNTU5MDZaFw0yMTA0MDExNjE4NTlaMDoxGzAZBgNVBAoMEmRsb3JlbmNA Z29vZ2xlLmNvbTEbMBkGA1UEAwwSZGxvcmVuY0Bnb29nbGUuY29tMFkwEwYHKoZI zj0CAQYIKoZIzj0DAQcDQgAE3R0ZtpfBd3Y8DaXuB1gM8JPlhsDIEfXO/WsMJEN1 4hEn8wajX2HklqL7igZPFICv6tBUGylIHp2mTH2Nhv38mqOCASYwggEiMA4GA1Ud DwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0G A1UdDgQWBBTy3UWIop0bNrdNgSrVHHD10qSASTAfBgNVHSMEGDAWgBTIxR0AQZok KTJRJOsNrkrtSgbT7DCBjQYIKwYBBQUHAQEEgYAwfjB8BggrBgEFBQcwAoZwaHR0 cDovL3ByaXZhdGVjYS1jb250ZW50LTYwM2ZlN2U3LTAwMDAtMjIyNy1iZjc1LWY0 ZjVlODBkMjk1NC5zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2NhMzZhMWU5NjI0MmI5 ZmNiMTQ2L2NhLmNydDAdBgNVHREEFjAUgRJkbG9yZW5jQGdvb2dsZS5jb20wCgYI KoZIzj0EAwMDaAAwZQIwC15Gtd9F6W9lmJuoXMym9DfWlBpK5HEPak38WPXqowRp 6p+2/3jSLkFT5Nn5fuISAjEAouVlX4zH2rlkfg45HnDJax7o6ZV+E0/6BdAms44D Ej6T/GLK6XJSB28haSPRWB7k -----END CERTIFICATE----- ``` * `chain` string This OPTIONAL property contains a PEM-encoded, DER-formatted, ASN.1 x509 certificate chain. The `certificate` property MUST be present if this property is present. This chain MAY be used by implementations to verify the `certificate` property. Clients MUST validate that any certificates in the chain that are self-signed or are expected to be trust anchors with an out-of-band mechanism. Example `chain`: ``` ----BEGIN CERTIFICATE----- MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIICVTCCAT2gAwIBAgIQAUrAOaxMcCVQ6AwcDagmRzANBgkqhkiG9w0BAQsFADAh MR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAw MFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRl c3RhdGlvbiA5YzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABFfnSOVAZLOTOYRs n4BeD3cMYHFvtwBsK8X0yJ21NKUwJ3fvnqdq0qGeIT92zstNLEWCqP3qMkhs9sh4 wP1tHTGjTjBMMBEGCisGAQQBgsQKAwMEAwUCBjAUBgorBgEEAYLECgMHBAYCBADH kP4wEAYKKwYBBAGCxAoDCAQCAwIwDwYKKwYBBAGCxAoDCQQBAzANBgkqhkiG9w0B AQsFAAOCAQEAVRtRFpmgFD+rQqBG92HArMQ+j1FMX23QL9Z76IhaSElmN6cjgsv3 8pJM8GL+ih6vVyCHeU6GoE9Bgj2XB02ZgkmWihnaJX2WG4VOm2dN3SqDmWFp4KLJ vuzVXEHWuGevwMAOsvMkmXP8HI2npaCPBmprirExbv6bxSyng4ZNHmgdzqmjYyt+ d+ELe3xEeYYqQYx+IswHPRE5mGk/PO4hysk79mhwRNuvmygDbI8Emwvp3Pgzlgr1 Gyp4apdU7AXEwysEQIb034aPrTlpmxh90SnTZFs2DHOvCjCPPAmoWfuQUwPhSPRb 92pXqODWYqpW8+IRED5e42Ncu9XtDgS5Pw== -----END CERTIFICATE----- ``` * `bundle` string This OPTIONAL property contains a JSON formatted `bundle` type, which can be used for offline verification. Example `bundle`: ```json { "SignedEntryTimestamp": "MEUCIQDHiGUesxPpn+qRONLmKlNIVPhl9gBMnwNeIQmRkRmZVQIgRxPpuYQDZR/8lYKcEfiQn5b+7VDoJIC72ZWHO9ZCp1A=", "Payload": { "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJzcGVjIjp7ImRhdGEiOnsiaGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImE0NDkyYjBlYWJkZDIzMTJmMDYzMjkwYWJkNzk3ZDlkNzFhM2FiMjhiZDY1YTJjMTg5YjBkZjBkMzliOGMzYjkifX0sInNpZ25hdHVyZSI6eyJjb250ZW50IjoiTUVRQ0lDTmRYeTNiWHAxRE1PTDZOUGZYMzVnSjI3YnpsZHdTdkNBTnd5ZE9RVWlqQWlCQWg5WlJwQ3AzYlg5eE9UbEhTR2w0cFVGd0ZtUFJJWGZpY09pRTBHM1Vzdz09IiwiZm9ybWF0IjoieDUwOSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTmxla05EUVdkRFowRjNTVUpCWjBsVVZISk9aa013YkZSSmRWSXZWR0UyWm14MWFtdFFOWHBaTDFSQlMwSm5aM0ZvYTJwUFVGRlJSRUY2UVhFS1RWSlZkMFYzV1VSV1VWRkxSWGQ0ZW1GWFpIcGtSemw1V2xNMWExcFlXWGhGVkVGUVFtZE9Wa0pCVFZSRFNFNXdXak5PTUdJelNteE5RalJZUkZSSmVBcE5SRmw1VFdwSmVFMUVaM2RPUm05WVJGUkplRTFFV1hsTmFrbDRUV3BuZDAweGIzZEJSRUphVFVKTlIwSjVjVWRUVFRRNVFXZEZSME5EY1VkVFRUUTVDa0YzUlVoQk1FbEJRazFGV1M4ck4yRktjRmRLVFhjNWVrTmljMDFrT0hOQlRUTmxSbk5OTjBSbFpFZGlXRzlNUjJ4YUwyZHBNR2h5WTBaU1NWVTRiM2NLUzBKeU1ISkVTRE5QVkZaSWJVdFVZMkV2SzIweGQxQjNTVzlZTTFGUVYycG5aMFYwVFVsSlFrdFVRVTlDWjA1V1NGRTRRa0ZtT0VWQ1FVMURRalJCZHdwRmQxbEVWbEl3YkVKQmQzZERaMWxKUzNkWlFrSlJWVWhCZDAxM1JFRlpSRlpTTUZSQlVVZ3ZRa0ZKZDBGRVFXUkNaMDVXU0ZFMFJVWm5VVlZ5WVRoTENuSnJaMjAzVGtsNFRrNXBVMkpZVG00eFdFVkxhRzFyZDBoM1dVUldVakJxUWtKbmQwWnZRVlY1VFZWa1FVVkhZVXBEYTNsVlUxUnlSR0UxU3pkVmIwY0tNQ3QzZDJkWk1FZERRM05IUVZGVlJrSjNSVUpDU1VkQlRVZzBkMlpCV1VsTGQxbENRbEZWU0UxQlMwZGpSMmd3WkVoQk5reDVPWGRqYld3eVdWaFNiQXBaTWtWMFdUSTVkV1JIVm5Wa1F6QXlUVVJPYlZwVVpHeE9lVEIzVFVSQmQweFVTWGxOYW1OMFdXMVpNMDVUTVcxT1Ixa3hXbFJuZDFwRVNUVk9WRkYxQ21NelVuWmpiVVp1V2xNMWJtSXlPVzVpUjFab1kwZHNla3h0VG5aaVV6bHFXVlJOTWxsVVJteFBWRmw1VGtSS2FVOVhXbXBaYWtVd1RtazVhbGxUTldvS1kyNVJkMHBCV1VSV1VqQlNRVkZJTDBKQ2IzZEhTVVZYWTBoS2NHVlhSak5aVjFKdlpESkdRVm95T1haYU1uaHNURzFPZG1KVVFVdENaMmR4YUd0cVR3cFFVVkZFUVhkT2NFRkVRbTFCYWtWQk1UQlVSR015Wm1oUFZrRlVNWFJzZFM4MmMzWnhSbEZ1YkRaWU9YZGhNbXRUU2t0RGJqUkZZbFJFYTNwYVJYb3lDblppUWtwb2FFZ3ZjbWRXUjFKMU5tWkJha1ZCYkhsb05uUmhZelJZVFRaS2IzVlZlRWtyTjFnelFtUTFXVXR5WlRGS1dFOWhia0ZaYW1adldHNTVUSFFLZDNCSVFWb3paVzFhY0VWa00yeHFTVEF3Vm04S0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPSJ9fX0sImtpbmQiOiJyZWtvcmQifQ==", "integratedTime": 1624396085, "logIndex": 5179, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d" } } ``` The following are REQUIRED properties of the bundle: - The `SignedEntryTimestamp` is a rekor-signed signature over the logIndex, body and integratedTime of the Rekor Log Entry - The `Payload` consists of all fields required to verify the SET: - The `body` is the body of the Rekor Log Entry - The `integratedTime` is the UNIX timestamp the log entry was integrated into the transparency log - The `logIndex` is the index of the log entry in the transparency log - The `logID` is the SHA256 hash of the DER-encoded public key for the log at the time the entry was included in the log * `rfc3161timestamp` string This OPTIONAL property contains a JSON formatted `RFC3161Timestamp` containing the timestamp response from a timestamp authority. ### Verification See the [client specification on verification](https://github.com/sigstore/architecture-docs/blob/main/client-spec.md#4-verification). ## Storage `cosign` image signatures are stored in an OCI registry and are designed to make use of the existing specifications. The full specifications for the OCI formats and specifications used are available [here](https://github.com/opencontainers). ### Discovery Signature object are placed in specific location in an OCI registry to enable consistent, interoperable discovery. Multiple discovery mechanisms MAY be used. Implementations MUST support at least the following mechanisms: * Tag-based Discovery #### Tag-based Discovery In this scheme, signatures are stored in an OCI registry in a predictable location, addressable by tag. The location of signatures corresponding to a specific object can be computed using the digest of the object. If the object is referenced by tag, the tag must first be resolved to a digest. Then the digest of the object (of the form `sha256:abcdef...`) is encoded into a tag name using the following rules: * Replace the `:` character with a `-` * Append the `.sig` suffix Example digest->tag mapping: 1. Start with `gcr.io/dlorenc-vmtest2/demo:latest` 2. Resolve this to a digest: `sha256:97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36` 3. Follow the encoding rules: `sha256-97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36.sig` 4. Signature can be found at `gcr.io/dlorenc-vmtest2/demo:sha256-97fc222cee7991b5b061d4d4afdb5f3428fcb0c9054e1690313786befa1e4e36.sig` Implementations MAY store signatures objects in the same OCI repository as the target image or a different one. ### Object Types This section describes the way the properties from above are embedded into OCI objects that can be stored in a registry. Implementations MUST support storing signatures in at least the following object types: * [OCI Image Manifest V1](#oci-image-manifest-v1) #### OCI Image Manifest V1 This section describes the way the mandatory and optional signature properties are embedded into an [OCI Image Manifest V1](https://github.com/opencontainers/image-spec/blob/master/manifest.md) object. Only one image manifest is created for every signed object. Multiple signatures can be embedded in one image manifest. ##### Payload and mediaType The `payload` bytes are uploaded to an OCI registry as a `blob`, and are referenced by `digest`, `size` and `mediaType.` The digest is embedded into the `Image` manifest as a `layer`, via a [`Descriptor`](https://github.com/opencontainers/image-spec/blob/master/descriptor.md). The `mediaType` property for the `payload` is included in the same descriptor. Example `payload`: ```json { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", }, "layers": [ { "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", "size": 210, "digest": "sha256:1119abab63e605dcc281019bad0424744178b6f61ba57378701fe7391994c999", }, ] } ``` ##### Signature The `signature` is base64-encoded and stored as an `annotation` on the layer, in the same descriptor. The `annotation` key is `dev.cosignproject.cosign/signature`. Example `signature`: ```json "annotations": { "dev.cosignproject.cosign/signature": "MEUCIBKI9FIC+YD3m/lWViyPxsJsbnIHj86sSbb7L3qvpEFoAiEA2ZChO/67CuAPQKJLBVsAc7bs9hBK8RpsdfjBsByGKJM=" } ``` ##### Certificate The `certificate` is stored as an `annotation` on the layer, in the same descriptor. The `annotation` key is `dev.cosignproject.cosign/certificate`. Example `certificate`: ```json "annotations": { "dev.sigstore.cosign/certificate": "-----BEGIN CERTIFICATE-----\nMIICrjCCAjSgAwIBAgIUAM4mURWUSkg06fmHmFfTmerYKaUwCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMTA0MDExNTU5MDZaFw0yMTA0MDExNjE4NTlaMDoxGzAZBgNVBAoMEmRsb3JlbmNA\nZ29vZ2xlLmNvbTEbMBkGA1UEAwwSZGxvcmVuY0Bnb29nbGUuY29tMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAE3R0ZtpfBd3Y8DaXuB1gM8JPlhsDIEfXO/WsMJEN1\n4hEn8wajX2HklqL7igZPFICv6tBUGylIHp2mTH2Nhv38mqOCASYwggEiMA4GA1Ud\nDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0G\nA1UdDgQWBBTy3UWIop0bNrdNgSrVHHD10qSASTAfBgNVHSMEGDAWgBTIxR0AQZok\nKTJRJOsNrkrtSgbT7DCBjQYIKwYBBQUHAQEEgYAwfjB8BggrBgEFBQcwAoZwaHR0\ncDovL3ByaXZhdGVjYS1jb250ZW50LTYwM2ZlN2U3LTAwMDAtMjIyNy1iZjc1LWY0\nZjVlODBkMjk1NC5zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2NhMzZhMWU5NjI0MmI5\nZmNiMTQ2L2NhLmNydDAdBgNVHREEFjAUgRJkbG9yZW5jQGdvb2dsZS5jb20wCgYI\nKoZIzj0EAwMDaAAwZQIwC15Gtd9F6W9lmJuoXMym9DfWlBpK5HEPak38WPXqowRp\n6p+2/3jSLkFT5Nn5fuISAjEAouVlX4zH2rlkfg45HnDJax7o6ZV+E0/6BdAms44D\nEj6T/GLK6XJSB28haSPRWB7k\n-----END CERTIFICATE-----\n", } ``` ##### Chain The `chain` is stored as an `annotation` on the layer, in the same descriptor. The `annotation` key is `dev.cosignproject.cosign/chain`. Example `chain`: ```json "annotations": { "dev.sigstore.cosign/chain": "-----BEGIN CERTIFICATE-----\nMIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq\nMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx\nMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu\nZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy\nA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas\ntaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm\nMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE\nFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u\nSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx\nVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup\nHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==\n-----END CERTIFICATE-----" } ``` ## Payloads Implementations MUST support at least the following payload types: * Simple Signing ### Simple Signing The Simple Signing payload format is [specified here](https://github.com/containers/image/blob/a5061e5a5f00333ea3a92e7103effd11c6e2f51d/docs/containers-signature.5.md#json-data-format) The following additional semantics are applied: * The `mediaType` used to identify this payload format is: `application/vnd.dev.cosign.simplesigning.v1+json`. * The `critical.type` value used to identify `cosign` signatures is: `cosign container image signature`. * The `critical.identity.docker-reference` field is ignored. * Optional user-specified claims may be included in the `Optional` section. For example: ```json { "critical": { "identity": { "docker-reference": "testing/manifest" }, "image": { "Docker-manifest-digest": "sha256:20be...fe55" }, "type": "cosign container image signature" }, "optional": { "creator": "atomic", "timestamp": 1458239713 } } ``` ## Signature Schemes Implementations must support at least the following schemes: * ECDSA-P256 No information about the signature scheme is included in the object. Clients must determine the signature scheme out-of-band during the verification process. ### Hashing Algorithms Signers and verifiers must know the hash algorithm used in addition to the signature scheme. In an attempt to avoid specifying a particular hashing algorithm, we require that digest be calculated using the SAME algorithm as the OCI registry. In practice, this means [sha256](https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests). When the payload is stored as a `blob` in the OCI registry, it is exposed and referenced via a [Content Addressable](https://en.wikipedia.org/wiki/Content-addressable_storage) API. Example referenced payload: ```json { "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", "size": 210, "digest": "sha256:1119abab63e605dcc281019bad0424744178b6f61ba57378701fe7391994c999", } ``` Here, `1119abab63e605dcc281019bad0424744178b6f61ba57378701fe7391994c999` is the hex-encoded digest, and the `sha256:` prefix specifies the algorithm. This value is already calculated and verified by both the registry and client-tooling. This means that our signatures is "linked" to a "container image" happens via two hops: `Sign(sha256(SimpleSigningPayload(sha256(Image Manifest))))` Allowing flexibility in hashing algorithms of the digital signature would only allow manipulation of the "outer" one - the image manifest itself is always referenced by `sha256` today. This means using a different hashing algorithm as part of the final signature, even one perceived as "stronger", would result in limited cryptographic benefits. Put simply: implementations MUST use the same hash algorithm used by the underlying registry to reference the payload. Any future algorithmic-agility will come from the storage layer as part of the OCI specification. # Rationales and Commentary This document, while labeled a `Specification`, aims to specify as few things as possible. Instead, we prescribe the usage of other specifications. This section contains a rationale for each choice, as well as comparisons to alternatives considered. ## Payload/Attestation Format: Simple Signing We chose Simple Signing because it has the most existing usage and meets our requirements: * Critical/Optional sections * Extensible with more attestations * Cross-language serialization support ### Alternatives * OCI Descriptor. This has the fields we need, with a few problems. * The annotations section is limiting with map[string]string. * The `URLs` field is not meant for this use-case. * No real benefit, since it's stored as a `blob` (not parsed by the registry). * Plain digest This doesn't have any attestation/annotation support. * Something new See above, we've tried to avoid making any new types where possible. ## OCI Type - Docker Manifest/OCI Manifest We're currently using Docker but will switch to OCI. The format support across registries is a toss-up, but likely to improve for OCI. OCI supports custom media-types and has other "correctness" benefits. ## Discovery - Tag Based We use the tag based mechanism because it's the only option we can think of. It meets the two hard requirements: works **everywhere** today and requires no extra services. It also does not mutate the signed object (like an attached signature or index might). Support for multiple signatures works but is racy. ### Alternatives Considered Notary v1/Grafeas both require another database. A few other proprietary APIs exist, but they're not cross-registry. ## Hash Algorithm - None! Most common signature schemes support customizable hash algorithms. These are typically stored with the signature for convenience, presenting a possible attack/confusion vector. We decided to pin to the registry hash algorithm. This removes an entire moving part without sacrificing agility. The registry/client-tooling already perform hash validation as part of the CAS. While their spec does not completely pin to specific algorithm, SHA256 is ubiquitous in practice. This means that our signed payload object references the actual image "target" by a sha-256 digest - the entire signature already relies on the strength of this algorithm. Trading off this perceived agility for the reduction in attack surface is a win. ** Note **: This is only possible if we store the `payload` in a blob by itself. Serializing the payload and signature together in something like a JWT for storage would mean the payload is no longer directly hashed into the CAS. There is also a performance benefit: we can validate the signature (stored in the manifest) against the payload (stored as a blob) without fetching the blob, because the blob's digest is also present in the manifest. ## Algorithms We only require ECDSA-P256 (with the SHA256 hash algorithm, see above), but will allow for other schemes. We will bias toward algorithms well-supported in the Go ecosystem, but will accept others. We will bias toward support for algorithms with wide industry adoption, API support, and hardware availability. ## Compatibility We are compatible with the In-Toto [Metablock](https://in-toto.readthedocs.io/en/latest/model.html) and JWS [rfc7515](https://tools.ietf.org/html/rfc7515) formats. This means we can convert to these formats during verification, and from these formats during upload/signature. You can think of this spec as an "on-registry serialization format" for either of these specified formats. cosign-2.5.0/test/000077500000000000000000000000001477503325500137725ustar00rootroot00000000000000cosign-2.5.0/test/README.md000066400000000000000000000046201477503325500152530ustar00rootroot00000000000000Cosign E2E Tests ================ How to add end-to-end tests for cosign: General-purpose tests --------------------- If the test is only testing cosign itself or only needs access to Rekor and/or Fulcio, add it to `e2e_test.go`. This test suite is run by the `e2e_test.sh` script, which sets up Rekor, Fulcio, and an OIDC provider, so this can easily be run in a developer environment with no additional setup steps. In your tests, where cosign calls out to Fulcio or Rekor, make sure to set the service URLs to the constants `rekorURL` or `fulcioURL`, which point to the local instances of Rekor and Fulcio. Also have the test call the `setLocalEnv` which downloads keys from the ephemeral Sigstore services and configures cosign to trust them. Environment-specific tests -------------------------- If the test needs a more specific kind of environment, such as a KMS or an image registry, create the test in its own file and designate a `go:build` tag for it. In `e2e_test.go`, add a negation for that build tag. In your tests, set the Rekor and Fulcio URLs to the contents of the environment variables `REKOR_URL` and `FULCIO_URL`, if Rekor and Fulcio are needed for the test. Add a job for the new test in the ``e2e-test.yml`` GitHub workflow file. The job should include the following: - If the test needs access to Sigstore services, a step that calls the `sigstore/scaffolding/actions/setup` action - Steps to do whatever other custom configuration the scenario needs - A step to run the test with `go test -tags=e2e, -v ./test/...` To run these kinds of tests tests locally, you'll have to [setup scaffolding](https://github.com/sigstore/scaffolding/blob/main/getting-started.md) and do the other setup steps manually. Dos --- Add E2E tests to cover any interaction that cosign has with an external service, such as Rekor, Fulcio, a TSA, etc, or for any case where it's not possible to cover the functionality with a unit test. Don'ts ------ - Do not add shell scripts that call the cosign binary directly unless the functionality under test is the CLI itself and absolutely cannot be tested any other way. As much as possible, tests should be runnable with `go test`. - Do not allow the tests to call the production or staging instances of the Sigstore services, i.e. any `*.sigstore.dev` or `*.sigstage.dev` services. They should use the local CI instances to avoid spamming the real infrastructure. cosign-2.5.0/test/cert_utils.go000066400000000000000000000174051477503325500165050ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package test import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "math/big" "net" "net/url" "time" ) /* To use: rootCert, rootKey, _ := GenerateRootCa() subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey) leafCert, _, _ := GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) roots := x509.NewCertPool() subs := x509.NewCertPool() roots.AddCert(rootCert) subs.AddCert(subCert) opts := x509.VerifyOptions{ Roots: roots, Intermediates: subs, KeyUsages: []x509.ExtKeyUsage{ x509.ExtKeyUsageCodeSigning, }, } _, err := leafCert.Verify(opts) */ func createCertificate(template *x509.Certificate, parent *x509.Certificate, pub interface{}, priv crypto.Signer) (*x509.Certificate, error) { certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv) if err != nil { return nil, err } cert, err := x509.ParseCertificate(certBytes) if err != nil { return nil, err } return cert, nil } func GenerateRootCa() (*x509.Certificate, *ecdsa.PrivateKey, error) { rootTemplate := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: "sigstore", Organization: []string{"sigstore.dev"}, }, NotBefore: time.Now().Add(-5 * time.Hour), NotAfter: time.Now().Add(5 * time.Hour), KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, IsCA: true, } priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err } cert, err := createCertificate(rootTemplate, rootTemplate, &priv.PublicKey, priv) if err != nil { return nil, nil, err } return cert, priv, nil } func GenerateSubordinateCa(rootTemplate *x509.Certificate, rootPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) { subTemplate := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: "sigstore-sub", Organization: []string{"sigstore.dev"}, }, NotBefore: time.Now().Add(-2 * time.Minute), NotAfter: time.Now().Add(2 * time.Hour), KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, BasicConstraintsValid: true, IsCA: true, } priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err } cert, err := createCertificate(subTemplate, rootTemplate, &priv.PublicKey, rootPriv) if err != nil { return nil, nil, err } return cert, priv, nil } func GenerateLeafCertWithExpiration(subject string, oidcIssuer string, expiration time.Time, priv *ecdsa.PrivateKey, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, error) { certTemplate := &x509.Certificate{ SerialNumber: big.NewInt(1), EmailAddresses: []string{subject}, NotBefore: expiration, NotAfter: expiration.Add(10 * time.Minute), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, IsCA: false, ExtraExtensions: []pkix.Extension{{ // OID for OIDC Issuer extension Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, Critical: false, Value: []byte(oidcIssuer), }, }, } cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv) if err != nil { return nil, err } return cert, nil } func GenerateLeafCert(subject string, oidcIssuer string, parentTemplate *x509.Certificate, parentPriv crypto.Signer, exts ...pkix.Extension) (*x509.Certificate, *ecdsa.PrivateKey, error) { exts = append(exts, pkix.Extension{ // OID for OIDC Issuer extension Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, Critical: false, Value: []byte(oidcIssuer), }) certTemplate := &x509.Certificate{ SerialNumber: big.NewInt(1), EmailAddresses: []string{subject}, NotBefore: time.Now().Add(-1 * time.Minute), NotAfter: time.Now().Add(time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, IsCA: false, ExtraExtensions: exts, } priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err } cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv) if err != nil { return nil, nil, err } return cert, priv, nil } func GenerateLeafCertWithGitHubOIDs(subject string, oidcIssuer string, githubWorkflowTrigger, githubWorkflowSha, githubWorkflowName, githubWorkflowRepository, githubWorkflowRef string, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) { certTemplate := &x509.Certificate{ SerialNumber: big.NewInt(1), EmailAddresses: []string{subject}, NotBefore: time.Now().Add(-1 * time.Minute), NotAfter: time.Now().Add(time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, IsCA: false, ExtraExtensions: []pkix.Extension{{ // OID for OIDC Issuer extension Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, Critical: false, Value: []byte(oidcIssuer), }, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, Value: []byte(githubWorkflowTrigger)}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}, Value: []byte(githubWorkflowSha)}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}, Value: []byte(githubWorkflowName)}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}, Value: []byte(githubWorkflowRepository)}, {Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, Value: []byte(githubWorkflowRef)}}, } priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err } cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv) if err != nil { return nil, nil, err } return cert, priv, nil } func GenerateLeafCertWithSubjectAlternateNames(dnsNames []string, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, oidcIssuer string, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) { certTemplate := &x509.Certificate{ SerialNumber: big.NewInt(1), EmailAddresses: emailAddresses, DNSNames: dnsNames, IPAddresses: ipAddresses, URIs: uris, NotBefore: time.Now().Add(-1 * time.Minute), NotAfter: time.Now().Add(time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, IsCA: false, ExtraExtensions: []pkix.Extension{{ // OID for OIDC Issuer extension Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, Critical: false, Value: []byte(oidcIssuer), }}, } priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, nil, err } cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv) if err != nil { return nil, nil, err } return cert, priv, nil } cosign-2.5.0/test/ci.mk000066400000000000000000000004551477503325500147220ustar00rootroot00000000000000############ # signing ci ############ .PHONY: sign-ci-containers sign-ci-containers: ko cosign sign --yes --key .github/workflows/cosign-test.key -a GIT_HASH=$(GIT_HASH) ${KO_PREFIX}/cosign:$(GIT_HASH) .PHONY: sign-ci-keyless-containers sign-ci-keyless-containers: ko ./scripts/sign-images-ci.sh cosign-2.5.0/test/cmd/000077500000000000000000000000001477503325500145355ustar00rootroot00000000000000cosign-2.5.0/test/cmd/getoidctoken/000077500000000000000000000000001477503325500172145ustar00rootroot00000000000000cosign-2.5.0/test/cmd/getoidctoken/main.go000066400000000000000000000035111477503325500204670ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "log" "net/http" "os" "time" "github.com/kelseyhightower/envconfig" ) type envConfig struct { FileName string `envconfig:"OIDC_FILE" default:"/var/run/sigstore/cosign/oidc-token" required:"true"` } func tokenWriter(filename string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, req *http.Request) { getToken(filename, w, req) } } func getToken(tokenFile string, w http.ResponseWriter, _ *http.Request) { content, err := os.ReadFile(tokenFile) if err != nil { log.Print("failed to read token file", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } _, err = fmt.Fprint(w, string(content)) if err != nil { log.Print("failed to write token file to response", err) http.Error(w, err.Error(), http.StatusInternalServerError) } } func main() { var env envConfig if err := envconfig.Process("", &env); err != nil { log.Fatalf("failed to process env var: %s", err) } http.HandleFunc("/", tokenWriter(env.FileName)) srv := &http.Server{ Addr: ":8080", ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, ReadHeaderTimeout: 10 * time.Second, } if err := srv.ListenAndServe(); err != nil { panic(err) } } cosign-2.5.0/test/config/000077500000000000000000000000001477503325500152375ustar00rootroot00000000000000cosign-2.5.0/test/config/gettoken/000077500000000000000000000000001477503325500170575ustar00rootroot00000000000000cosign-2.5.0/test/config/gettoken/gettoken.yaml000066400000000000000000000023111477503325500215600ustar00rootroot00000000000000# Copyright 2022 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. apiVersion: serving.knative.dev/v1 kind: Service metadata: name: gettoken spec: template: spec: containers: - name: gettoken image: ko://github.com/sigstore/cosign/v2/test/cmd/getoidctoken env: - name: OIDC_FILE value: "/var/run/sigstore/cosign/oidc-token" volumeMounts: - name: oidc-info mountPath: /var/run/sigstore/cosign volumes: - name: oidc-info projected: sources: - serviceAccountToken: path: oidc-token expirationSeconds: 600 audience: sigstore cosign-2.5.0/test/e2e_attach_test.go000066400000000000000000000370011477503325500173600ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e && cross package test import ( "bytes" "context" "crypto" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "net/http/httptest" "os" "path" "path/filepath" "strings" "testing" "time" "github.com/go-openapi/strfmt" "github.com/google/go-cmp/cmp" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/sigstore/cosign/v2/cmd/cosign/cli/attach" "github.com/sigstore/cosign/v2/cmd/cosign/cli/download" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" cliverify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" tsaclient "github.com/sigstore/timestamp-authority/pkg/client" "github.com/sigstore/timestamp-authority/pkg/server" "github.com/spf13/viper" ) func TestAttachSignature(t *testing.T) { ctx := context.Background() repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attach-e2e") imgRef, desc, cleanup := mkimage(t, imgName) defer cleanup() // Generate payload b := bytes.Buffer{} must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t) payloadRef := mkfile(b.String(), td, t) hash := sha256.Sum256(b.Bytes()) // Scenario 1: attach a single signature with certificate and certificate chain to an artifact // and verify it using the root certificate. rootCert1, rootKey1, _ := GenerateRootCa() pemRoot1 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert1.Raw}) pemRootRef1 := mkfile(string(pemRoot1), td, t) subCert1, subKey1, _ := GenerateSubordinateCa(rootCert1, rootKey1) leafCert1, privKey1, _ := GenerateLeafCert("foo@example.com", "oidc-issuer", subCert1, subKey1) pemSub1 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert1.Raw}) pemLeaf1 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert1.Raw}) pemLeafRef1 := mkfile(string(pemLeaf1), td, t) certChainRef1 := mkfile(string(append(pemSub1, pemRoot1...)), td, t) signature1, _ := privKey1.Sign(rand.Reader, hash[:], crypto.SHA256) b64signature1 := base64.StdEncoding.EncodeToString(signature1) sigRef1 := mkfile(b64signature1, td, t) err := attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef1, payloadRef, pemLeafRef1, certChainRef1, "", "", imgName) must(err, t) remoteSigRef, err := name.ParseReference(fmt.Sprintf("%s:sha256-%s.sig", imgRef, strings.Split(desc.Digest.String(), ":")[1]), name.WeakValidation) must(err, t) si, err := ociremote.SignedImage(remoteSigRef, ociremote.WithRemoteOptions(registryClientOpts(ctx)...)) must(err, t) manifest, err := si.Manifest() must(err, t) equals(manifest.Config.MediaType, types.MediaType("application/vnd.oci.image.config.v1+json"), t) if len(manifest.Layers) != 1 { t.Fatal("expected exactly one layer") } _, certOk := manifest.Layers[0].Annotations["dev.sigstore.cosign/certificate"] equals(certOk, true, t) _, chainOk := manifest.Layers[0].Annotations["dev.sigstore.cosign/chain"] equals(chainOk, true, t) verifyCmd := cliverify.VerifyCommand{ IgnoreSCT: true, IgnoreTlog: true, CertChain: pemRootRef1, CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuerRegexp: ".*", CertIdentityRegexp: ".*", }, } args := []string{imgName} must(verifyCmd.Exec(ctx, args), t) // Scenario 2: Attaches second signature with another certificate and certificate chain to the // same artifact and verify it using both root certificates separately. rootCert2, rootKey2, _ := GenerateRootCa() pemRoot2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert2.Raw}) pemRootRef2 := mkfile(string(pemRoot2), td, t) subCert2, subKey2, _ := GenerateSubordinateCa(rootCert2, rootKey2) leafCert2, privKey2, _ := GenerateLeafCert("foo@exampleclient.com", "oidc-issuer", subCert2, subKey2) pemSub2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert2.Raw}) pemLeaf2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert2.Raw}) pemLeafRef2 := mkfile(string(pemLeaf2), td, t) certChainRef2 := mkfile(string(append(pemSub2, pemRoot2...)), td, t) signature2, _ := privKey2.Sign(rand.Reader, hash[:], crypto.SHA256) b64signature2 := base64.StdEncoding.EncodeToString(signature2) sigRef2 := mkfile(b64signature2, td, t) err = attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef2, payloadRef, pemLeafRef2, certChainRef2, "", "", imgName) must(err, t) // verify using first root certificate verifyCmd = cliverify.VerifyCommand{ IgnoreSCT: true, IgnoreTlog: true, CertChain: pemRootRef1, CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuerRegexp: ".*", CertIdentityRegexp: ".*", }, } args = []string{imgName} must(verifyCmd.Exec(ctx, args), t) // verify using second root cert verifyCmd = cliverify.VerifyCommand{ IgnoreSCT: true, IgnoreTlog: true, CertChain: pemRootRef2, CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuerRegexp: ".*", CertIdentityRegexp: ".*", }, } args = []string{imgName} must(verifyCmd.Exec(ctx, args), t) } func TestAttachWithRFC3161Timestamp(t *testing.T) { ctx := context.Background() // TSA server needed to create timestamp viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) server := httptest.NewServer(apiServer.GetHandler()) t.Cleanup(server.Close) repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attach-timestamp-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() b := bytes.Buffer{} must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t) rootCert, rootKey, _ := GenerateRootCa() subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) payloadref := mkfile(b.String(), td, t) h := sha256.Sum256(b.Bytes()) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) b64signature := base64.StdEncoding.EncodeToString(signature) sigRef := mkfile(b64signature, td, t) pemleafRef := mkfile(string(pemLeaf), td, t) pemrootRef := mkfile(string(pemRoot), td, t) certchainRef := mkfile(string(append(pemSub, pemRoot...)), td, t) t.Setenv("SIGSTORE_ROOT_FILE", pemrootRef) tsclient, err := tsaclient.GetTimestampClient(server.URL) if err != nil { t.Error(err) } chain, err := tsclient.Timestamp.GetTimestampCertChain(nil) if err != nil { t.Fatalf("unexpected error getting timestamp chain: %v", err) } file, err := os.CreateTemp(os.TempDir(), "tempfile") if err != nil { t.Fatalf("error creating temp file: %v", err) } defer os.Remove(file.Name()) _, err = file.WriteString(chain.Payload) if err != nil { t.Fatalf("error writing chain payload to temp file: %v", err) } tsBytes, err := tsa.GetTimestampedSignature(signature, client.NewTSAClient(server.URL+"/api/v1/timestamp")) if err != nil { t.Fatalf("unexpected error creating timestamp: %v", err) } rfc3161TSRef := mkfile(string(tsBytes), td, t) // Upload it! err = attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadref, pemleafRef, certchainRef, rfc3161TSRef, "", imgName) if err != nil { t.Fatal(err) } must(verifyKeylessTSA(imgName, file.Name(), true, true), t) } func TestAttachWithRekorBundle(t *testing.T) { ctx := context.Background() repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attach-timestamp-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() b := bytes.Buffer{} must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t) rootCert, rootKey, _ := GenerateRootCa() subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) payloadref := mkfile(b.String(), td, t) h := sha256.Sum256(b.Bytes()) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) b64signature := base64.StdEncoding.EncodeToString(signature) sigRef := mkfile(b64signature, td, t) pemleafRef := mkfile(string(pemLeaf), td, t) pemrootRef := mkfile(string(pemRoot), td, t) t.Setenv("SIGSTORE_ROOT_FILE", pemrootRef) certchainRef := mkfile(string(append(pemSub, pemRoot...)), td, t) localPayload := cosign.LocalSignedPayload{ Base64Signature: b64signature, Cert: string(pemLeaf), Bundle: &bundle.RekorBundle{ SignedEntryTimestamp: strfmt.Base64("MEUCIEDcarEwRYkrxE9ne+kzEVvUhnWaauYzxhUyXOLy1hwAAiEA4VdVCvNRs+D/5o33C2KBy+q2YX3lP4Y7nqRFU+K3hi0="), Payload: bundle.RekorPayload{ Body: "REMOVED", IntegratedTime: 1631646761, LogIndex: 693591, LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d", }, }, } jsonBundle, err := json.Marshal(localPayload) if err != nil { t.Fatal(err) } bundlePath := filepath.Join(td, "bundle.json") if err := os.WriteFile(bundlePath, jsonBundle, 0644); err != nil { t.Fatal(err) } // Upload it! err = attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadref, pemleafRef, certchainRef, "", bundlePath, imgName) if err != nil { t.Fatal(err) } } func TestUploadDownload(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() ctx := context.Background() testCases := map[string]struct { signature string signatureType attach.SignatureArgType expectedErr bool }{ "stdin containing signature": { signature: "testsignatureraw", signatureType: attach.StdinSignature, expectedErr: false, }, "file containing signature": { signature: "testsignaturefile", signatureType: attach.FileSignature, expectedErr: false, }, "raw signature as argument": { signature: "testsignatureraw", signatureType: attach.RawSignature, expectedErr: true, }, "empty signature as argument": { signature: "", signatureType: attach.RawSignature, expectedErr: true, }, } imgName := path.Join(repo, "cosign-e2e") for testName, testCase := range testCases { t.Run(testName, func(t *testing.T) { ref, _, cleanup := mkimage(t, imgName) payload := "testpayload" payloadPath := mkfile(payload, td, t) signature := base64.StdEncoding.EncodeToString([]byte(testCase.signature)) restoreStdin := func() {} var sigRef string switch { case testCase.signatureType == attach.FileSignature: sigRef = mkfile(signature, td, t) case testCase.signatureType == attach.StdinSignature: sigRef = mkfile(signature, td, t) default: sigRef = signature } // Upload it! err := attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadPath, "", "", "", "", imgName) if testCase.expectedErr { mustErr(err, t) } else { must(err, t) } restoreStdin() // Now download it! se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...)) must(err, t) sigs, err := se.Signatures() must(err, t) signatures, err := sigs.Get() must(err, t) if testCase.expectedErr { if len(signatures) != 0 { t.Fatalf("unexpected signatures %d, wanted 0", len(signatures)) } } else { if len(signatures) != 1 { t.Fatalf("unexpected signatures %d, wanted 1", len(signatures)) } if b64sig, err := signatures[0].Base64Signature(); err != nil { t.Fatalf("Base64Signature() = %v", err) } else if diff := cmp.Diff(b64sig, signature); diff != "" { t.Error(diff) } if p, err := signatures[0].Payload(); err != nil { t.Fatalf("Payload() = %v", err) } else if diff := cmp.Diff(p, []byte(payload)); diff != "" { t.Error(diff) } } // Now delete it! cleanup() }) } } func TestAttachSBOM_bom_flag(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() ctx := context.Background() bomData, err := os.ReadFile("./testdata/bom-go-mod.spdx") must(err, t) testCases := map[string]struct { bom string bomType attach.SignatureArgType expectedErr bool }{ "stdin containing bom": { bom: string(bomData), bomType: attach.StdinSignature, expectedErr: false, }, "file containing bom": { bom: string(bomData), bomType: attach.FileSignature, expectedErr: false, }, "raw bom as argument": { bom: string(bomData), bomType: attach.RawSignature, expectedErr: true, }, "empty bom as argument": { bom: "", bomType: attach.RawSignature, expectedErr: true, }, } for testName, testCase := range testCases { t.Run(testName, func(t *testing.T) { imgName := path.Join(repo, "sbom-image") img, _, cleanup := mkimage(t, imgName) var sbomRef string restoreStdin := func() {} switch { case testCase.bomType == attach.FileSignature: sbomRef = mkfile(testCase.bom, td, t) case testCase.bomType == attach.StdinSignature: sbomRef = "-" restoreStdin = mockStdin(testCase.bom, td, t) default: sbomRef = testCase.bom } out := bytes.Buffer{} _, errPl := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{Platform: "darwin/amd64"}, img.Name(), &out) if errPl == nil { t.Fatalf("Expected error when passing Platform to single arch image") } _, err := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, img.Name(), &out) if err == nil { t.Fatal("Expected error") } t.Log(out.String()) out.Reset() // Upload it! err = attach.SBOMCmd(ctx, options.RegistryOptions{}, options.RegistryExperimentalOptions{}, sbomRef, "spdx", imgName) restoreStdin() if testCase.expectedErr { mustErr(err, t) } else { sboms, err := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, imgName, &out) if err != nil { t.Fatal(err) } t.Log(out.String()) if len(sboms) != 1 { t.Fatalf("Expected one sbom, got %d", len(sboms)) } want, err := os.ReadFile("./testdata/bom-go-mod.spdx") if err != nil { t.Fatal(err) } if diff := cmp.Diff(string(want), sboms[0]); diff != "" { t.Errorf("diff: %s", diff) } } cleanup() }) } } cosign-2.5.0/test/e2e_insecure_registry_test.go000066400000000000000000000071371477503325500216700ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e && registry package test import ( "context" "crypto/tls" "net/http" "os" "path" "testing" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" cliverify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/sigstore/cosign/v2/pkg/cosign/env" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) const ( oci11Var = "OCI11" rekorURLVar = "REKOR_URL" ) func TestInsecureRegistry(t *testing.T) { if os.Getenv("COSIGN_TEST_REPO") == "" { t.Fatal("COSIGN_TEST_REPO must be set to an insecure registry for this test") } repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-registry-e2e") cleanup := makeImageIndexWithInsecureRegistry(t, imgName) defer cleanup() _, privKey, pubKey := keypair(t, td) useOCI11 := os.Getenv("oci11Var") != "" rekorURL := os.Getenv(rekorURLVar) must(downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td), t) ko := options.KeyOpts{ KeyRef: privKey, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } mustErr(sign.SignCmd(ro, ko, so, []string{imgName}), t) so.Registry = options.RegistryOptions{ AllowInsecure: true, } if useOCI11 { so.RegistryExperimental = options.RegistryExperimentalOptions{ RegistryReferrersMode: options.RegistryReferrersModeOCI11, } } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) mustErr(verify(pubKey, imgName, true, nil, "", false), t) cmd := cliverify.VerifyCommand{ KeyRef: pubKey, CheckClaims: true, RegistryOptions: options.RegistryOptions{ AllowInsecure: true, }, } if useOCI11 { cmd.ExperimentalOCI11 = true } must(cmd.Exec(context.Background(), []string{imgName}), t) } func makeImageIndexWithInsecureRegistry(t *testing.T, n string) func() { ref, err := name.ParseReference(n, name.WeakValidation) if err != nil { t.Fatal(err) } index, err := random.Index(512, 1, 0) if err != nil { t.Fatal(err) } regClientOpts := registryClientOpts(context.Background()) // Add TLS config to allow us to push the image to the insecure registry insecureTransport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } regClientOpts = append(regClientOpts, remote.WithTransport(insecureTransport)) if err := remote.WriteIndex(ref, index, regClientOpts...); err != nil { t.Fatal(err) } remoteImage, err := remote.Get(ref, regClientOpts...) if err != nil { t.Fatal(err) } cleanup := func() { _ = remote.Delete(ref, regClientOpts...) ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteImage.Descriptor.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...)) _ = remote.Delete(ref, regClientOpts...) } return cleanup } cosign-2.5.0/test/e2e_kms_test.go000066400000000000000000000053031477503325500167060ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e && kms package test import ( "context" "os" "path" "testing" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/pkg/cosign/env" _ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault" ) const ( rekorURLVar = "REKOR_URL" testKMSVar = "TEST_KMS" defaultKMS = "hashivault://transit" ) func TestSecretsKMS(t *testing.T) { ctx := context.Background() repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-kms-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() kms := os.Getenv(testKMSVar) if kms == "" { kms = defaultKMS } prefix := path.Join(td, "test-kms") must(generate.GenerateKeyPairCmd(ctx, kms, prefix, nil), t) pubKey := prefix + ".pub" privKey := kms // Verify should fail at first mustErr(verify(pubKey, imgName, true, nil, "", false), t) rekorURL := os.Getenv(rekorURLVar) must(downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td), t) // Now sign and verify with the KMS key ko := options.KeyOpts{ KeyRef: privKey, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) must(verify(pubKey, imgName, true, nil, "", false), t) // Sign and verify with annotations mustErr(verify(pubKey, imgName, true, map[string]any{"foo": "bar"}, "", false), t) soAnno := options.SignOptions{ Upload: true, TlogUpload: true, AnnotationOptions: options.AnnotationOptions{ Annotations: []string{"foo=bar"}, }, } must(sign.SignCmd(ro, ko, soAnno, []string{imgName}), t) must(verify(pubKey, imgName, true, map[string]any{"foo": "bar"}, "", false), t) // Store signatures in a different repo t.Setenv("COSIGN_REPOSITORY", path.Join(repo, "subbedrepo")) must(sign.SignCmd(ro, ko, so, []string{imgName}), t) must(verify(pubKey, imgName, true, nil, "", false), t) os.Unsetenv("COSIGN_REPOSITORY") } cosign-2.5.0/test/e2e_test.go000066400000000000000000002514521477503325500160440ustar00rootroot00000000000000// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e && !cross && !kms && !registry package test import ( "bytes" "context" "crypto" "crypto/ed25519" "crypto/rand" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "io" "net/http" "net/http/httptest" "net/url" "os" "path" "path/filepath" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/theupdateframework/go-tuf/v2/metadata" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8s "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" // Initialize all known client auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth" "github.com/sigstore/cosign/v2/cmd/cosign/cli" "github.com/sigstore/cosign/v2/cmd/cosign/cli/attach" "github.com/sigstore/cosign/v2/cmd/cosign/cli/attest" "github.com/sigstore/cosign/v2/cmd/cosign/cli/dockerfile" "github.com/sigstore/cosign/v2/cmd/cosign/cli/download" "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v2/cmd/cosign/cli/initialize" "github.com/sigstore/cosign/v2/cmd/cosign/cli/manifest" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/publickey" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" cliverify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/sigstore/cosign/v2/internal/pkg/cosign/fulcio/fulcioroots" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/bundle" "github.com/sigstore/cosign/v2/pkg/cosign/env" "github.com/sigstore/cosign/v2/pkg/cosign/kubernetes" "github.com/sigstore/cosign/v2/pkg/oci/mutate" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/payload" tsaclient "github.com/sigstore/timestamp-authority/pkg/client" "github.com/sigstore/timestamp-authority/pkg/server" "github.com/spf13/viper" ) func TestSignVerify(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() // Verify should fail at first mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) // So should download mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) // Now sign the image ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Now verify and download should work! must(verify(pubKeyPath, imgName, true, nil, "", false), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) // Look for a specific annotation mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) so.AnnotationOptions = options.AnnotationOptions{ Annotations: []string{"foo=bar"}, } // Sign the image with an annotation must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // It should match this time. must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) // But two doesn't work mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, "", false), t) } func TestSignVerifyCertBundle(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) caCertFile, _ /* caPrivKeyFile */, caIntermediateCertFile, _ /* caIntermediatePrivKeyFile */, certFile, certChainFile, err := generateCertificateBundleFiles(td, true, "foobar") must(err, t) ctx := context.Background() // Verify should fail at first mustErr(verifyCertBundle(pubKeyPath, caCertFile, caIntermediateCertFile, imgName, true, nil, "", true), t) // So should download mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) // Now sign the image ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Now verify and download should work! ignoreTlog := true must(verifyCertBundle(pubKeyPath, caCertFile, caIntermediateCertFile, imgName, true, nil, "", ignoreTlog), t) // verification with certificate chain instead of root/intermediate files should work as well must(verifyCertChain(pubKeyPath, certChainFile, certFile, imgName, true, nil, "", ignoreTlog), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) // Look for a specific annotation mustErr(verifyCertBundle(pubKeyPath, caCertFile, caIntermediateCertFile, imgName, true, map[string]interface{}{"foo": "bar"}, "", ignoreTlog), t) so.AnnotationOptions = options.AnnotationOptions{ Annotations: []string{"foo=bar"}, } // Sign the image with an annotation must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // It should match this time. must(verifyCertBundle(pubKeyPath, caCertFile, caIntermediateCertFile, imgName, true, map[string]interface{}{"foo": "bar"}, "", ignoreTlog), t) // But two doesn't work mustErr(verifyCertBundle(pubKeyPath, caCertFile, caIntermediateCertFile, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, "", ignoreTlog), t) } func TestSignVerifyClean(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") _, _, _ = mkimage(t, imgName) _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() // Now sign the image ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Now verify and download should work! must(verify(pubKeyPath, imgName, true, nil, "", false), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) // Now clean signature from the given image must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t) // It doesn't work mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) } func TestImportSignVerifyClean(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") _, _, _ = mkimage(t, imgName) _, privKeyPath, pubKeyPath := importSampleKeyPair(t, td) ctx := context.Background() // Now sign the image ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Now verify and download should work! must(verify(pubKeyPath, imgName, true, nil, "", false), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) // Now clean signature from the given image must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t) // It doesn't work mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) } type targetInfo struct { name string source string usage string } func downloadTargets(td string, targets []targetInfo, targetsMeta *metadata.Metadata[metadata.TargetsType]) error { targetsDir := filepath.Join(td, "targets") err := os.RemoveAll(targetsDir) if err != nil { return err } err = os.Mkdir(targetsDir, 0700) if err != nil { return err } targetsMeta.Signed.Targets = make(map[string]*metadata.TargetFiles) for _, target := range targets { targetLocalPath := filepath.Join(targetsDir, target.name) if strings.HasPrefix(target.source, "http") { fp, err := os.Create(targetLocalPath) if err != nil { return err } defer fp.Close() err = downloadFile(target.source, fp) if err != nil { return err } } if strings.HasPrefix(target.source, "/") { err = copyFile(target.source, targetLocalPath) if err != nil { return err } } targetFileInfo, err := metadata.TargetFile().FromFile(targetLocalPath, "sha256") if err != nil { return err } if target.usage != "" { customMsg := fmt.Sprintf(`{"sigstore":{"usage": "%s"}}`, target.usage) custom := json.RawMessage([]byte(customMsg)) targetFileInfo.Custom = &custom } targetsMeta.Signed.Targets[target.name] = targetFileInfo } return nil } type tuf struct { publicKey *metadata.Key signer signature.Signer root *metadata.Metadata[metadata.RootType] snapshot *metadata.Metadata[metadata.SnapshotType] timestamp *metadata.Metadata[metadata.TimestampType] targets *metadata.Metadata[metadata.TargetsType] } func newKey() (*metadata.Key, signature.Signer, error) { pub, private, err := ed25519.GenerateKey(nil) if err != nil { return nil, nil, err } public, err := metadata.KeyFromPublicKey(pub) if err != nil { return nil, nil, err } signer, err := signature.LoadSigner(private, crypto.Hash(0)) if err != nil { return nil, nil, err } return public, signer, nil } func newTUF(td string, targetList []targetInfo) (*tuf, error) { // source: https://github.com/theupdateframework/go-tuf/blob/v2.0.2/examples/repository/basic_repository.go expiration := time.Now().AddDate(0, 0, 1).UTC() targets := metadata.Targets(expiration) err := downloadTargets(td, targetList, targets) if err != nil { return nil, err } snapshot := metadata.Snapshot(expiration) timestamp := metadata.Timestamp(expiration) root := metadata.Root(expiration) root.Signed.ConsistentSnapshot = false public, signer, err := newKey() if err != nil { return nil, err } tuf := &tuf{ publicKey: public, signer: signer, root: root, snapshot: snapshot, timestamp: timestamp, targets: targets, } for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { err := tuf.root.Signed.AddKey(tuf.publicKey, name) if err != nil { return nil, err } switch name { case "targets": _, err = tuf.targets.Sign(tuf.signer) case "snapshot": _, err = tuf.snapshot.Sign(tuf.signer) case "timestamp": _, err = tuf.timestamp.Sign(tuf.signer) case "root": _, err = tuf.root.Sign(tuf.signer) } if err != nil { return nil, err } } err = tuf.targets.ToFile(filepath.Join(td, "targets.json"), false) if err != nil { return nil, err } err = tuf.snapshot.ToFile(filepath.Join(td, "snapshot.json"), false) if err != nil { return nil, err } err = tuf.timestamp.ToFile(filepath.Join(td, "timestamp.json"), false) if err != nil { return nil, err } err = tuf.root.ToFile(filepath.Join(td, fmt.Sprintf("%d.%s.json", tuf.root.Signed.Version, "root")), false) if err != nil { return nil, err } err = tuf.root.VerifyDelegate("root", tuf.root) if err != nil { return nil, err } err = tuf.root.VerifyDelegate("targets", tuf.targets) if err != nil { return nil, err } err = tuf.root.VerifyDelegate("snapshot", tuf.snapshot) if err != nil { return nil, err } err = tuf.root.VerifyDelegate("timestamp", tuf.timestamp) if err != nil { return nil, err } return tuf, nil } func (tr *tuf) update(td string, targetList []targetInfo) error { err := downloadTargets(td, targetList, tr.targets) if err != nil { return err } tr.targets.Signatures = make([]metadata.Signature, 0) tr.targets.Signed.Version++ _, err = tr.targets.Sign(tr.signer) if err != nil { return err } tr.snapshot.Signatures = make([]metadata.Signature, 0) tr.snapshot.Signed.Meta["targets.json"].Version++ tr.snapshot.Signed.Version++ tr.snapshot.Sign(tr.signer) tr.timestamp.Signatures = make([]metadata.Signature, 0) tr.timestamp.Signed.Meta["snapshot.json"].Version++ tr.timestamp.Signed.Version++ tr.timestamp.Sign(tr.signer) err = tr.targets.ToFile(filepath.Join(td, "targets.json"), false) if err != nil { return err } err = tr.snapshot.ToFile(filepath.Join(td, "snapshot.json"), false) if err != nil { return err } err = tr.timestamp.ToFile(filepath.Join(td, "timestamp.json"), false) if err != nil { return err } return nil } func downloadTSACerts(downloadDirectory string, tsaServer string) (string, string, string, error) { resp, err := http.Get(tsaServer + "/api/v1/timestamp/certchain") if err != nil { return "", "", "", err } defer resp.Body.Close() buffer := new(bytes.Buffer) buffer.ReadFrom(resp.Body) b := buffer.Bytes() certs, err := cryptoutils.UnmarshalCertificatesFromPEM(b) if err != nil { return "", "", "", err } leaves := make([]*x509.Certificate, 0) intermediates := make([]*x509.Certificate, 0) roots := make([]*x509.Certificate, 0) for _, cert := range certs { if !cert.IsCA { leaves = append(leaves, cert) } else { // root certificates are self-signed if bytes.Equal(cert.RawSubject, cert.RawIssuer) { roots = append(roots, cert) } else { intermediates = append(intermediates, cert) } } } if len(leaves) != 1 { return "", "", "", fmt.Errorf("unexpected number of certificate leaves") } if len(roots) != 1 { return "", "", "", fmt.Errorf("unexpected number of certificate roots") } leafPath := filepath.Join(downloadDirectory, "tsa_leaf.crt.pem") leafFP, err := os.Create(leafPath) if err != nil { return "", "", "", err } defer leafFP.Close() err = pem.Encode(leafFP, &pem.Block{ Type: "CERTIFICATE", Bytes: leaves[0].Raw, }) if err != nil { return "", "", "", err } rootPath := filepath.Join(downloadDirectory, "tsa_root.crt.pem") rootFP, err := os.Create(rootPath) if err != nil { return "", "", "", err } defer rootFP.Close() err = pem.Encode(rootFP, &pem.Block{ Type: "CERTIFICATE", Bytes: roots[0].Raw, }) if err != nil { return "", "", "", err } intermediatePath := filepath.Join(downloadDirectory, "tsa_intermediate_0.crt.pem") intermediateFP, err := os.Create(intermediatePath) if err != nil { return "", "", "", err } defer intermediateFP.Close() intermediateBuffer := new(bytes.Buffer) for _, i := range intermediates { _, err = intermediateBuffer.Write(i.Raw) if err != nil { return "", "", "", err } } err = pem.Encode(intermediateFP, &pem.Block{ Type: "CERTIFICATE", Bytes: intermediateBuffer.Bytes(), }) if err != nil { return "", "", "", err } return leafPath, intermediatePath, rootPath, nil } func TestSignVerifyWithTUFMirror(t *testing.T) { home, err := os.UserHomeDir() // fulcio repo was downloaded to $HOME in e2e_test.sh must(err, t) tufLocalCache := t.TempDir() t.Setenv("TUF_ROOT", tufLocalCache) tufMirror := t.TempDir() viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") tsaAPIServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) tsaServer := httptest.NewServer(tsaAPIServer.GetHandler()) t.Cleanup(tsaServer.Close) tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) })) mirror := tufServer.URL tsaLeaf, tsaInter, tsaRoot, err := downloadTSACerts(t.TempDir(), tsaServer.URL) must(err, t) tests := []struct { name string targets []targetInfo wantSignErr bool wantVerifyErr bool }{ { name: "invalid CT key name with no usage", targets: []targetInfo{ { name: "ct.pub", source: filepath.Join(home, "fulcio", "config", "ctfe", "pubkey.pem"), }, }, wantSignErr: true, }, { name: "standard key names", targets: []targetInfo{ { name: "rekor.pub", source: rekorURL + "/api/v1/log/publicKey", }, { name: "fulcio.crt.pem", source: fulcioURL + "/api/v1/rootCert", }, { name: "ctfe.pub", source: filepath.Join(home, "fulcio", "config", "ctfe", "pubkey.pem"), }, { name: "tsa_leaf.crt.pem", source: tsaLeaf, }, { name: "tsa_root.crt.pem", source: tsaRoot, }, { name: "tsa_intermediate_0.crt.pem", source: tsaInter, }, }, }, { name: "invalid verifier key names with no usage", targets: []targetInfo{ { name: "tlog.pubkey", source: rekorURL + "/api/v1/log/publicKey", }, { name: "ca.cert", source: fulcioURL + "/api/v1/rootCert", }, { name: "ctfe.pub", source: filepath.Join(home, "fulcio", "config", "ctfe", "pubkey.pem"), }, { name: "tsaleaf.pem", source: tsaLeaf, }, { name: "tsaca.pem", source: tsaRoot, }, { name: "tsachain.pem", source: tsaInter, }, }, wantVerifyErr: true, }, { name: "nonstandard key names with valid usage", targets: []targetInfo{ { name: "tlog.pubkey", usage: "Rekor", source: rekorURL + "/api/v1/log/publicKey", }, { name: "ca.cert", usage: "Fulcio", source: fulcioURL + "/api/v1/rootCert", }, { name: "intermediate.cert", usage: "Fulcio", source: fulcioURL + "/api/v1/rootCert", }, { name: "cert-transparency.pem", usage: "CTFE", source: filepath.Join(home, "fulcio", "config", "ctfe", "pubkey.pem"), }, { name: "tsaleaf.pem", source: tsaLeaf, usage: "TSA", }, { name: "tsaca.pem", source: tsaRoot, usage: "TSA", }, { name: "tsachain.pem", source: tsaInter, usage: "TSA", }, }, }, } tuf, err := newTUF(tufMirror, tests[0].targets) must(err, t) for i, test := range tests { t.Run(test.name, func(t *testing.T) { ctx := context.Background() if i > 0 { must(tuf.update(tufMirror, test.targets), t) } rootPath := filepath.Join(tufMirror, "1.root.json") must(initialize.DoInitialize(ctx, rootPath, mirror), t) identityToken, err := getOIDCToken() if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e-tuf") _, _, cleanup := mkimage(t, imgName) defer cleanup() ko := options.KeyOpts{ FulcioURL: fulcioURL, RekorURL: rekorURL, IDToken: identityToken, SkipConfirmation: true, TSAServerURL: tsaServer.URL + "/api/v1/timestamp", } so := options.SignOptions{ Upload: true, TlogUpload: true, SkipConfirmation: true, } gotErr := sign.SignCmd(ro, ko, so, []string{imgName}) if test.wantSignErr { mustErr(gotErr, t) return } must(gotErr, t) issuer := os.Getenv("OIDC_URL") verifyCmd := cliverify.VerifyCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: certID, }, Offline: true, CheckClaims: true, UseSignedTimestamps: true, } gotErr = verifyCmd.Exec(ctx, []string{imgName}) if test.wantVerifyErr { mustErr(gotErr, t) } else { must(gotErr, t) } }) } } func TestAttestVerify(t *testing.T) { for _, newBundleFormat := range []bool{false, true} { attestVerify(t, newBundleFormat, "slsaprovenance", `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`, `predicate: builder: id: "2"`, `predicate: builder: id: "1"`, ) } } func TestAttestVerifySPDXJSON(t *testing.T) { attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.spdx.json") if err != nil { t.Fatal(err) } for _, newBundleFormat := range []bool{false, true} { attestVerify(t, newBundleFormat, "spdxjson", string(attestationBytes), `predicate: spdxVersion: "SPDX-2.2"`, `predicate: spdxVersion: "SPDX-9.9"`, ) } } func TestAttestVerifyCycloneDXJSON(t *testing.T) { attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.cyclonedx.json") if err != nil { t.Fatal(err) } for _, newBundleFormat := range []bool{false, true} { attestVerify(t, newBundleFormat, "cyclonedx", string(attestationBytes), `predicate: specVersion: "1.4"`, `predicate: specVersion: "7.7"`, ) } } func TestAttestVerifyURI(t *testing.T) { attestationBytes, err := os.ReadFile("./testdata/test-result.json") if err != nil { t.Fatal(err) } for _, newBundleFormat := range []bool{false, true} { attestVerify(t, newBundleFormat, "https://example.com/TestResult/v1", string(attestationBytes), `predicate: passed: true`, `predicate: passed: false"`, ) } } func attestVerify(t *testing.T, newBundleFormat bool, predicateType, attestation, goodCue, badCue string) { repo, stop := reg(t) defer stop() td := t.TempDir() var imgName, attestationPath string if _, err := url.ParseRequestURI(predicateType); err == nil { // If the predicate type is URI, it cannot be included as image name and path. imgName = path.Join(repo, "cosign-attest-uri-e2e-image") attestationPath = filepath.Join(td, "cosign-attest-uri-e2e-attestation") } else { imgName = path.Join(repo, fmt.Sprintf("cosign-attest-%s-e2e-image", predicateType)) attestationPath = filepath.Join(td, fmt.Sprintf("cosign-attest-%s-e2e-attestation", predicateType)) } _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() // Verify should fail at first verifyAttestation := cliverify.VerifyAttestationCommand{ KeyRef: pubKeyPath, IgnoreTlog: true, MaxWorkers: 10, } if newBundleFormat { verifyAttestation.NewBundleFormat = true } // Fail case when using without type and policy flag mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) if err := os.WriteFile(attestationPath, []byte(attestation), 0600); err != nil { t.Fatal(err) } // Now attest the image ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, NewBundleFormat: newBundleFormat} attestCmd := attest.AttestCommand{ KeyOpts: ko, PredicatePath: attestationPath, PredicateType: predicateType, Timeout: 30 * time.Second, RekorEntryType: "dsse", } must(attestCmd.Exec(ctx, imgName), t) // Use cue to verify attestation policyPath := filepath.Join(td, "policy.cue") verifyAttestation.PredicateType = predicateType verifyAttestation.Policies = []string{policyPath} // Fail case if err := os.WriteFile(policyPath, []byte(badCue), 0600); err != nil { t.Fatal(err) } mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) // Success case if err := os.WriteFile(policyPath, []byte(goodCue), 0600); err != nil { t.Fatal(err) } must(verifyAttestation.Exec(ctx, []string{imgName}), t) // Look for a specific annotation mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) } func TestAttestationDownload(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attest-download-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, _ := keypair(t, td) ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} ctx := context.Background() slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { t.Fatal(err) } vulnAttestation := ` { "invocation": { "parameters": null, "uri": "invocation.example.com/cosign-testing", "event_id": "", "builder.id": "" }, "scanner": { "uri": "fakescanner.example.com/cosign-testing", "version": "", "db": { "uri": "", "version": "" }, "result": null }, "metadata": { "scanStartedOn": "2022-04-12T00:00:00Z", "scanFinishedOn": "2022-04-12T00:10:00Z" } } ` vulnAttestationPath := filepath.Join(td, "attestation.vuln.json") if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil { t.Fatal(err) } ref, err := name.ParseReference(imgName) if err != nil { t.Fatal(err) } regOpts := options.RegistryOptions{} ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { t.Fatal(err) } // Attest to create a slsa attestation attestCommand := attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, Replace: true, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Attest to create a vuln attestation attestCommand = attest.AttestCommand{ KeyOpts: ko, PredicatePath: vulnAttestationPath, PredicateType: "vuln", Timeout: 30 * time.Second, Replace: true, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Call download.AttestationCmd() to ensure success attOpts := options.AttestationDownloadOptions{} must(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t) attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) if err != nil { t.Fatal(err) } if len(attestations) != 2 { t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations))) } } func TestAttestationDownloadWithPredicateType(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attest-download-predicate-type-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, _ := keypair(t, td) ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} ctx := context.Background() slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { t.Fatal(err) } vulnAttestation := ` { "invocation": { "parameters": null, "uri": "invocation.example.com/cosign-testing", "event_id": "", "builder.id": "" }, "scanner": { "uri": "fakescanner.example.com/cosign-testing", "version": "", "db": { "uri": "", "version": "" }, "result": null }, "metadata": { "scanStartedOn": "2022-04-12T00:00:00Z", "scanFinishedOn": "2022-04-12T00:10:00Z" } } ` vulnAttestationPath := filepath.Join(td, "attestation.vuln.json") if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil { t.Fatal(err) } ref, err := name.ParseReference(imgName) if err != nil { t.Fatal(err) } regOpts := options.RegistryOptions{} ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { t.Fatal(err) } // Attest to create a slsa attestation attestCommand := attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, Replace: true, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Attest to create a vuln attestation attestCommand = attest.AttestCommand{ KeyOpts: ko, PredicatePath: vulnAttestationPath, PredicateType: "vuln", Timeout: 30 * time.Second, Replace: true, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Call download.AttestationCmd() to ensure success with --predicate-type attOpts := options.AttestationDownloadOptions{ PredicateType: "vuln", } must(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t) predicateType, _ := options.ParsePredicateType(attOpts.PredicateType) attestations, err := cosign.FetchAttestationsForReference(ctx, ref, predicateType, ociremoteOpts...) if err != nil { t.Fatal(err) } if len(attestations) != 1 { t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) } } func TestAttestationDownloadWithBadPredicateType(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attest-download-bad-type-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, _ := keypair(t, td) ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} ctx := context.Background() slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { t.Fatal(err) } regOpts := options.RegistryOptions{} // Attest to create a slsa attestation attestCommand := attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, Replace: true, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Call download.AttestationCmd() to ensure failure with non-existent --predicate-type attOpts := options.AttestationDownloadOptions{ PredicateType: "vuln", } mustErr(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t) } func TestAttestationReplaceCreate(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attest-replace-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, _ := keypair(t, td) ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} ctx := context.Background() slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { t.Fatal(err) } ref, err := name.ParseReference(imgName) if err != nil { t.Fatal(err) } regOpts := options.RegistryOptions{} ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { t.Fatal(err) } // Attest with replace=true to create an attestation attestCommand := attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, Replace: true, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Download and count the attestations attOpts := options.AttestationDownloadOptions{} attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) if err != nil { t.Fatal(err) } if len(attestations) != 1 { t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) } } func TestAttestationReplace(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attest-replace-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, _ := keypair(t, td) ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} ctx := context.Background() slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { t.Fatal(err) } ref, err := name.ParseReference(imgName) if err != nil { t.Fatal(err) } regOpts := options.RegistryOptions{} ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { t.Fatal(err) } // Attest once with replace=false creating an attestation attestCommand := attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Download and count the attestations attOpts := options.AttestationDownloadOptions{} attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) if err != nil { t.Fatal(err) } if len(attestations) != 1 { t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) } // Attest again with replace=true, replacing the previous attestation attestCommand = attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Replace: true, Timeout: 30 * time.Second, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) // Download and count the attestations if err != nil { t.Fatal(err) } if len(attestations) != 1 { t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) } // Attest once more replace=true using a different predicate, to ensure it adds a new attestation attestCommand = attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "custom", Replace: true, Timeout: 30 * time.Second, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Download and count the attestations attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) if err != nil { t.Fatal(err) } if len(attestations) != 2 { t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations))) } } func TestAttestationRFC3161Timestamp(t *testing.T) { // TSA server needed to create timestamp viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) server := httptest.NewServer(apiServer.GetHandler()) t.Cleanup(server.Close) repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-attest-timestamp-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} ctx := context.Background() slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { t.Fatal(err) } ref, err := name.ParseReference(imgName) if err != nil { t.Fatal(err) } regOpts := options.RegistryOptions{} ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { t.Fatal(err) } // Attest with TSA and skipping tlog creating an attestation attestCommand := attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, TSAServerURL: server.URL + "/api/v1/timestamp", TlogUpload: false, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // Download and count the attestations attOpts := options.AttestationDownloadOptions{} attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) if err != nil { t.Fatal(err) } if len(attestations) != 1 { t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) } client, err := tsaclient.GetTimestampClient(server.URL) if err != nil { t.Error(err) } chain, err := client.Timestamp.GetTimestampCertChain(nil) if err != nil { t.Fatalf("unexpected error getting timestamp chain: %v", err) } file, err := os.CreateTemp(os.TempDir(), "tempfile") if err != nil { t.Fatalf("error creating temp file: %v", err) } defer os.Remove(file.Name()) _, err = file.WriteString(chain.Payload) if err != nil { t.Fatalf("error writing chain payload to temp file: %v", err) } verifyAttestation := cliverify.VerifyAttestationCommand{ KeyRef: pubKeyPath, TSACertChainPath: file.Name(), IgnoreTlog: true, PredicateType: "slsaprovenance", MaxWorkers: 10, } must(verifyAttestation.Exec(ctx, []string{imgName}), t) } func TestAttestationBlobRFC3161Timestamp(t *testing.T) { // TSA server needed to create timestamp viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) server := httptest.NewServer(apiServer.GetHandler()) t.Cleanup(server.Close) blob := "someblob" predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` predicateType := "slsaprovenance" td := t.TempDir() t.Cleanup(func() { os.RemoveAll(td) }) bp := filepath.Join(td, blob) if err := os.WriteFile(bp, []byte(blob), 0600); err != nil { t.Fatal(err) } predicatePath := filepath.Join(td, "predicate") if err := os.WriteFile(predicatePath, []byte(predicate), 0600); err != nil { t.Fatal(err) } bundlePath := filepath.Join(td, "bundle.sigstore.json") _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() ko := options.KeyOpts{ KeyRef: privKeyPath, BundlePath: bundlePath, NewBundleFormat: true, TSAServerURL: server.URL + "/api/v1/timestamp", PassFunc: passFunc, } attestBlobCmd := attest.AttestBlobCommand{ KeyOpts: ko, PredicatePath: predicatePath, PredicateType: predicateType, Timeout: 30 * time.Second, TlogUpload: false, RekorEntryType: "dsse", } must(attestBlobCmd.Exec(ctx, bp), t) client, err := tsaclient.GetTimestampClient(server.URL) if err != nil { t.Error(err) } chain, err := client.Timestamp.GetTimestampCertChain(nil) if err != nil { t.Fatalf("unexpected error getting timestamp chain: %v", err) } var certs []*x509.Certificate for block, contents := pem.Decode([]byte(chain.Payload)); ; block, contents = pem.Decode(contents) { cert, err := x509.ParseCertificate(block.Bytes) if err != nil { t.Error(err) } certs = append(certs, cert) if len(contents) == 0 { break } } tsaCA := &root.SigstoreTimestampingAuthority{ Root: certs[len(certs)-1], Intermediates: certs[:len(certs)-1], } trustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, nil, nil, []root.TimestampingAuthority{tsaCA}, nil) if err != nil { t.Error(err) } trustedRootPath := filepath.Join(td, "trustedroot.json") trustedRootBytes, err := trustedRoot.MarshalJSON() if err != nil { t.Error(err) } if err := os.WriteFile(trustedRootPath, trustedRootBytes, 0600); err != nil { t.Fatal(err) } ko = options.KeyOpts{ KeyRef: pubKeyPath, BundlePath: bundlePath, NewBundleFormat: true, } verifyBlobAttestation := cliverify.VerifyBlobAttestationCommand{ KeyOpts: ko, PredicateType: predicateType, IgnoreTlog: true, CheckClaims: true, TrustedRootPath: trustedRootPath, } must(verifyBlobAttestation.Exec(ctx, bp), t) } func TestVerifyWithCARoots(t *testing.T) { ctx := context.Background() // TSA server needed to create timestamp viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) server := httptest.NewServer(apiServer.GetHandler()) t.Cleanup(server.Close) repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-verify-caroots-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() blob := "someblob2sign" b := bytes.Buffer{} blobRef := filepath.Join(td, blob) if err := os.WriteFile(blobRef, []byte(blob), 0644); err != nil { t.Fatal(err) } must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t) rootCert, rootKey, _ := GenerateRootCa() subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey) leafCert, privKey, _ := GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey) privKeyRef := importECDSAPrivateKey(t, privKey, td, "cosign-test-key.pem") pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw}) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) rootCert02, rootKey02, _ := GenerateRootCa() subCert02, subKey02, _ := GenerateSubordinateCa(rootCert02, rootKey02) leafCert02, _, _ := GenerateLeafCert("subject02@mail.com", "oidc-issuer02", subCert02, subKey02) pemRoot02 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert02.Raw}) pemSub02 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert02.Raw}) pemLeaf02 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert02.Raw}) pemsubRef02 := mkfile(string(pemSub02), td, t) pemrootRef02 := mkfile(string(pemRoot02), td, t) pemleafRef02 := mkfile(string(pemLeaf02), td, t) rootPool := x509.NewCertPool() rootPool.AddCert(rootCert) payloadref := mkfile(b.String(), td, t) h := sha256.Sum256(b.Bytes()) signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) b64signature := base64.StdEncoding.EncodeToString(signature) sigRef := mkfile(b64signature, td, t) pemsubRef := mkfile(string(pemSub), td, t) pemrootRef := mkfile(string(pemRoot), td, t) pemleafRef := mkfile(string(pemLeaf), td, t) certchainRef := mkfile(string(append(pemSub, pemRoot...)), td, t) pemrootBundleRef := mkfile(string(append(pemRoot, pemRoot02...)), td, t) pemsubBundleRef := mkfile(string(append(pemSub, pemSub02...)), td, t) tsclient, err := tsaclient.GetTimestampClient(server.URL) if err != nil { t.Error(err) } chain, err := tsclient.Timestamp.GetTimestampCertChain(nil) if err != nil { t.Fatalf("unexpected error getting timestamp chain: %v", err) } tsaChainRef, err := os.CreateTemp(os.TempDir(), "tempfile") if err != nil { t.Fatalf("error creating temp file: %v", err) } defer os.Remove(tsaChainRef.Name()) _, err = tsaChainRef.WriteString(chain.Payload) if err != nil { t.Fatalf("error writing chain payload to temp file: %v", err) } tsBytes, err := tsa.GetTimestampedSignature(signature, client.NewTSAClient(server.URL+"/api/v1/timestamp")) if err != nil { t.Fatalf("unexpected error creating timestamp: %v", err) } rfc3161TSRef := mkfile(string(tsBytes), td, t) // Upload it! err = attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadref, pemleafRef, certchainRef, rfc3161TSRef, "", imgName) if err != nil { t.Fatal(err) } // Now sign the blob with one key ko := options.KeyOpts{ KeyRef: privKeyRef, PassFunc: passFunc, } blobSig, err := sign.SignBlobCmd(ro, ko, blobRef, true, "", "", false) if err != nil { t.Fatal(err) } // the following fields with non-changing values are logically "factored out" for brevity // and passed to verifyKeylessTSAWithCARoots in the testing loop: // imageName string // tsaCertChainRef string // skipSCT bool // skipTlogVerify bool tests := []struct { name string rootRef string subRef string leafRef string skipBlob bool // skip the verify-blob test (for cases that need the image) wantError bool }{ { "verify with root, intermediate and leaf certificates", pemrootRef, pemsubRef, pemleafRef, false, false, }, // NB - "confusely" switching the root and intermediate PEM files does _NOT_ (currently) produce an error // - the Go crypto/x509 package doesn't strictly verify that the certificate chain is anchored // in a self-signed root certificate. In this case, only the chain up to the intermediate // certificate is verified, and the root certificate is ignored. // See also https://gist.github.com/dmitris/15160f703b3038b1b00d03d3c7b66ce0 and in particular // https://gist.github.com/dmitris/15160f703b3038b1b00d03d3c7b66ce0#file-main-go-L133-L135 as an example. { "switch root and intermediate no error", pemsubRef, pemrootRef, pemleafRef, false, false, }, { "leave out the root certificate", "", pemsubRef, pemleafRef, false, true, }, { "leave out the intermediate certificate", pemrootRef, "", pemleafRef, false, true, }, { "leave out the codesigning leaf certificate which is extracted from the image", pemrootRef, pemsubRef, "", true, false, }, { "wrong leaf certificate", pemrootRef, pemsubRef, pemleafRef02, false, true, }, { "root and intermediates bundles", pemrootBundleRef, pemsubBundleRef, pemleafRef, false, false, }, { "wrong root and intermediates bundles", pemrootRef02, pemsubRef02, pemleafRef, false, true, }, { "wrong root bundle", pemrootRef02, pemsubBundleRef, pemleafRef, false, true, }, { "wrong intermediates bundle", pemrootRef, pemsubRef02, pemleafRef, false, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := verifyKeylessTSAWithCARoots(imgName, tt.rootRef, tt.subRef, tt.leafRef, tsaChainRef.Name(), true, true) hasErr := (err != nil) if hasErr != tt.wantError { if tt.wantError { t.Errorf("%s - no expected error", tt.name) } else { t.Errorf("%s - unexpected error: %v", tt.name, err) } } if !tt.skipBlob { err = verifyBlobKeylessWithCARoots(blobRef, string(blobSig), tt.rootRef, tt.subRef, tt.leafRef, true, true) hasErr = (err != nil) if hasErr != tt.wantError { if tt.wantError { t.Errorf("%s - no expected error", tt.name) } else { t.Errorf("%s - unexpected error: %v", tt.name, err) } } } }) } } func TestRekorBundle(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } // Sign the image must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Make sure verify works must(verify(pubKeyPath, imgName, true, nil, "", false), t) // Make sure offline verification works with bundling must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t) } func TestRekorOutput(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") bundlePath := filepath.Join(td, "bundle.sig") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, BundlePath: bundlePath, } so := options.SignOptions{ Upload: true, TlogUpload: true, } // Sign the image must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Make sure verify works must(verify(pubKeyPath, imgName, true, nil, "", false), t) if file, err := os.ReadFile(bundlePath); err != nil { t.Fatal(err) } else { var localCosignPayload cosign.LocalSignedPayload if err := json.Unmarshal(file, &localCosignPayload); err != nil { t.Fatal(err) } } // Make sure offline verification works with bundling must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t) } func TestFulcioBundle(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, FulcioURL: fulcioURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, IssueCertificate: true, } // Sign the image must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Make sure verify works must(verify(pubKeyPath, imgName, true, nil, "", false), t) // Make sure offline verification works with bundling // use rekor prod since we have hardcoded the public key must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t) } func TestRFC3161Timestamp(t *testing.T) { // TSA server needed to create timestamp viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) server := httptest.NewServer(apiServer.GetHandler()) t.Cleanup(server.Close) client, err := tsaclient.GetTimestampClient(server.URL) if err != nil { t.Error(err) } chain, err := client.Timestamp.GetTimestampCertChain(nil) if err != nil { t.Fatalf("unexpected error getting timestamp chain: %v", err) } file, err := os.CreateTemp(os.TempDir(), "tempfile") if err != nil { t.Fatalf("error creating temp file: %v", err) } defer os.Remove(file.Name()) _, err = file.WriteString(chain.Payload) if err != nil { t.Fatalf("error writing chain payload to temp file: %v", err) } repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, TSAServerURL: server.URL + "/api/v1/timestamp", } so := options.SignOptions{ Upload: true, TlogUpload: false, } // Sign the image must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Make sure verify works against the TSA server must(verifyTSA(pubKeyPath, imgName, true, nil, "", file.Name(), true), t) } func TestRekorBundleAndRFC3161Timestamp(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } // TSA server needed to create timestamp viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) server := httptest.NewServer(apiServer.GetHandler()) t.Cleanup(server.Close) client, err := tsaclient.GetTimestampClient(server.URL) if err != nil { t.Error(err) } chain, err := client.Timestamp.GetTimestampCertChain(nil) if err != nil { t.Fatalf("unexpected error getting timestamp chain: %v", err) } file, err := os.CreateTemp(os.TempDir(), "tempfile") if err != nil { t.Fatalf("error creating temp file: %v", err) } defer os.Remove(file.Name()) _, err = file.WriteString(chain.Payload) if err != nil { t.Fatalf("error writing chain payload to temp file: %v", err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, TSAServerURL: server.URL + "/api/v1/timestamp", RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } // Sign the image must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Make sure verify works against the Rekor and TSA clients must(verifyTSA(pubKeyPath, imgName, true, nil, "", file.Name(), false), t) } func TestDuplicateSign(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") ref, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() // Verify should fail at first mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t) // So should download mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) // Now sign the image ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, } so := options.SignOptions{ Upload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Now verify and download should work! // Ignore the tlog, because uploading to the tlog causes new signatures with new timestamp entries to be appended. must(verify(pubKeyPath, imgName, true, nil, "", true), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) // Signing again should work just fine... must(sign.SignCmd(ro, ko, so, []string{imgName}), t) se, err := ociremote.SignedEntity(ref, ociremote.WithRemoteOptions(registryClientOpts(ctx)...)) must(err, t) sigs, err := se.Signatures() must(err, t) signatures, err := sigs.Get() must(err, t) if len(signatures) > 1 { t.Errorf("expected there to only be one signature, got %v", signatures) } } func TestKeyURLVerify(t *testing.T) { // TODO: re-enable once distroless images are being signed by the new client t.Skip() // Verify that an image can be verified via key url keyRef := "https://raw.githubusercontent.com/GoogleContainerTools/distroless/main/cosign.pub" img := "gcr.io/distroless/base:latest" must(verify(keyRef, img, true, nil, "", false), t) } func TestGenerateKeyPairEnvVar(t *testing.T) { t.Setenv("COSIGN_PASSWORD", "foo") keys, err := cosign.GenerateKeyPair(generate.GetPass) if err != nil { t.Fatal(err) } if _, err := cosign.LoadPrivateKey(keys.PrivateBytes, []byte("foo")); err != nil { t.Fatal(err) } } func TestGenerateKeyPairK8s(t *testing.T) { td := t.TempDir() wd, err := os.Getwd() if err != nil { t.Fatal(err) } if err := os.Chdir(td); err != nil { t.Fatal(err) } defer func() { os.Chdir(wd) }() password := "foo" t.Setenv("COSIGN_PASSWORD", password) ctx := context.Background() name := "cosign-secret" namespace := "default" if err := kubernetes.KeyPairSecret(ctx, fmt.Sprintf("k8s://%s/%s", namespace, name), generate.GetPass); err != nil { t.Fatal(err) } // make sure the secret actually exists cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( clientcmd.NewDefaultClientConfigLoadingRules(), nil).ClientConfig() if err != nil { t.Fatal(err) } client, err := k8s.NewForConfig(cfg) if err != nil { t.Fatal(err) } s, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { t.Fatal(err) } if v, ok := s.Data["cosign.password"]; !ok || string(v) != password { t.Fatalf("password is incorrect, got %v expected %v", v, "foo") } // Clean up the secret (so tests can be re-run locally) err = client.CoreV1().Secrets(namespace).Delete(ctx, name, metav1.DeleteOptions{}) if err != nil { t.Fatal(err) } } func TestMultipleSignatures(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() td1 := t.TempDir() td2 := t.TempDir() imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, priv1, pub1 := keypair(t, td1) _, priv2, pub2 := keypair(t, td2) // Verify should fail at first for both keys mustErr(verify(pub1, imgName, true, nil, "", false), t) mustErr(verify(pub2, imgName, true, nil, "", false), t) // Now sign the image with one key ko := options.KeyOpts{ KeyRef: priv1, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Now verify should work with that one, but not the other must(verify(pub1, imgName, true, nil, "", false), t) mustErr(verify(pub2, imgName, true, nil, "", false), t) // Now sign with the other key too ko.KeyRef = priv2 must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Now verify should work with both must(verify(pub1, imgName, true, nil, "", false), t) must(verify(pub2, imgName, true, nil, "", false), t) } func TestSignBlob(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } blob := "someblob" td1 := t.TempDir() td2 := t.TempDir() bp := filepath.Join(td1, blob) if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { t.Fatal(err) } _, privKeyPath1, pubKeyPath1 := keypair(t, td1) _, _, pubKeyPath2 := keypair(t, td2) ctx := context.Background() ko1 := options.KeyOpts{ KeyRef: pubKeyPath1, } ko2 := options.KeyOpts{ KeyRef: pubKeyPath2, } // Verify should fail on a bad input cmd1 := cliverify.VerifyBlobCmd{ KeyOpts: ko1, SigRef: "badsig", IgnoreTlog: true, } cmd2 := cliverify.VerifyBlobCmd{ KeyOpts: ko2, SigRef: "badsig", IgnoreTlog: true, } mustErr(cmd1.Exec(ctx, blob), t) mustErr(cmd2.Exec(ctx, blob), t) // Now sign the blob with one key ko := options.KeyOpts{ KeyRef: privKeyPath1, PassFunc: passFunc, } sig, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false) if err != nil { t.Fatal(err) } // Now verify should work with that one, but not the other cmd1.SigRef = string(sig) cmd2.SigRef = string(sig) must(cmd1.Exec(ctx, bp), t) mustErr(cmd2.Exec(ctx, bp), t) } func TestSignBlobBundle(t *testing.T) { blob := "someblob" td1 := t.TempDir() bp := filepath.Join(td1, blob) bundlePath := filepath.Join(td1, "bundle.sig") if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { t.Fatal(err) } err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td1) if err != nil { t.Fatal(err) } _, privKeyPath1, pubKeyPath1 := keypair(t, td1) ctx := context.Background() ko1 := options.KeyOpts{ KeyRef: pubKeyPath1, BundlePath: bundlePath, } // Verify should fail on a bad input verifyBlobCmd := cliverify.VerifyBlobCmd{ KeyOpts: ko1, IgnoreTlog: true, } mustErr(verifyBlobCmd.Exec(ctx, bp), t) // Now sign the blob with one key ko := options.KeyOpts{ KeyRef: privKeyPath1, PassFunc: passFunc, BundlePath: bundlePath, RekorURL: rekorURL, SkipConfirmation: true, } if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil { t.Fatal(err) } // Now verify should work must(verifyBlobCmd.Exec(ctx, bp), t) // Now we turn on the tlog and sign again if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", true); err != nil { t.Fatal(err) } // Point to a fake rekor server to make sure offline verification of the tlog entry works verifyBlobCmd.RekorURL = "notreal" verifyBlobCmd.IgnoreTlog = false must(verifyBlobCmd.Exec(ctx, bp), t) } func TestSignBlobNewBundle(t *testing.T) { td1 := t.TempDir() blob := "someblob" blobPath := filepath.Join(td1, blob) if err := os.WriteFile(blobPath, []byte(blob), 0644); err != nil { t.Fatal(err) } bundlePath := filepath.Join(td1, "bundle.sigstore.json") ctx := context.Background() _, privKeyPath, pubKeyPath := keypair(t, td1) ko1 := options.KeyOpts{ KeyRef: pubKeyPath, BundlePath: bundlePath, NewBundleFormat: true, } verifyBlobCmd := cliverify.VerifyBlobCmd{ KeyOpts: ko1, IgnoreTlog: true, } // Verify should fail before bundle is written mustErr(verifyBlobCmd.Exec(ctx, blobPath), t) // Produce signed bundle ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, BundlePath: bundlePath, NewBundleFormat: true, } if _, err := sign.SignBlobCmd(ro, ko, blobPath, true, "", "", false); err != nil { t.Fatal(err) } // Verify should succeed now that bundle is written must(verifyBlobCmd.Exec(ctx, blobPath), t) } func TestSignBlobRFC3161TimestampBundle(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } // TSA server needed to create timestamp viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) server := httptest.NewServer(apiServer.GetHandler()) t.Cleanup(server.Close) blob := "someblob" bp := filepath.Join(td, blob) bundlePath := filepath.Join(td, "bundle.sig") tsPath := filepath.Join(td, "rfc3161Timestamp.json") if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { t.Fatal(err) } client, err := tsaclient.GetTimestampClient(server.URL) if err != nil { t.Error(err) } chain, err := client.Timestamp.GetTimestampCertChain(nil) if err != nil { t.Fatalf("unexpected error getting timestamp chain: %v", err) } file, err := os.CreateTemp(os.TempDir(), "tempfile") if err != nil { t.Fatalf("error creating temp file: %v", err) } defer os.Remove(file.Name()) _, err = file.WriteString(chain.Payload) if err != nil { t.Fatalf("error writing chain payload to temp file: %v", err) } _, privKeyPath1, pubKeyPath1 := keypair(t, td) ctx := context.Background() ko1 := options.KeyOpts{ KeyRef: pubKeyPath1, BundlePath: bundlePath, RFC3161TimestampPath: tsPath, TSACertChainPath: file.Name(), } // Verify should fail on a bad input verifyBlobCmd := cliverify.VerifyBlobCmd{ KeyOpts: ko1, IgnoreTlog: true, } mustErr(verifyBlobCmd.Exec(ctx, bp), t) // Now sign the blob with one key ko := options.KeyOpts{ KeyRef: privKeyPath1, PassFunc: passFunc, BundlePath: bundlePath, RFC3161TimestampPath: tsPath, TSAServerURL: server.URL + "/api/v1/timestamp", RekorURL: rekorURL, SkipConfirmation: true, } if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil { t.Fatal(err) } // Now verify should work must(verifyBlobCmd.Exec(ctx, bp), t) // Now we turn on the tlog and sign again if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", true); err != nil { t.Fatal(err) } // Point to a fake rekor server to make sure offline verification of the tlog entry works verifyBlobCmd.RekorURL = "notreal" verifyBlobCmd.IgnoreTlog = false must(verifyBlobCmd.Exec(ctx, bp), t) } func TestGenerate(t *testing.T) { repo, stop := reg(t) defer stop() imgName := path.Join(repo, "cosign-e2e") _, desc, cleanup := mkimage(t, imgName) defer cleanup() // Generate the payload for the image, and check the digest. b := bytes.Buffer{} must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t) ss := payload.SimpleContainerImage{} must(json.Unmarshal(b.Bytes(), &ss), t) equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) // Now try with some annotations. b.Reset() a := map[string]interface{}{"foo": "bar"} must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, a, &b), t) must(json.Unmarshal(b.Bytes(), &ss), t) equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) equals(ss.Optional["foo"], "bar", t) } func TestSaveLoad(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } tests := []struct { description string getSignedEntity func(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) }{ { description: "save and load an image", getSignedEntity: mkimage, }, { description: "save and load an image index", getSignedEntity: mkimageindex, }, } for i, test := range tests { t.Run(test.description, func(t *testing.T) { repo, stop := reg(t) defer stop() keysDir := t.TempDir() imgName := path.Join(repo, fmt.Sprintf("save-load-%d", i)) _, _, cleanup := test.getSignedEntity(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, keysDir) ctx := context.Background() // Now sign the image and verify it ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) must(verify(pubKeyPath, imgName, true, nil, "", false), t) // save the image to a temp dir imageDir := t.TempDir() must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) // verify the local image using a local key must(verifyLocal(pubKeyPath, imageDir, true, nil, ""), t) // load the image from the temp dir into a new image and verify the new image imgName2 := path.Join(repo, fmt.Sprintf("save-load-%d-2", i)) must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t) must(verify(pubKeyPath, imgName2, true, nil, "", false), t) }) } } func TestSaveLoadAttestation(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() imgName := path.Join(repo, "save-load") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() // Now sign the image and verify it ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) must(verify(pubKeyPath, imgName, true, nil, "", false), t) // now, append an attestation to the image slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { t.Fatal(err) } // Now attest the image ko = options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} attestCommand := attest.AttestCommand{ KeyOpts: ko, PredicatePath: slsaAttestationPath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, RekorEntryType: "dsse", } must(attestCommand.Exec(ctx, imgName), t) // save the image to a temp dir imageDir := t.TempDir() must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) // load the image from the temp dir into a new image and verify the new image imgName2 := path.Join(repo, "save-load-2") must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t) must(verify(pubKeyPath, imgName2, true, nil, "", false), t) // Use cue to verify attestation on the new image policyPath := filepath.Join(td, "policy.cue") verifyAttestation := cliverify.VerifyAttestationCommand{ KeyRef: pubKeyPath, IgnoreTlog: true, MaxWorkers: 10, } verifyAttestation.PredicateType = "slsaprovenance" verifyAttestation.Policies = []string{policyPath} // Success case (remote) cuePolicy := `predicate: builder: id: "2"` if err := os.WriteFile(policyPath, []byte(cuePolicy), 0600); err != nil { t.Fatal(err) } must(verifyAttestation.Exec(ctx, []string{imgName2}), t) // Success case (local) verifyAttestation.LocalImage = true must(verifyAttestation.Exec(ctx, []string{imageDir}), t) } func TestAttachSBOM(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } repo, stop := reg(t) defer stop() ctx := context.Background() imgName := path.Join(repo, "sbom-image") img, _, cleanup := mkimage(t, imgName) defer cleanup() out := bytes.Buffer{} _, errPl := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{Platform: "darwin/amd64"}, img.Name(), &out) if errPl == nil { t.Fatalf("Expected error when passing Platform to single arch image") } _, err = download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, img.Name(), &out) if err == nil { t.Fatal("Expected error") } t.Log(out.String()) out.Reset() // Upload it! must(attach.SBOMCmd(ctx, options.RegistryOptions{}, options.RegistryExperimentalOptions{}, "./testdata/bom-go-mod.spdx", "spdx", imgName), t) sboms, err := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, imgName, &out) if err != nil { t.Fatal(err) } t.Log(out.String()) if len(sboms) != 1 { t.Fatalf("Expected one sbom, got %d", len(sboms)) } want, err := os.ReadFile("./testdata/bom-go-mod.spdx") if err != nil { t.Fatal(err) } if diff := cmp.Diff(string(want), sboms[0]); diff != "" { t.Errorf("diff: %s", diff) } // Generate key pairs to sign the sbom td1 := t.TempDir() td2 := t.TempDir() _, privKeyPath1, pubKeyPath1 := keypair(t, td1) _, _, pubKeyPath2 := keypair(t, td2) // Verify should fail on a bad input mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t) mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), t) // Now sign the sbom with one key ko1 := options.KeyOpts{ KeyRef: privKeyPath1, PassFunc: passFunc, RekorURL: rekorURL, } so := options.SignOptions{ Upload: true, TlogUpload: true, Attachment: "sbom", } must(sign.SignCmd(ro, ko1, so, []string{imgName}), t) // Now verify should work with that one, but not the other must(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t) mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), t) } func TestNoTlog(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) // Verify should fail at first mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t) // Now sign the image without the tlog ko := options.KeyOpts{ KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL, } so := options.SignOptions{ Upload: true, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) // Now verify should work! must(verify(pubKeyPath, imgName, true, nil, "", true), t) } func TestGetPublicKeyCustomOut(t *testing.T) { td := t.TempDir() keys, privKeyPath, _ := keypair(t, td) ctx := context.Background() outFile := "output.pub" outPath := filepath.Join(td, outFile) outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0600) must(err, t) pk := publickey.Pkopts{ KeyRef: privKeyPath, } must(publickey.GetPublicKey(ctx, pk, publickey.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t) output, err := os.ReadFile(outPath) must(err, t) equals(keys.PublicBytes, output, t) } // If a signature has a bundle, but *not for that signature*, cosign verification should fail. // This test is pretty long, so here are the basic points: // 1. Sign image1 with a keypair, store entry in rekor // 2. Sign image2 with keypair, DO NOT store entry in rekor // 3. Take the bundle from image1 and store it on the signature in image2 // 4. Verification of image2 should now fail, since the bundle is for a different signature func TestInvalidBundle(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } regName, stop := reg(t) defer stop() img1 := path.Join(regName, "cosign-e2e") imgRef, _, cleanup := mkimage(t, img1) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() // Sign image1 and store the entry in rekor // (we're just using it for its bundle) remoteOpts := ociremote.WithRemoteOptions(registryClientOpts(ctx)...) ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL} so := options.SignOptions{ Upload: true, TlogUpload: true, SkipConfirmation: true, } must(sign.SignCmd(ro, ko, so, []string{img1}), t) // verify image1 must(verify(pubKeyPath, img1, true, nil, "", false), t) // extract the bundle from image1 si, err := ociremote.SignedImage(imgRef, remoteOpts) must(err, t) imgSigs, err := si.Signatures() must(err, t) sigs, err := imgSigs.Get() must(err, t) if l := len(sigs); l != 1 { t.Error("expected one signature") } bund, err := sigs[0].Bundle() must(err, t) if bund == nil { t.Fail() } // Now, we move on to image2 // Sign image2 and DO NOT store the entry in rekor img2 := path.Join(regName, "unrelated") imgRef2, _, cleanup := mkimage(t, img2) defer cleanup() so = options.SignOptions{ Upload: true, TlogUpload: false, } must(sign.SignCmd(ro, ko, so, []string{img2}), t) must(verify(pubKeyPath, img2, true, nil, "", true), t) si2, err := ociremote.SignedEntity(imgRef2, remoteOpts) must(err, t) sigs2, err := si2.Signatures() must(err, t) gottenSigs2, err := sigs2.Get() must(err, t) if len(gottenSigs2) != 1 { t.Fatal("there should be one signature") } sigsTag, err := ociremote.SignatureTag(imgRef2) if err != nil { t.Fatal(err) } // At this point, we would mutate the signature to add the bundle annotation // since we don't have a function for it at the moment, mock this by deleting the signature // and pushing a new signature with the additional bundle annotation if err := remote.Delete(sigsTag); err != nil { t.Fatal(err) } mustErr(verify(pubKeyPath, img2, true, nil, "", false), t) newSig, err := mutate.Signature(gottenSigs2[0], mutate.WithBundle(bund)) must(err, t) si2, err = ociremote.SignedEntity(imgRef2, remoteOpts) must(err, t) newImage, err := mutate.AttachSignatureToEntity(si2, newSig) must(err, t) if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil { t.Fatal(err) } // veriyfing image2 now should fail cmd := cliverify.VerifyCommand{ KeyRef: pubKeyPath, RekorURL: rekorURL, CheckClaims: true, HashAlgorithm: crypto.SHA256, MaxWorkers: 10, } args := []string{img2} mustErr(cmd.Exec(context.Background(), args), t) } func TestAttestBlobSignVerify(t *testing.T) { blob := "someblob" predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` predicateType := "slsaprovenance" td1 := t.TempDir() t.Cleanup(func() { os.RemoveAll(td1) }) bp := filepath.Join(td1, blob) if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { t.Fatal(err) } anotherBlob := filepath.Join(td1, "another-blob") if err := os.WriteFile(anotherBlob, []byte("another-blob"), 0644); err != nil { t.Fatal(err) } predicatePath := filepath.Join(td1, "predicate") if err := os.WriteFile(predicatePath, []byte(predicate), 0644); err != nil { t.Fatal(err) } outputSignature := filepath.Join(td1, "signature") _, privKeyPath1, pubKeyPath1 := keypair(t, td1) ctx := context.Background() ko := options.KeyOpts{ KeyRef: pubKeyPath1, } blobVerifyAttestationCmd := cliverify.VerifyBlobAttestationCommand{ KeyOpts: ko, SignaturePath: outputSignature, PredicateType: predicateType, IgnoreTlog: true, CheckClaims: true, } // Verify should fail on a bad input mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t) // Now attest the blob with the private key ko = options.KeyOpts{ KeyRef: privKeyPath1, PassFunc: passFunc, } attestBlobCmd := attest.AttestBlobCommand{ KeyOpts: ko, PredicatePath: predicatePath, PredicateType: predicateType, OutputSignature: outputSignature, RekorEntryType: "dsse", } must(attestBlobCmd.Exec(ctx, bp), t) // Now verify should work must(blobVerifyAttestationCmd.Exec(ctx, bp), t) // Make sure we fail with the wrong predicate type blobVerifyAttestationCmd.PredicateType = "custom" mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t) // Make sure we fail with the wrong blob (set the predicate type back) blobVerifyAttestationCmd.PredicateType = predicateType mustErr(blobVerifyAttestationCmd.Exec(ctx, anotherBlob), t) } func TestOffline(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { t.Fatal(err) } regName, stop := reg(t) defer stop() img1 := path.Join(regName, "cosign-e2e") imgRef, _, cleanup := mkimage(t, img1) defer cleanup() _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() // Sign image1 and store the entry in rekor ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL} so := options.SignOptions{ Upload: true, TlogUpload: true, SkipConfirmation: true, } must(sign.SignCmd(ro, ko, so, []string{img1}), t) // verify image1 online and offline must(verify(pubKeyPath, img1, true, nil, "", false), t) verifyCmd := &cliverify.VerifyCommand{ KeyRef: pubKeyPath, RekorURL: "notreal", Offline: true, CheckClaims: true, MaxWorkers: 10, } must(verifyCmd.Exec(ctx, []string{img1}), t) // Get signatures si, err := ociremote.SignedEntity(imgRef) must(err, t) sigs, err := si.Signatures() must(err, t) gottenSigs, err := sigs.Get() must(err, t) fakeBundle := &bundle.RekorBundle{ SignedEntryTimestamp: []byte(""), Payload: bundle.RekorPayload{ Body: "", }, } newSig, err := mutate.Signature(gottenSigs[0], mutate.WithBundle(fakeBundle)) must(err, t) sigsTag, err := ociremote.SignatureTag(imgRef) must(err, t) if err := remote.Delete(sigsTag); err != nil { t.Fatal(err) } si, err = ociremote.SignedEntity(imgRef) must(err, t) newImage, err := mutate.AttachSignatureToEntity(si, newSig) must(err, t) mustErr(verify(pubKeyPath, img1, true, nil, "", false), t) if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil { t.Fatal(err) } // Confirm offline verification fails mustErr(verifyCmd.Exec(ctx, []string{img1}), t) } func TestDockerfileVerify(t *testing.T) { td := t.TempDir() // set up SIGSTORE_ variables to point to keys for the local instances err := setLocalEnv(t, td) if err != nil { t.Fatal(err) } // unset the roots that were generated for timestamp signing, they won't work here err = fulcioroots.ReInit() if err != nil { t.Fatal(err) } identityToken, err := getOIDCToken() if err != nil { t.Fatal(err) } // create some images repo, stop := reg(t) defer stop() signedImg1 := path.Join(repo, "cosign-e2e-dockerfile-signed1") _, _, cleanup1 := mkimage(t, signedImg1) defer cleanup1() signedImg2 := path.Join(repo, "cosign-e2e-dockerfile-signed2") _, _, cleanup2 := mkimage(t, signedImg2) defer cleanup2() unsignedImg := path.Join(repo, "cosign-e2e-dockerfile-unsigned") _, _, cleanupUnsigned := mkimage(t, unsignedImg) defer cleanupUnsigned() // sign the images using --identity-token ko := options.KeyOpts{ FulcioURL: fulcioURL, RekorURL: rekorURL, IDToken: identityToken, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, SkipConfirmation: true, } ctx := context.Background() must(sign.SignCmd(ro, ko, so, []string{signedImg1}), t) must(sign.SignCmd(ro, ko, so, []string{signedImg2}), t) // create the dockerfiles singleStageDockerfileContents := fmt.Sprintf(` FROM %s `, signedImg1) singleStageDockerfile := mkfile(singleStageDockerfileContents, td, t) unsignedBuildStageDockerfileContents := fmt.Sprintf(` FROM %s FROM %s FROM %s `, signedImg1, unsignedImg, signedImg2) unsignedBuildStageDockerfile := mkfile(unsignedBuildStageDockerfileContents, td, t) fromAsDockerfileContents := fmt.Sprintf(` FROM --platform=linux/amd64 %s AS base `, signedImg1) fromAsDockerfile := mkfile(fromAsDockerfileContents, td, t) withArgDockerfileContents := ` ARG test_image FROM ${test_image} ` withArgDockerfile := mkfile(withArgDockerfileContents, td, t) withLowercaseDockerfileContents := fmt.Sprintf(` from %s `, signedImg1) withLowercaseDockerfile := mkfile(withLowercaseDockerfileContents, td, t) issuer := os.Getenv("OIDC_URL") tests := []struct { name string dockerfile string baseOnly bool env map[string]string wantErr bool }{ { name: "verify single stage", dockerfile: singleStageDockerfile, }, { name: "verify unsigned build stage", dockerfile: unsignedBuildStageDockerfile, wantErr: true, }, { name: "verify base image only", dockerfile: unsignedBuildStageDockerfile, baseOnly: true, }, { name: "verify from as", dockerfile: fromAsDockerfile, }, { name: "verify with arg", dockerfile: withArgDockerfile, env: map[string]string{"test_image": signedImg1}, }, { name: "verify image exists but is unsigned", dockerfile: withArgDockerfile, env: map[string]string{"test_image": unsignedImg}, wantErr: true, }, { name: "verify with lowercase", dockerfile: withLowercaseDockerfile, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { cmd := dockerfile.VerifyDockerfileCommand{ VerifyCommand: cliverify.VerifyCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: certID, }, RekorURL: rekorURL, }, BaseOnly: test.baseOnly, } args := []string{test.dockerfile} for k, v := range test.env { t.Setenv(k, v) } if test.wantErr { mustErr(cmd.Exec(ctx, args), t) } else { must(cmd.Exec(ctx, args), t) } }) } } func TestManifestVerify(t *testing.T) { td := t.TempDir() // set up SIGSTORE_ variables to point to keys for the local instances err := setLocalEnv(t, td) if err != nil { t.Fatal(err) } // unset the roots that were generated for timestamp signing, they won't work here err = fulcioroots.ReInit() if err != nil { t.Fatal(err) } identityToken, err := getOIDCToken() if err != nil { t.Fatal(err) } // create some images repo, stop := reg(t) defer stop() signedImg := path.Join(repo, "cosign-e2e-manifest-signed") _, _, cleanup := mkimage(t, signedImg) defer cleanup() unsignedImg := path.Join(repo, "cosign-e2e-manifest-unsigned") _, _, cleanupUnsigned := mkimage(t, unsignedImg) defer cleanupUnsigned() // sign the images using --identity-token ko := options.KeyOpts{ FulcioURL: fulcioURL, RekorURL: rekorURL, IDToken: identityToken, SkipConfirmation: true, } so := options.SignOptions{ Upload: true, TlogUpload: true, SkipConfirmation: true, } ctx := context.Background() must(sign.SignCmd(ro, ko, so, []string{signedImg}), t) // create the manifests manifestTemplate := ` apiVersion: v1 kind: Pod metadata: name: single-pod spec: containers: - name: %s image: %s ` signedManifestContents := fmt.Sprintf(manifestTemplate, "signed-img", signedImg) signedManifest := mkfileWithExt(signedManifestContents, td, ".yaml", t) unsignedManifestContents := fmt.Sprintf(manifestTemplate, "unsigned-img", unsignedImg) unsignedManifest := mkfileWithExt(unsignedManifestContents, td, ".yaml", t) issuer := os.Getenv("OIDC_URL") tests := []struct { name string manifest string wantErr bool }{ { name: "signed manifest", manifest: signedManifest, }, { name: "unsigned manifest", manifest: unsignedManifest, wantErr: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { cmd := manifest.VerifyManifestCommand{ VerifyCommand: cliverify.VerifyCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, CertIdentity: certID, }, RekorURL: rekorURL, }, } args := []string{test.manifest} if test.wantErr { mustErr(cmd.Exec(ctx, args), t) } else { must(cmd.Exec(ctx, args), t) } }) } } // getOIDCToken gets an OIDC token from the mock OIDC server. func getOIDCToken() (string, error) { issuer := os.Getenv("OIDC_URL") resp, err := http.Get(issuer + "/token") if err != nil { return "", err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", err } return string(body), nil } cosign-2.5.0/test/e2e_test.ps1000066400000000000000000000027721477503325500161410ustar00rootroot00000000000000# Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function New-TmpDir { $parent = [System.IO.Path]::GetTempPath() $name = [System.IO.Path]::GetRandomFileName() New-Item -ItemType Directory -Path (Join-Path $parent $name) } make cosign $TmpDir = New-TmpDir Copy-Item -Path .\cosign -Destination (Join-Path $TmpDir cosign.exe) Push-Location $TmpDir # See if things blow up immediately .\cosign.exe version # Generate a random alphanumeric password for the private key $pass = Get-Random Write-Output $pass | .\cosign.exe generate-key-pair $signing_key = "cosign.key" $verification_key = "cosign.pub" $test_img = "ghcr.io/distroless/static" Write-Output $pass | .\cosign.exe sign --key $signing_key --output-signature interactive.sig --output-payload interactive.payload --tlog-upload=false $test_img .\cosign.exe verify --key $verification_key --signature interactive.sig --payload interactive.payload --insecure-ignore-tlog=true $test_img Pop-Location Write-Output "Success" cosign-2.5.0/test/e2e_test.sh000077500000000000000000000062401477503325500160450ustar00rootroot00000000000000#!/usr/bin/env bash # # Copyright 2021 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -ex docker_compose="docker compose" if ! ${docker_compose} version >/dev/null 2>&1; then docker_compose="docker-compose" fi echo "setting up OIDC provider" pushd ./test/fakeoidc oidcimg=$(ko build main.go --local) docker network ls | grep fulcio_default || docker network create fulcio_default --label "com.docker.compose.network=fulcio_default" docker run -d --rm -p 8080:8080 --network fulcio_default --name fakeoidc $oidcimg cleanup_oidc() { echo "cleaning up oidc" docker stop fakeoidc } trap cleanup_oidc EXIT oidc_ip=$(docker inspect fakeoidc | jq -r '.[0].NetworkSettings.Networks.fulcio_default.IPAddress') export OIDC_URL="http://${oidc_ip}:8080" cat < /tmp/fulcio-config.json { "OIDCIssuers": { "$OIDC_URL": { "IssuerURL": "$OIDC_URL", "ClientID": "sigstore", "Type": "email" } } } EOF popd pushd $HOME echo "downloading service repos" for repo in rekor fulcio; do if [[ ! -d $repo ]]; then git clone https://github.com/sigstore/${repo}.git else pushd $repo git pull popd fi done echo "starting services" export FULCIO_METRICS_PORT=2113 export FULCIO_CONFIG=/tmp/fulcio-config.json for repo in rekor fulcio; do pushd $repo if [ "$repo" == "fulcio" ]; then yq -i e '.networks={"default":{ "name":"fulcio_default","external":true }}' docker-compose.yml yq -i e '.services.fulcio-server.networks=["default"]' docker-compose.yml fi ${docker_compose} up -d echo -n "waiting up to 60 sec for system to start" count=0 until [ $(${docker_compose} ps | grep -c "(healthy)") == 3 ]; do if [ $count -eq 18 ]; then echo "! timeout reached" exit 1 else echo -n "." sleep 10 let 'count+=1' fi done popd done cleanup_services() { echo "cleaning up" cleanup_oidc for repo in rekor fulcio; do pushd $HOME/$repo ${docker_compose} down popd done } trap cleanup_services EXIT echo echo "running tests" popd go test -tags=e2e -v -race ./test/... # Test on a private registry echo "testing sign/verify/clean on private registry" cleanup() { cleanup_services docker rm -f registry } trap cleanup EXIT docker run -d -p 5000:5000 --restart always -e REGISTRY_STORAGE_DELETE_ENABLED=true --name registry registry:latest export COSIGN_TEST_REPO=localhost:5000 go test -tags=e2e -v ./test/... -run TestSignVerifyClean # Run the built container to make sure it doesn't crash make ko-local img="ko.local/cosign:$(git rev-parse HEAD)" docker run $img version cosign-2.5.0/test/e2e_test_pkcs11.sh000077500000000000000000000031311477503325500172230ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2024 The Sigstore Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail # Test pkcs11 token signing # using a fork of https://github.com/vegardit/docker-softhsm2-pkcs11-proxy that stopped to build 5 months ago CONTAINER_ID=$(docker run -dit --name softhsm -v $(pwd):/root/cosign -p 2345:2345 ghcr.io/cpanato/softhsm2-pkcs11-proxy:latest@sha256:2614345f73f9432d85365f0ac450c4bf0abac51b205b54241a94f9cf9e671772) docker exec -i $CONTAINER_ID /bin/bash << 'EOF' # to install the latest go that is not available in the alpine repository echo "@edge http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories apk update # add make pcsc-lite-libs go command apk add make build-base apk add go@edge cd /root/cosign softhsm2-util --init-token --free --label "My Token" --pin 1234 --so-pin 1234 go test -v -cover -coverprofile=./cover.out -tags=softhsm,pkcs11key -coverpkg github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key test/pkcs11_test.go EOF cleanup_pkcs11() { docker rm -f $CONTAINER_ID } trap cleanup_pkcs11 EXIT cosign-2.5.0/test/e2e_tsa_test.go000066400000000000000000000163041477503325500167060ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e && cross package test import ( "context" "crypto/x509" "encoding/pem" "net/http/httptest" "path" "path/filepath" "testing" "time" "github.com/secure-systems-lab/go-securesystemslib/encrypted" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" cliverify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/sigstore/cosign/v2/pkg/cosign" tsaclient "github.com/sigstore/timestamp-authority/pkg/client" tsaserver "github.com/sigstore/timestamp-authority/pkg/server" "github.com/spf13/viper" ) func TestTSAMTLS(t *testing.T) { repo, stop := reg(t) defer stop() td := t.TempDir() imgName := path.Join(repo, "cosign-tsa-mtls-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() pemRootRef, pemLeafRef, pemKeyRef := generateSigningKeys(t, td) // Set up TSA server with TLS timestampCACert, timestampServerCert, timestampServerKey, timestampClientCert, timestampClientKey := generateMTLSKeys(t, td) timestampServerURL, timestampChainFile, tsaCleanup := setUpTSAServerWithTLS(t, td, timestampCACert, timestampServerKey, timestampServerCert) t.Cleanup(tsaCleanup) ko := options.KeyOpts{ KeyRef: pemKeyRef, PassFunc: passFunc, TSAServerURL: timestampServerURL, TSAClientCACert: timestampCACert, TSAClientCert: timestampClientCert, TSAClientKey: timestampClientKey, TSAServerName: "server.example.com", } so := options.SignOptions{ Upload: true, TlogUpload: false, Cert: pemLeafRef, CertChain: pemRootRef, } must(sign.SignCmd(ro, ko, so, []string{imgName}), t) verifyCmd := cliverify.VerifyCommand{ IgnoreTlog: true, IgnoreSCT: true, CheckClaims: true, CertChain: pemRootRef, TSACertChainPath: timestampChainFile, CertVerifyOptions: options.CertVerifyOptions{ CertIdentityRegexp: ".*", CertOidcIssuerRegexp: ".*", }, } must(verifyCmd.Exec(context.Background(), []string{imgName}), t) } func TestSignBlobTSAMTLS(t *testing.T) { td := t.TempDir() blob := time.Now().Format("Mon Jan 2 15:04:05 MST 2006") blobPath := mkfile(blob, td, t) timestampPath := filepath.Join(td, "timestamp.txt") bundlePath := filepath.Join(td, "cosign.bundle") _, privKey, pubKey := keypair(t, td) // Set up TSA server with TLS timestampCACert, timestampServerCert, timestampServerKey, timestampClientCert, timestampClientKey := generateMTLSKeys(t, td) timestampServerURL, timestampChainFile, tsaCleanup := setUpTSAServerWithTLS(t, td, timestampCACert, timestampServerKey, timestampServerCert) t.Cleanup(tsaCleanup) signingKO := options.KeyOpts{ KeyRef: privKey, PassFunc: passFunc, TSAServerURL: timestampServerURL, TSAClientCACert: timestampCACert, TSAClientCert: timestampClientCert, TSAClientKey: timestampClientKey, TSAServerName: "server.example.com", RFC3161TimestampPath: timestampPath, BundlePath: bundlePath, } sig, err := sign.SignBlobCmd(ro, signingKO, blobPath, true, "", "", false) must(err, t) verifyKO := options.KeyOpts{ KeyRef: pubKey, TSACertChainPath: timestampChainFile, RFC3161TimestampPath: timestampPath, BundlePath: bundlePath, } verifyCmd := cliverify.VerifyBlobCmd{ KeyOpts: verifyKO, SigRef: string(sig), CertVerifyOptions: options.CertVerifyOptions{ CertIdentityRegexp: ".*", CertOidcIssuerRegexp: ".*", }, IgnoreTlog: true, } must(verifyCmd.Exec(context.Background(), blobPath), t) } func generateSigningKeys(t *testing.T, td string) (string, string, string) { rootCert, rootKey, _ := GenerateRootCa() pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemRootRef := mkfile(string(pemRoot), td, t) leafCert, privKey, _ := GenerateLeafCert("xyz@nosuchprovider.com", "oidc-issuer", rootCert, rootKey) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) pemLeafRef := mkfile(string(pemLeaf), td, t) x509Encoded, _ := x509.MarshalPKCS8PrivateKey(privKey) encBytes, _ := encrypted.Encrypt(x509Encoded, keyPass) keyPem := pem.EncodeToMemory(&pem.Block{ Type: cosign.CosignPrivateKeyPemType, Bytes: encBytes}) pemKeyRef := mkfile(string(keyPem), td, t) return pemRootRef, pemLeafRef, pemKeyRef } func generateMTLSKeys(t *testing.T, td string) (string, string, string, string, string) { rootCert, rootKey, _ := GenerateRootCa() pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw}) pemRootRef := mkfile(string(pemRoot), td, t) serverLeafCert, serverPrivKey, _ := GenerateLeafCertWithSubjectAlternateNames([]string{"server.example.com"}, nil, nil, nil, "oidc-issuer", rootCert, rootKey) serverPemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: serverLeafCert.Raw}) serverPemLeafRef := mkfile(string(serverPemLeaf), td, t) serverX509Encoded, _ := x509.MarshalPKCS8PrivateKey(serverPrivKey) serverKeyPem := pem.EncodeToMemory(&pem.Block{ Type: cosign.ECPrivateKeyPemType, Bytes: serverX509Encoded}) serverPemKeyRef := mkfile(string(serverKeyPem), td, t) clientLeafCert, clientPrivKey, _ := GenerateLeafCert("tsa-mtls-client", "oidc-issuer", rootCert, rootKey) clientPemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: clientLeafCert.Raw}) clientPemLeafRef := mkfile(string(clientPemLeaf), td, t) clientX509Encoded, _ := x509.MarshalPKCS8PrivateKey(clientPrivKey) clientKeyPem := pem.EncodeToMemory(&pem.Block{ Type: cosign.ECPrivateKeyPemType, Bytes: clientX509Encoded}) clientPemKeyRef := mkfile(string(clientKeyPem), td, t) return pemRootRef, serverPemLeafRef, serverPemKeyRef, clientPemLeafRef, clientPemKeyRef } func setUpTSAServerWithTLS(t *testing.T, td, timestampCACert, timestampServerKey, timestampServerCert string) (string, string, func()) { viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") viper.Set("disable-ntp-monitoring", true) viper.Set("tls-host", "0.0.0.0") viper.Set("tls-port", 3000) viper.Set("tls-ca", timestampCACert) viper.Set("tls-key", timestampServerKey) viper.Set("tls-certificate", timestampServerCert) tsaAPIServer := tsaserver.NewRestAPIServer("localhost", 3000, []string{"https"}, false, 10*time.Second, 10*time.Second) tsaServer := httptest.NewServer(tsaAPIServer.GetHandler()) tsaClient, err := tsaclient.GetTimestampClient(tsaServer.URL) must(err, t) tsaChain, err := tsaClient.Timestamp.GetTimestampCertChain(nil) must(err, t) timestampServerURL := tsaServer.URL + "/api/v1/timestamp" timestampChainFile := mkfile(tsaChain.Payload, td, t) return timestampServerURL, timestampChainFile, tsaServer.Close } cosign-2.5.0/test/fakeoidc/000077500000000000000000000000001477503325500155375ustar00rootroot00000000000000cosign-2.5.0/test/fakeoidc/go.mod000066400000000000000000000002321477503325500166420ustar00rootroot00000000000000module github.com/sigstore/cosign/test/fakeoidc go 1.23.4 require github.com/go-jose/go-jose/v4 v4.0.5 require golang.org/x/crypto v0.32.0 // indirect cosign-2.5.0/test/fakeoidc/go.sum000066400000000000000000000022311477503325500166700ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= cosign-2.5.0/test/fakeoidc/main.go000066400000000000000000000056541477503325500170240ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Mock OIDC server, based on https://github.com/sigstore/fulcio/blob/efec18aaed12d1f91eeaaba96e90f86170c2ada4/pkg/server/grpc_server_test.go#L2235 package main import ( "crypto/rand" "crypto/rsa" "encoding/json" "fmt" "log" "net/http" "time" "github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4/jwt" ) var ( signer jose.Signer jwk jose.JSONWebKey ) type config struct { Issuer string `json:"issuer"` JWKSURI string `json:"jwks_uri"` } type customClaims struct { Email string `json:"email"` EmailVerified bool `json:"email_verified"` } func init() { pk, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Fatal(err) } jwk = jose.JSONWebKey{ Algorithm: string(jose.RS256), Key: pk, } signer, err = jose.NewSigner(jose.SigningKey{ Algorithm: jose.RS256, Key: jwk.Key, }, nil) if err != nil { log.Fatal(err) } } func token(w http.ResponseWriter, r *http.Request) { log.Print("handling token") token, err := jwt.Signed(signer).Claims(jwt.Claims{ Issuer: fmt.Sprintf("http://%s", r.Host), IssuedAt: jwt.NewNumericDate(time.Now()), Expiry: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), Subject: "foo@bar.com", Audience: jwt.Audience{"sigstore"}, }).Claims(customClaims{ Email: "foo@bar.com", EmailVerified: true, }).Serialize() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } w.Write([]byte(token)) } func keys(w http.ResponseWriter, r *http.Request) { log.Print("handling keys") keys, err := json.Marshal(jose.JSONWebKeySet{ Keys: []jose.JSONWebKey{ jwk.Public(), }, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } w.Header().Add("Content-type", "application/json") w.Write(keys) } func wellKnown(w http.ResponseWriter, r *http.Request) { log.Print("handling discovery") issuer := fmt.Sprintf("http://%s", r.Host) cfg, err := json.Marshal(config{ Issuer: issuer, JWKSURI: issuer + "/keys", }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } w.Header().Add("Content-type", "application/json") w.Write(cfg) } func main() { http.HandleFunc("/token", token) http.HandleFunc("/keys", keys) http.HandleFunc("/.well-known/openid-configuration", wellKnown) if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } } cosign-2.5.0/test/fuzz/000077500000000000000000000000001477503325500147705ustar00rootroot00000000000000cosign-2.5.0/test/fuzz/dictionaries/000077500000000000000000000000001477503325500174455ustar00rootroot00000000000000cosign-2.5.0/test/fuzz/dictionaries/FuzzEvaluatePolicyAgainstJSON.dict000066400000000000000000000013641477503325500261240ustar00rootroot00000000000000"{\"authorityMatches\":{\"keyatt\":{\"signatures\":null,\"attestations\":{\"vuln-key\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}},\"keysignature\":{\"signatures\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}],\"attestations\":null},\"keylessatt\":{\"signatures\":null,\"attestations\":{\"custom-keyless\":[{\"subject\":\"PLACEHOLDER\",\"issuer\":\"PLACEHOLDER\"}]}}}}" # Below is from https://github.com/rc0r/afl-fuzz/blob/master/dictionaries/json.dict "0" ",0" ":0" "0:" "-1.2e+3" "true" "false" "null" "\"\"" ",\"\"" ":\"\"" "\"\":" "{}" ",{}" ":{}" "{\"\":0}" "{{}}" "[]" ",[]" ":[]" "[0]" "[[]]" "''" "\\" "\\b" "\\f" "\\n" "\\r" "\\t" "\\u0000" "\\x00" "\\0" "\\uD800\\uDC00" "\\uDBFF\\uDFFF" "\"\":0" "//" "/**/" cosign-2.5.0/test/fuzz/dictionaries/FuzzImportKeyPairLoadPrivateKey.dict000066400000000000000000000006731477503325500265420ustar00rootroot00000000000000"-----BEGIN RSA PRIVATE KEY-----" "-----END RSA PRIVATE KEY-----" "-----BEGIN PRIVATE KEY-----" "-----END PRIVATE KEY-----" "-----BEGIN PUBLIC KEY-----" "-----END PUBLIC KEY-----" "-----BEGIN PGP PRIVATE KEY BLOCK-----" "Version: BCPG C# v1.6.1.0" "-----END PGP PRIVATE KEY BLOCK-----" "-----BEGIN EC PRIVATE KEY-----" "-----END EC PRIVATE KEY-----" "-----BEGIN ENCRYPTED COSIGN PRIVATE KEY-----" "-----END ENCRYPTED COSIGN PRIVATE KEY-----" cosign-2.5.0/test/fuzz/oss_fuzz_build.sh000077500000000000000000000041641477503325500203750ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2024 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. go get github.com/AdamKorcz/go-118-fuzz-build/testing mv ./pkg/cosign/keys_test.go ./pkg/cosign/keys_test_keep_in_fuzz_scope.go compile_native_go_fuzzer github.com/sigstore/cosign/v2/pkg/cosign/attestation FuzzGenerateStatement FuzzGenerateStatement compile_native_go_fuzzer github.com/sigstore/cosign/v2/pkg/cosign/cue FuzzValidateJSON FuzzValidateJSON_cue compile_native_go_fuzzer github.com/sigstore/cosign/v2/pkg/cosign/rego FuzzValidateJSON FuzzValidateJSON_rego compile_native_go_fuzzer github.com/sigstore/cosign/v2/pkg/cosign FuzzImportKeyPairLoadPrivateKey FuzzImportKeyPairLoadPrivateKey compile_native_go_fuzzer github.com/sigstore/cosign/v2/pkg/cosign FuzzSigVerify FuzzSigVerify compile_native_go_fuzzer github.com/sigstore/cosign/v2/pkg/policy FuzzEvaluatePolicyAgainstJSON FuzzEvaluatePolicyAgainstJSON zip -j $OUT/FuzzEvaluatePolicyAgainstJSON_seed_corpus.zip test/fuzz/seeds/FuzzEvaluatePolicyAgainstJSON_seed* zip -j $OUT/FuzzEvaluatePolicyAgainstJSON_seed_corpus.zip $SRC/go-fuzz-corpus/json/corpus/* zip -j $OUT/FuzzValidateJSON_cue_seed_corpus.zip $SRC/go-fuzz-corpus/json/corpus/* zip -j $OUT/FuzzValidateJSON_rego_seed_corpus.zip $SRC/go-fuzz-corpus/json/corpus/* zip -j $OUT/FuzzGenerateStatement_seed_corpus.zip $SRC/go-fuzz-corpus/json/corpus/* cp $SRC/afl-fuzz/dictionaries/json.dict $OUT/FuzzValidateJSON_cue.dict cp $SRC/afl-fuzz/dictionaries/json.dict $OUT/FuzzValidateJSON_rego.dict cp $SRC/afl-fuzz/dictionaries/json.dict $OUT/FuzzGenerateStatement.dict cp test/fuzz/dictionaries/FuzzImportKeyPairLoadPrivateKey.dict $OUT/ cosign-2.5.0/test/fuzz/seeds/000077500000000000000000000000001477503325500160735ustar00rootroot00000000000000cosign-2.5.0/test/fuzz/seeds/FuzzEvaluatePolicyAgainstJSON_seed1000066400000000000000000000010751477503325500247100ustar00rootroot00000000000000package sigstore isCompliant[response] { attestationsKeylessATT := input.authorityMatches.keylessatt.attestations result = (count(attestationsKeylessATT) == 1) attestationsKeyATT := input.authorityMatches.keyatt.attestations result = (count(attestationsKeyATT) == 1) keySignature := input.authorityMatches.keysignature.signatures result = (count(keySignature) == 1) errorMsg = "" warnMsg = "Throw warning error even if succeeded" response := { "result" : result, "error" : errorMsg, "warning" : warnMsg } } cosign-2.5.0/test/fuzz/seeds/FuzzEvaluatePolicyAgainstJSON_seed2000066400000000000000000000006751477503325500247160ustar00rootroot00000000000000package sigstore import "struct" import "list" authorityMatches: { keyatt: { attestations: struct.MaxFields(1) & struct.MinFields(1) }, keysignature: { signatures: list.MaxItems(1) & list.MinItems(1) }, if( len(authorityMatches.keylessatt.attestations) < 2) { keylessattMinAttestations: 2 keylessattMinAttestations: "Error" }, keylesssignature: { signatures: list.MaxItems(1) & list.MinItems(1) } } cosign-2.5.0/test/helpers.go000066400000000000000000000603011477503325500157630ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e package test import ( "bytes" "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "fmt" "io" "log" "math/big" "net" "net/http" "net/http/httptest" "net/url" "os" "path" "path/filepath" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/registry" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/secure-systems-lab/go-securesystemslib/encrypted" // Initialize all known client auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" cliverify "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/cosign/env" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" sigs "github.com/sigstore/cosign/v2/pkg/signature" ) const ( rekorURL = "http://127.0.0.1:3000" fulcioURL = "http://127.0.0.1:5555" certID = "foo@bar.com" ) var keyPass = []byte("hello") var passFunc = func(_ bool) ([]byte, error) { return keyPass, nil } var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string, skipTlogVerify bool) error { cmd := cliverify.VerifyCommand{ KeyRef: keyRef, RekorURL: rekorURL, CheckClaims: checkClaims, Annotations: sigs.AnnotationsMap{Annotations: annotations}, Attachment: attachment, HashAlgorithm: crypto.SHA256, MaxWorkers: 10, IgnoreTlog: skipTlogVerify, } args := []string{imageRef} return cmd.Exec(context.Background(), args) } var verifyCertChain = func(keyRef, certChain, certFile, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string, skipTlogVerify bool) error { cmd := cliverify.VerifyCommand{ KeyRef: keyRef, RekorURL: rekorURL, CheckClaims: checkClaims, Annotations: sigs.AnnotationsMap{Annotations: annotations}, Attachment: attachment, HashAlgorithm: crypto.SHA256, MaxWorkers: 10, IgnoreTlog: skipTlogVerify, CertVerifyOptions: options.CertVerifyOptions{ Cert: certFile, CertChain: certChain, }, } args := []string{imageRef} return cmd.Exec(context.Background(), args) } var verifyCertBundle = func(keyRef, caCertFile, caIntermediateCertFile, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string, skipTlogVerify bool) error { cmd := cliverify.VerifyCommand{ KeyRef: keyRef, RekorURL: rekorURL, CheckClaims: checkClaims, Annotations: sigs.AnnotationsMap{Annotations: annotations}, Attachment: attachment, HashAlgorithm: crypto.SHA256, MaxWorkers: 10, IgnoreTlog: skipTlogVerify, CertVerifyOptions: options.CertVerifyOptions{ CAIntermediates: caIntermediateCertFile, CARoots: caCertFile, CertOidcIssuerRegexp: ".*", CertIdentityRegexp: ".*", }, } args := []string{imageRef} return cmd.Exec(context.Background(), args) } var verifyTSA = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment, tsaCertChain string, skipTlogVerify bool) error { cmd := cliverify.VerifyCommand{ KeyRef: keyRef, RekorURL: rekorURL, CheckClaims: checkClaims, Annotations: sigs.AnnotationsMap{Annotations: annotations}, Attachment: attachment, HashAlgorithm: crypto.SHA256, TSACertChainPath: tsaCertChain, IgnoreTlog: skipTlogVerify, MaxWorkers: 10, } args := []string{imageRef} return cmd.Exec(context.Background(), args) } var verifyKeylessTSA = func(imageRef string, tsaCertChain string, skipSCT bool, skipTlogVerify bool) error { //nolint: unused cmd := cliverify.VerifyCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuerRegexp: ".*", CertIdentityRegexp: ".*", }, RekorURL: rekorURL, HashAlgorithm: crypto.SHA256, TSACertChainPath: tsaCertChain, IgnoreSCT: skipSCT, IgnoreTlog: skipTlogVerify, MaxWorkers: 10, } args := []string{imageRef} return cmd.Exec(context.Background(), args) } var verifyKeylessTSAWithCARoots = func(imageRef string, caroots string, // filename of a PEM file with CA Roots certificates intermediates string, // empty or filename of a PEM file with Intermediate certificates certFile string, // filename of a PEM file with the codesigning certificate tsaCertChain string, skipSCT bool, skipTlogVerify bool) error { cmd := cliverify.VerifyCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuerRegexp: ".*", CertIdentityRegexp: ".*", }, CertRef: certFile, CARoots: caroots, CAIntermediates: intermediates, RekorURL: rekorURL, HashAlgorithm: crypto.SHA256, TSACertChainPath: tsaCertChain, IgnoreSCT: skipSCT, IgnoreTlog: skipTlogVerify, MaxWorkers: 10, } args := []string{imageRef} return cmd.Exec(context.Background(), args) } var verifyBlobKeylessWithCARoots = func(blobRef string, sig string, caroots string, // filename of a PEM file with CA Roots certificates intermediates string, // empty or filename of a PEM file with Intermediate certificates certFile string, // filename of a PEM file with the codesigning certificate skipSCT bool, skipTlogVerify bool) error { cmd := cliverify.VerifyBlobCmd{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuerRegexp: ".*", CertIdentityRegexp: ".*", }, SigRef: sig, CertRef: certFile, CARoots: caroots, CAIntermediates: intermediates, IgnoreSCT: skipSCT, IgnoreTlog: skipTlogVerify, } return cmd.Exec(context.Background(), blobRef) } // Used to verify local images stored on disk var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string) error { cmd := cliverify.VerifyCommand{ KeyRef: keyRef, RekorURL: rekorURL, CheckClaims: checkClaims, Annotations: sigs.AnnotationsMap{Annotations: annotations}, Attachment: attachment, HashAlgorithm: crypto.SHA256, LocalImage: true, MaxWorkers: 10, } args := []string{path} return cmd.Exec(context.Background(), args) } var verifyOffline = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string) error { cmd := cliverify.VerifyCommand{ KeyRef: keyRef, RekorURL: "notreal", Offline: true, CheckClaims: checkClaims, Annotations: sigs.AnnotationsMap{Annotations: annotations}, Attachment: attachment, HashAlgorithm: crypto.SHA256, MaxWorkers: 10, } args := []string{imageRef} return cmd.Exec(context.Background(), args) } var ro = &options.RootOptions{Timeout: options.DefaultTimeout} func keypair(t *testing.T, td string) (*cosign.KeysBytes, string, string) { wd, err := os.Getwd() if err != nil { t.Fatal(err) } if err := os.Chdir(td); err != nil { t.Fatal(err) } defer func() { _ = os.Chdir(wd) }() keys, err := cosign.GenerateKeyPair(passFunc) if err != nil { t.Fatal(err) } privKeyPath := filepath.Join(td, "cosign.key") if err := os.WriteFile(privKeyPath, keys.PrivateBytes, 0600); err != nil { t.Fatal(err) } pubKeyPath := filepath.Join(td, "cosign.pub") if err := os.WriteFile(pubKeyPath, keys.PublicBytes, 0600); err != nil { t.Fatal(err) } return keys, privKeyPath, pubKeyPath } // convert the given ecdsa.PrivateKey to a PEM encoded string, import into sigstore format, // and write to the given file path. Returns the path to the imported key (/) func importECDSAPrivateKey(t *testing.T, privKey *ecdsa.PrivateKey, td, fname string) string { t.Helper() x509Encoded, _ := x509.MarshalPKCS8PrivateKey(privKey) encBytes, _ := encrypted.Encrypt(x509Encoded, keyPass) keyPEM := pem.EncodeToMemory(&pem.Block{ Type: cosign.CosignPrivateKeyPemType, Bytes: encBytes}) cosignKeyPath := filepath.Join(td, fname) if err := os.WriteFile(cosignKeyPath, keyPEM, 0600); err != nil { t.Fatal(err) } return cosignKeyPath } func importSampleKeyPair(t *testing.T, td string) (*cosign.KeysBytes, string, string) { //nolint: gosec const validrsa1 = `-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAx5piWVlE62NnZ0UzJ8Z6oKiKOC4dbOZ1HsNhIRtqkM+Oq4G+ 25yq6P+0JU/Qvr9veOGEb3R/J9u8JBo+hv2i5X8OtgvP2V2pi6f1s6vK7L0+6uRb 4YTT/UdMshaVf97MgEqbq41Jf/cuvh+3AV0tZ1BpixZg4aXMKpY6HUP69lbsu27o SUN1myMv7TSgZiV4CYs3l/gkEfpysBptWlcHRuw5RsB+C0RbjRtbJ/5VxmE/vd3M lafd5t1WSpMb8yf0a84u5NFaXwZ7CweMfXeOddS0yb19ShSuW3PPRadruBM1mq15 js9GfagPxDS75Imcs+fA62lWvHxEujTGjYHxawIDAQABAoIBAH+sgLwmHa9zJfEo klAe5NFe/QpydN/ziXbkAnzqzH9URC3wD+TpkWj4JoK3Sw635NWtasjf+3XDV9S/ 9L7j/g5N91r6sziWcJykEsWaXXKQmm4lI6BdFjwsHyLKz1W7bZOiJXDWLu1rbrqu DqEQuLoc9WXCKrYrFy0maoXNtfla/1p05kKN0bMigcnnyAQ+xBTwoyco4tkIz5se IYxorz7qzXrkHQI+knz5BawmNe3ekoSaXUPoLoOR7TRTGsLteL5yukvWAi8S/0rE gftC+PZCQpoQhSUYq7wXe7RowJ1f+kXb7HsSedOTfTSW1D/pUb/uW+CcRKig42ZI I9H9TAECgYEA5XGBML6fJyWVqx64sHbUAjQsmQ0RwU6Zo7sqHIEPf6tYVYp7KtzK KOfi8seOOL5FSy4pjCo11Dzyrh9bn45RNmtjSYTgOnVPSoCfuRNfOcpG+/wCHjYf EjDvdrCpbg59kVUeaMeBDiyWAlM48HJAn8O7ez2U/iKQCyJmOIwFhSkCgYEA3rSz Fi1NzqYWxWos4NBmg8iKcQ9SMkmPdgRLAs/WNnZJ8fdgJZwihevkXGytRGJEmav2 GMKRx1g6ey8fjXTQH9WM8X/kJC5fv8wLHnUCH/K3Mcp9CYwn7PFvSnBr4kQoc/el bURhcF1+/opEC8vNX/Wk3zAG7Xs1PREXlH2SIHMCgYBV/3kgwBH/JkM25EjtO1yz hsLAivmAruk/SUO7c1RP0fVF+qW3pxHOyztxLALOmeJ3D1JbSubqKf377Zz17O3b q9yHDdrNjnKtxhAX2n7ytjJs+EQC9t4mf1kB761RpvTBqFnBhCWHHocLUA4jcW9v cnmu86IIrwO2aKpPv4vCIQKBgHU9gY3qOazRSOmSlJ+hdmZn+2G7pBTvHsQNTIPl cCrpqNHl3crO4GnKHkT9vVVjuiOAIKU2QNJFwzu4Og8Y8LvhizpTjoHxm9x3iV72 UDELcJ+YrqyJCTe2flUcy96o7Pbn50GXnwgtYD6WAW6IUszyn2ITgYIhu4wzZEt6 s6O7AoGAPTKbRA87L34LMlXyUBJma+etMARIP1zu8bXJ7hSJeMcog8zaLczN7ruT pGAaLxggvtvuncMuTrG+cdmsR9SafSFKRS92NCxhOUonQ+NP6mLskIGzJZoQ5JvQ qGzRVIDGbNkrVHM0IsAtHRpC0rYrtZY+9OwiraGcsqUMLwwQdCA= -----END RSA PRIVATE KEY-----` wd, err := os.Getwd() if err != nil { t.Fatal(err) } if err := os.Chdir(td); err != nil { t.Fatal(err) } defer func() { _ = os.Chdir(wd) }() err = os.WriteFile("validrsa1.key", []byte(validrsa1), 0600) if err != nil { t.Fatal(err) } keys, err := cosign.ImportKeyPair("validrsa1.key", passFunc) if err != nil { t.Fatal(err) } privKeyPath := filepath.Join(td, "import-cosign.key") if err := os.WriteFile(privKeyPath, keys.PrivateBytes, 0600); err != nil { t.Fatal(err) } pubKeyPath := filepath.Join(td, "import-cosign.pub") if err := os.WriteFile(pubKeyPath, keys.PublicBytes, 0600); err != nil { t.Fatal(err) } return keys, privKeyPath, pubKeyPath } func mockStdin(contents, td string, t *testing.T) func() { //nolint: unused origin := os.Stdin p := mkfile(contents, td, t) f, err := os.Open(p) if err != nil { t.Fatal(err) } os.Stdin = f return func() { os.Stdin = origin } } func mkfile(contents, td string, t *testing.T) string { f, err := os.CreateTemp(td, "") if err != nil { t.Fatal(err) } defer f.Close() if _, err := f.Write([]byte(contents)); err != nil { t.Fatal(err) } return f.Name() } func mkfileWithExt(contents, td, ext string, t *testing.T) string { f := mkfile(contents, td, t) newName := f + ext err := os.Rename(f, newName) if err != nil { t.Fatal(err) } return newName } func mkimage(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) { ref, err := name.ParseReference(n, name.WeakValidation) if err != nil { t.Fatal(err) } img, err := random.Image(512, 5) if err != nil { t.Fatal(err) } regClientOpts := registryClientOpts(context.Background()) if err := remote.Write(ref, img, regClientOpts...); err != nil { t.Fatal(err) } remoteImage, err := remote.Get(ref, regClientOpts...) if err != nil { t.Fatal(err) } cleanup := func() { _ = remote.Delete(ref, regClientOpts...) ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteImage.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...)) _ = remote.Delete(ref, regClientOpts...) } return ref, remoteImage, cleanup } func mkimageindex(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) { ref, err := name.ParseReference(n, name.WeakValidation) if err != nil { t.Fatal(err) } ii, err := random.Index(512, 5, 4) if err != nil { t.Fatal(err) } regClientOpts := registryClientOpts(context.Background()) if err := remote.WriteIndex(ref, ii, regClientOpts...); err != nil { t.Fatal(err) } remoteIndex, err := remote.Get(ref, regClientOpts...) if err != nil { t.Fatal(err) } cleanup := func() { _ = remote.Delete(ref, regClientOpts...) ref, _ := ociremote.SignatureTag(ref.Context().Digest(remoteIndex.Digest.String()), ociremote.WithRemoteOptions(regClientOpts...)) _ = remote.Delete(ref, regClientOpts...) } return ref, remoteIndex, cleanup } func must(err error, t *testing.T) { t.Helper() if err != nil { t.Fatal(err) } } func mustErr(err error, t *testing.T) { t.Helper() if err == nil { t.Fatal("expected error") } } func equals(v1, v2 interface{}, t *testing.T) { if diff := cmp.Diff(v1, v2); diff != "" { t.Error(diff) } } func reg(t *testing.T) (string, func()) { repo := os.Getenv("COSIGN_TEST_REPO") //nolint: forbidigo if repo != "" { return repo, func() {} } t.Log("COSIGN_TEST_REPO unset, using fake registry") r := httptest.NewServer(registry.New()) u, err := url.Parse(r.URL) if err != nil { t.Fatal(err) } return u.Host, r.Close } func registryClientOpts(ctx context.Context) []remote.Option { return []remote.Option{ remote.WithAuthFromKeychain(authn.DefaultKeychain), remote.WithContext(ctx), } } // setLocalEnv sets SIGSTORE_CT_LOG_PUBLIC_KEY_FILE, SIGSTORE_ROOT_FILE, and SIGSTORE_REKOR_PUBLIC_KEY for the locally running sigstore deployment. func setLocalEnv(t *testing.T, dir string) error { // fulcio repo is downloaded to the user's home directory by e2e_test.sh home, err := os.UserHomeDir() if err != nil { return fmt.Errorf("error getting home directory: %w", err) } t.Setenv(env.VariableSigstoreCTLogPublicKeyFile.String(), path.Join(home, "fulcio/config/ctfe/pubkey.pem")) err = downloadAndSetEnv(t, fulcioURL+"/api/v1/rootCert", env.VariableSigstoreRootFile.String(), dir) if err != nil { return fmt.Errorf("error setting %s env var: %w", env.VariableSigstoreRootFile.String(), err) } err = downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), dir) if err != nil { return fmt.Errorf("error setting %s env var: %w", env.VariableSigstoreRekorPublicKey.String(), err) } return nil } // copyFile copies a file from a source to a destination. func copyFile(src, dst string) error { f, err := os.Open(src) if err != nil { return fmt.Errorf("error opening source file: %w", err) } defer f.Close() cp, err := os.Create(dst) if err != nil { return fmt.Errorf("error creating destination file: %w", err) } defer cp.Close() _, err = io.Copy(cp, f) if err != nil { return fmt.Errorf("error copying file: %w", err) } return nil } // downloadFile fetches a URL and stores it at the given file path. func downloadFile(url string, fp *os.File) error { resp, err := http.Get(url) //nolint: gosec if err != nil { return fmt.Errorf("error downloading file: %w", err) } defer resp.Body.Close() _, err = io.Copy(fp, resp.Body) if err != nil { return fmt.Errorf("error writing to file: %w", err) } return nil } // downloadAndSetEnv fetches a URL and sets the given environment variable to point to the downloaded file path. func downloadAndSetEnv(t *testing.T, url, envVar, dir string) error { f, err := os.CreateTemp(dir, "") if err != nil { return fmt.Errorf("error creating temp file: %w", err) } err = downloadFile(url, f) if err != nil { return fmt.Errorf("error downloading file: %w", err) } t.Setenv(envVar, f.Name()) return nil } func generateCertificateBundleFiles(td string, genIntermediate bool, outputSuffix string) ( caCertFile string, caPrivKeyFile string, caIntermediateCertFile string, caIntermediatePrivKeyFile string, certFile string, certChainFile string, err error, ) { caCertBuf, caPrivKeyBuf, caIntermediateCertBuf, caIntermediatePrivKeyBuf, certBuf, certChainBuf, err := generateCertificateBundle(genIntermediate) if err != nil { err = fmt.Errorf("error generating certificate bundle: %w", err) return } caCertFile = filepath.Join(td, fmt.Sprintf("caCert%s.pem", outputSuffix)) err = os.WriteFile(caCertFile, caCertBuf.Bytes(), 0600) if err != nil { err = fmt.Errorf("error writing caCert to file %s: %w", caCertFile, err) return } caPrivKeyFile = filepath.Join(td, fmt.Sprintf("caPrivKey%s.pem", outputSuffix)) err = os.WriteFile(caPrivKeyFile, caPrivKeyBuf.Bytes(), 0600) if err != nil { err = fmt.Errorf("error writing caPrivKey to file %s: %w", caPrivKeyFile, err) return } if genIntermediate { caIntermediateCertFile = filepath.Join(td, fmt.Sprintf("caIntermediateCert%s.pem", outputSuffix)) err = os.WriteFile(caIntermediateCertFile, caIntermediateCertBuf.Bytes(), 0600) if err != nil { err = fmt.Errorf("error writing caIntermediateCert to file %s: %w", caIntermediateCertFile, err) return } caIntermediatePrivKeyFile = filepath.Join(td, fmt.Sprintf("caIntermediatePrivKey%s.pem", outputSuffix)) err = os.WriteFile(caIntermediatePrivKeyFile, caIntermediatePrivKeyBuf.Bytes(), 0600) if err != nil { err = fmt.Errorf("error writing caIntermediatePrivKey to file %s: %w", caIntermediatePrivKeyFile, err) return } } certFile = filepath.Join(td, fmt.Sprintf("cert%s.pem", outputSuffix)) err = os.WriteFile(certFile, certBuf.Bytes(), 0600) if err != nil { err = fmt.Errorf("error writing cert to file %s: %w", certFile, err) return } // write the contents of certChainBuf to a file certChainFile = filepath.Join(td, fmt.Sprintf("certchain%s.pem", outputSuffix)) err = os.WriteFile(certChainFile, certChainBuf.Bytes(), 0600) if err != nil { err = fmt.Errorf("error writing certificate chain to file %s: %w", certFile, err) return } return } func generateCertificateBundle(genIntermediate bool) ( caCertBuf *bytes.Buffer, caPrivKeyBuf *bytes.Buffer, caIntermediateCertBuf *bytes.Buffer, caIntermediatePrivKeyBuf *bytes.Buffer, certBuf *bytes.Buffer, certBundleBuf *bytes.Buffer, err error, //nolint: unparam ) { // set up our CA certificate ca := &x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ Organization: []string{"CA Company, INC."}, OrganizationalUnit: []string{"CA Root Team"}, Country: []string{"US"}, Province: []string{""}, Locality: []string{"San Francisco"}, StreetAddress: []string{"Golden Gate Bridge"}, PostalCode: []string{"94016"}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), IsCA: true, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning /*, x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth */}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, EmailAddresses: []string{"ca@example.com"}, } // create our private and public key caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { log.Fatal(err) } // create the CA caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) if err != nil { log.Fatal(err) } caCertBuf = &bytes.Buffer{} err = pem.Encode(caCertBuf, &pem.Block{ Type: "CERTIFICATE", Bytes: caBytes, }) if err != nil { log.Fatalf("unable to write PEM encode: %v", err) } caPrivKeyBuf = &bytes.Buffer{} err = pem.Encode(caPrivKeyBuf, &pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), }) if err != nil { log.Fatalf("unable to PEM encode private key to buffer: %v", err) //nolint:gocritic } // generate intermediate CA if requested var caIntermediate *x509.Certificate var caIntermediatePrivKey *rsa.PrivateKey if genIntermediate { caIntermediate = &x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ Organization: []string{"CA Company, INC."}, OrganizationalUnit: []string{"CA Intermediate Team"}, Country: []string{"US"}, Province: []string{""}, Locality: []string{"San Francisco"}, StreetAddress: []string{"Golden Gate Bridge"}, PostalCode: []string{"94016"}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), IsCA: true, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning /*, x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth */}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, EmailAddresses: []string{"ca@example.com"}, } // create our private and public key caIntermediatePrivKey, err = rsa.GenerateKey(rand.Reader, 4096) if err != nil { log.Fatal(err) } // create the Intermediate CA caIntermediateBytes, err := x509.CreateCertificate(rand.Reader, caIntermediate, ca, &caIntermediatePrivKey.PublicKey, caPrivKey) if err != nil { log.Fatal(err) } caIntermediateCertBuf = &bytes.Buffer{} err = pem.Encode(caIntermediateCertBuf, &pem.Block{ Type: "CERTIFICATE", Bytes: caIntermediateBytes, }) if err != nil { log.Fatalf("unable to write to buffer: %v", err) } caIntermediatePrivKeyBuf = &bytes.Buffer{} err = pem.Encode(caIntermediatePrivKeyBuf, &pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(caIntermediatePrivKey), }) if err != nil { log.Fatalf("unable to PEM encode caIntermediatePrivKey: %v", err) } } // set up our server certificate cert := &x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ Organization: []string{"End User"}, OrganizationalUnit: []string{"End Node Team"}, Country: []string{"US"}, Province: []string{""}, Locality: []string{"San Francisco"}, StreetAddress: []string{"Golden Gate Bridge"}, PostalCode: []string{"94016"}, }, IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), SubjectKeyId: []byte{1, 2, 3, 4, 6}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning /* x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth */}, KeyUsage: x509.KeyUsageDigitalSignature, EmailAddresses: []string{"xyz@nosuchprovider.com"}, DNSNames: []string{"next.hugeunicorn.xyz"}, } certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { log.Fatal(err) } var certBytes []byte if !genIntermediate { certBytes, err = x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) } else { certBytes, err = x509.CreateCertificate(rand.Reader, cert, caIntermediate, &caIntermediatePrivKey.PublicKey, caIntermediatePrivKey) } if err != nil { log.Fatal(err) } certBuf = &bytes.Buffer{} err = pem.Encode(certBuf, &pem.Block{ Type: "CERTIFICATE", Bytes: certBytes, }) if err != nil { log.Fatalf("failed to encode cert: %v", err) } // concatenate into certChainBuf the contents of caIntermediateCertBuf and caCertBuf certBundleBuf = &bytes.Buffer{} if genIntermediate { _, err = certBundleBuf.Write(caIntermediateCertBuf.Bytes()) if err != nil { log.Fatalf("failed to write caIntermediateCertBuf to certChainBuf: %v", err) } } _, err = certBundleBuf.Write(caCertBuf.Bytes()) if err != nil { log.Fatalf("failed to write caCertBuf to certChainBuf: %v", err) } return caCertBuf, caPrivKeyBuf, caIntermediateCertBuf, caIntermediatePrivKeyBuf, certBuf, certBundleBuf, nil } cosign-2.5.0/test/helpers_test.go000066400000000000000000000111061477503325500170210ustar00rootroot00000000000000// // Copyright 2024 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e package test import ( "crypto/x509" "encoding/pem" "log" "os" "testing" ) func TestGenerateCertificateBundleFiles(t *testing.T) { for _, tt := range []struct { name string genIntermediate bool }{ { name: "without intermediate", genIntermediate: false, }, { name: "with intermediate", genIntermediate: true, }, } { t.Run(tt.name, func(t *testing.T) { td := t.TempDir() suffix := "foo" caCertFile, caPrivKeyFile, caIntermediateCertFile, caIntermediatePrivKeyFile, certFile, certChainFile, err := generateCertificateBundleFiles(td, true, suffix) if err != nil { t.Fatalf("Error generating certificate bundle: %v", err) } verifyCertificate(t, caCertFile) if tt.genIntermediate { verifyCertificate(t, caIntermediateCertFile) } verifyCertificate(t, certFile) verifyPrivateKey(t, caPrivKeyFile) if tt.genIntermediate { verifyPrivateKey(t, caIntermediatePrivKeyFile) verifyCertificateChain(t, certChainFile) } }) } } func verifyCertificate(t *testing.T, certFile string) { t.Helper() // open and parse certFile, ensure it is a TLS certificate data, err := os.ReadFile(certFile) if err != nil { t.Fatalf("Error reading certificate file %s: %v\n", certFile, err) return } // Check if the file contents are a PEM-encoded TLS certificate if !isPEMEncodedCert(data) { t.Fatalf("file %s doesn't contain a valid PEM-encoded TLS certificate", certFile) } } func verifyCertificateChain(t *testing.T, certChainFile string) { t.Helper() // open and parse certChainFile, ensure it is a TLS certificate chain data, err := os.ReadFile(certChainFile) if err != nil { t.Fatalf("Error reading certificate file %s: %v\n", certChainFile, err) } // Check if the file contents are a PEM-encoded TLS certificate if !isPEMEncodedCertChain(data) { t.Fatalf("file %s doesn't contain a valid PEM-encoded TLS certificate chain", certChainFile) } } // isPEMEncodedCert checks if the provided data is a PEM-encoded certificate func isPEMEncodedCert(data []byte) bool { // Decode the PEM data block, _ := pem.Decode(data) if block == nil || block.Type != "CERTIFICATE" { return false } // Parse the certificate to ensure it is valid _, err := x509.ParseCertificate(block.Bytes) return err == nil } func verifyPrivateKey(t *testing.T, privKeyFile string) { t.Helper() // open and parse certFile, ensure it is a TLS certificate data, err := os.ReadFile(privKeyFile) if err != nil { t.Fatalf("Error reading private key file %s: %v\n", privKeyFile, err) return } // Check if the file contents are a PEM-encoded private key if !isPEMEncodedPrivateKey(data) { t.Fatalf("file %s doesn't contain a valid PEM-encoded private key", privKeyFile) } } // isPEMEncodedPrivateKey checks if the provided data is a PEM-encoded private key func isPEMEncodedPrivateKey(data []byte) bool { // Decode the PEM data block, _ := pem.Decode(data) if block == nil { return false } var err error switch block.Type { case "PRIVATE KEY": _, err = x509.ParsePKCS8PrivateKey(block.Bytes) case "RSA PRIVATE KEY": _, err = x509.ParsePKCS1PrivateKey(block.Bytes) case "EC PRIVATE KEY": _, err = x509.ParseECPrivateKey(block.Bytes) default: return false } if err != nil { log.Printf("isPEMEncodedPrivateKey: %v", err) return false } return true } // isPEMEncodedCertChain checks if the provided data is a concatenation of a PEM-encoded // intermediate certificate followed by a root certificate func isPEMEncodedCertChain(data []byte) bool { // Decode the PEM blocks one by one blockCnt := 0 for len(data) > 0 { var block *pem.Block block, data = pem.Decode(data) if block == nil { break } if block.Type != "CERTIFICATE" { return false } // Parse the certificate to ensure it is valid _, err := x509.ParseCertificate(block.Bytes) if err != nil { return false } blockCnt++ } // we want exactly two blocks in the certificate chain - intermediate and root return blockCnt == 2 } cosign-2.5.0/test/piv_test.go000066400000000000000000000146561477503325500161720ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build resetyubikey && e2e && !pivkeydisabled // +build resetyubikey,e2e,!pivkeydisabled // DANGER // This test requires a yubikey to be present. It WILL reset the yubikey to exercise functionality. // DO NOT RUN THIS TEST IF YOU DO NOT WANT TO RESET YOUR YUBIKEY // This requires the "resetyubikey" tag to be passed to go test. // See https://docs.sigstore.dev/key_management/hardware-based-tokens/#tested-devices package test import ( "context" "crypto/x509" "testing" // Import the functions directly for testing. . "github.com/sigstore/cosign/v2/cmd/cosign/cli/pivcli" ) func TestSetManagementKeyCmd(t *testing.T) { ctx := context.Background() Confirm = func(_ string) bool { return true } must(ResetKeyCmd(ctx), t) // The key should be the default management key so this should fail mustErr(SetManagementKeyCmd(ctx, "foobar", "newkey", false), t) must(SetManagementKeyCmd(ctx, "", "newkey", false), t) // Now it should fail with the wrong key mustErr(SetManagementKeyCmd(ctx, "", "otherkey", false), t) // But pass if we use the right key must(SetManagementKeyCmd(ctx, "newkey", "otherkey", false), t) // Reset and try a random key too! must(ResetKeyCmd(ctx), t) must(SetManagementKeyCmd(ctx, "", "", true), t) mustErr(SetManagementKeyCmd(ctx, "", "", true), t) } func TestSetPUKCmd(t *testing.T) { ctx := context.Background() Confirm = func(_ string) bool { return true } must(ResetKeyCmd(ctx), t) // The PUK should be the default key so this should fail mustErr(SetPukCmd(ctx, "11111111", "12121212"), t) must(SetPukCmd(ctx, "", "12121212"), t) // Now it should fail with the wrong key mustErr(SetPukCmd(ctx, "", "43214321"), t) // But pass if we use the right key must(SetPukCmd(ctx, "12121212", "43214321"), t) } func TestSetPinCmd(t *testing.T) { ctx := context.Background() Confirm = func(_ string) bool { return true } must(ResetKeyCmd(ctx), t) // The PIN should be the default PIN so this should fail mustErr(SetPinCmd(ctx, "111111", "222222"), t) must(SetPinCmd(ctx, "", "222222"), t) // Now it should fail with the wrong key mustErr(SetPinCmd(ctx, "333333", "444444"), t) // But pass if we use the right key must(SetPinCmd(ctx, "222222", "111111"), t) } func TestUnblockCmd(t *testing.T) { ctx := context.Background() Confirm = func(_ string) bool { return true } must(ResetKeyCmd(ctx), t) // Set a PUK must(SetPukCmd(ctx, "", "43214321"), t) // Set the pin to something, then lock the device by trying the wrong one too many times. must(SetPinCmd(ctx, "", "111111"), t) for i := 0; i < 5; i++ { mustErr(SetPinCmd(ctx, "222222", "333333"), t) } // Now even with the right PIN it should be stuck mustErr(SetPinCmd(ctx, "111111", "222222"), t) // But we can unblock it must(UnblockCmd(ctx, "43214321", "222222"), t) must(SetPinCmd(ctx, "222222", "333333"), t) } func TestGenerateKeyCmd(t *testing.T) { ctx := context.Background() Confirm = func(_ string) bool { return true } must(ResetKeyCmd(ctx), t) // This should work with the default key must(GenerateKeyCmd(ctx, "", false, "", "", ""), t) // Set the key to something other than the default must(SetManagementKeyCmd(ctx, "", "mynewkey", false), t) // Now this should fail mustErr(GenerateKeyCmd(ctx, "", false, "", "", ""), t) // Unless we use the right key must(GenerateKeyCmd(ctx, "mynewkey", false, "", "", ""), t) // Now if we use a random key it should set a new one must(GenerateKeyCmd(ctx, "mynewkey", true, "", "", ""), t) // The old one shouldn't work. mustErr(GenerateKeyCmd(ctx, "mynewkey", false, "", "", ""), t) } func TestAttestationCmd(t *testing.T) { ctx := context.Background() Confirm = func(_ string) bool { return true } must(ResetKeyCmd(ctx), t) must(GenerateKeyCmd(ctx, "", false, "", "", ""), t) attestations, err := AttestationCmd(ctx, "") if err != nil { t.Fatal(err) } root := x509.NewCertPool() if !root.AppendCertsFromPEM([]byte(yubicoCert)) { t.Fatal("error adding roots") } // Check the device against the manufacturer if _, err := attestations.DeviceCert.Verify(x509.VerifyOptions{ Roots: root, }); err != nil { t.Fatal(err) } intermediate := x509.NewCertPool() intermediate.AddCert(attestations.DeviceCert) // Now check the key, with the device as a chain if _, err := attestations.KeyCert.Verify(x509.VerifyOptions{ Roots: root, Intermediates: intermediate, }); err != nil { // This is known to fail on YubiKey firmware 4.3 // See https://labanskoller.se/blog/2019/12/30/pki-is-hard-how-yubico-trusted-openssl-and-got-it-wrong/ // if attestations.KeyAttestation.Version.Major == 4 && attestations.KeyAttestation.Version.Minor == 3 { t.Skipf("key attestation cert chain verification is known to be broken on firmware 4.3") } else { t.Fatal(err) } } } const yubicoCert = `-----BEGIN CERTIFICATE----- MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1 YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg U2VyaWFsIDI2Mzc1MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMN2 cMTNR6YCdcTFRxuPy31PabRn5m6pJ+nSE0HRWpoaM8fc8wHC+Tmb98jmNvhWNE2E ilU85uYKfEFP9d6Q2GmytqBnxZsAa3KqZiCCx2LwQ4iYEOb1llgotVr/whEpdVOq joU0P5e1j1y7OfwOvky/+AXIN/9Xp0VFlYRk2tQ9GcdYKDmqU+db9iKwpAzid4oH BVLIhmD3pvkWaRA2H3DA9t7H/HNq5v3OiO1jyLZeKqZoMbPObrxqDg+9fOdShzgf wCqgT3XVmTeiwvBSTctyi9mHQfYd2DwkaqxRnLbNVyK9zl+DzjSGp9IhVPiVtGet X02dxhQnGS7K6BO0Qe8CAwEAAaNCMEAwHQYDVR0OBBYEFMpfyvLEojGc6SJf8ez0 1d8Cv4O/MA8GA1UdEwQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 DQEBCwUAA4IBAQBc7Ih8Bc1fkC+FyN1fhjWioBCMr3vjneh7MLbA6kSoyWF70N3s XhbXvT4eRh0hvxqvMZNjPU/VlRn6gLVtoEikDLrYFXN6Hh6Wmyy1GTnspnOvMvz2 lLKuym9KYdYLDgnj3BeAvzIhVzzYSeU77/Cupofj093OuAswW0jYvXsGTyix6B3d bW5yWvyS9zNXaqGaUmP3U9/b6DlHdDogMLu3VLpBB9bm5bjaKWWJYgWltCVgUbFq Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8 SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7 -----END CERTIFICATE-----` cosign-2.5.0/test/pkcs11_test.go000066400000000000000000000417471477503325500164770ustar00rootroot00000000000000// Copyright 2021 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build !pkcs11keydisabled && softhsm // +build !pkcs11keydisabled,softhsm // DANGER // This test requires SoftHSMv2 to be installed. An initialized token should already exist. // This test will import an RSA key pair, using the specified token label. // By default, the test assumes the following : // - The SoftHSMv2 library is located at "/usr/local/lib/softhsm/libsofthsm2.so" // - The initialized token has the label "My Token" // - The initialized token has the pin "1234" // - The test will import the key pair using the key label "My Key" // These values can be overriden using the following environment variable : // - SOFTHSM_LIB // - SOFTHSM_TOKENLABEL // - SOFTHSM_PIN // - SOFTHSM_KEYLABEL // By default, the test makes use of the following SoftHSMv2 configuration files : // - /etc/softhsm2.conf // - /etc/softhsm.conf // These values can be overriden using the following environment variable : // - SOFTHSM2_CONF // - SOFTHSM_CONF package test import ( "bytes" "context" "crypto/rsa" "crypto/x509" "encoding/hex" "encoding/pem" "fmt" "io" "math/big" "os" "strings" "testing" // Import the functions directly for testing. "github.com/miekg/pkcs11" . "github.com/sigstore/cosign/v2/cmd/cosign/cli/pkcs11cli" "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" "github.com/stretchr/testify/require" ) var ( modulePath = "/usr/local/lib/softhsm/libsofthsm2.so" tokenLabel = "My Token" pin = "1234" keyLabel = "My Key" keyID = "355d2d0b569a2a0169e46b82e172cf99aca41400" uri = "" ) func init() { if x := os.Getenv("SOFTHSM_LIB"); x != "" { modulePath = x } if x := os.Getenv("SOFTHSM_TOKENLABEL"); x != "" { tokenLabel = x } if x := os.Getenv("SOFTHSM_PIN"); x != "" { pin = x } if x := os.Getenv("SOFTHSM_KEYLABEL"); x != "" { keyLabel = x } if x := os.Getenv("SOFTHSM_CONF"); x == "" { os.Setenv("SOFTHSM_CONF", "/etc/softhsm.conf") } if x := os.Getenv("SOFTHSM2_CONF"); x == "" { os.Setenv("SOFTHSM2_CONF", "/etc/softhsm2.conf") } keyIDBytes, _ := hex.DecodeString(keyID) pkcs11UriConfig := pkcs11key.NewPkcs11UriConfigFromInput(modulePath, nil, tokenLabel, []byte(keyLabel), keyIDBytes, pin) uri, _ = pkcs11UriConfig.Construct() } func TestParsePKCS11URI(t *testing.T) { _ = context.Background() uriString := "pkcs11:" uriString += "library-manufacturer=manufacturer;library-description=description;library-version=1;" uriString += "slot-manufacturer=manufacturer;slot-description=description;slot-id=1;" uriString += "manufacturer=manufacturer;model=model;serial=12345678;token=token%20label;" uriString += "type=private;object=key%20label;id=%6b%65%79%5f%69%64" uriString += "?" uriString += "module-path=/path/to/some%20folder/libmodule.so&module-name=libmodule.so&" uriString += "pin-value=1234&pin-source=/path/to/pinfile" pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig() must(pkcs11UriConfig.Parse(uriString), t) require.Equal(t, pkcs11UriConfig.KeyID, []byte("key_id")) require.Equal(t, pkcs11UriConfig.KeyLabel, []byte("key label")) require.Equal(t, pkcs11UriConfig.ModulePath, "/path/to/some folder/libmodule.so") require.Equal(t, pkcs11UriConfig.Pin, "1234") require.Equal(t, *pkcs11UriConfig.SlotID, 1) require.Equal(t, pkcs11UriConfig.TokenLabel, "token label") } func TestConstructPKCS11URI(t *testing.T) { _ = context.Background() uri := "pkcs11:token=token%20label;slot-id=1;id=%6b%65%79%5f%69%64;object=key%20label" uri += "?" uri += "module-path=/path/to/some%20folder/libmodule.so&pin-value=1234" slotID := 1 pkcs11UriConfig := pkcs11key.NewPkcs11UriConfigFromInput("/path/to/some folder/libmodule.so", &slotID, "token label", []byte("key label"), []byte("key_id"), "1234") uriString, err := pkcs11UriConfig.Construct() require.NoError(t, err) require.Equal(t, uri, uriString) } func TestListTokensCmd(t *testing.T) { ctx := context.Background() tokens, err := GetTokens(ctx, modulePath) if err != nil { t.Fatal(err) } bTokenFound := false for _, token := range tokens { if token.TokenInfo.Label == tokenLabel { bTokenFound = true break } } if !bTokenFound { t.Fatalf("token with label '%s' not found", tokenLabel) } } func TestListKeysUrisCmd(t *testing.T) { ctx := context.Background() tokens, err := GetTokens(ctx, modulePath) if err != nil { t.Fatal(err) } bTokenFound := false var slotID uint for _, token := range tokens { if token.TokenInfo.Label == tokenLabel { bTokenFound = true slotID = token.Slot break } } if !bTokenFound { t.Fatalf("token with label '%s' not found", tokenLabel) } err = importKey(slotID) if err != nil { t.Fatal(err) } defer deleteKey(slotID) keysInfo, err := GetKeysInfo(ctx, modulePath, slotID, pin) if err != nil { t.Fatal(err) } bKeyFound := false for _, keyInfo := range keysInfo { if hex.EncodeToString(keyInfo.KeyID) == keyID && string(keyInfo.KeyLabel) == keyLabel { foundUriConfig := pkcs11key.NewPkcs11UriConfig() err = foundUriConfig.Parse(keyInfo.KeyURI) if err != nil { t.Fatal(err) } uriConfig := pkcs11key.NewPkcs11UriConfig() err = uriConfig.Parse(uri) if err != nil { t.Fatal(err) } if foundUriConfig.TokenLabel == uriConfig.TokenLabel && bytes.Compare(foundUriConfig.KeyID, uriConfig.KeyID) == 0 && bytes.Compare(foundUriConfig.KeyLabel, uriConfig.KeyLabel) == 0 && foundUriConfig.ModulePath == uriConfig.ModulePath && foundUriConfig.Pin == uriConfig.Pin { bKeyFound = true } break } } if !bKeyFound { t.Fatalf("key not found") } } func TestCertificateIgnored(t *testing.T) { ctx := context.Background() tokens, err := GetTokens(ctx, modulePath) if err != nil { t.Fatal(err) } bTokenFound := false var slotID uint for _, token := range tokens { if token.TokenInfo.Label == tokenLabel { bTokenFound = true slotID = token.Slot break } } if !bTokenFound { t.Fatalf("token with label '%s' not found", tokenLabel) } err = importKey(slotID) if err != nil { t.Fatal(err) } defer deleteKey(slotID) pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig() err = pkcs11UriConfig.Parse(uri) if err != nil { t.Fatal(err) } const envvar = "COSIGN_PKCS11_IGNORE_CERTIFICATE" if err := os.Setenv(envvar, "1"); err != nil { t.Fatal(err) } defer os.Setenv(envvar, "") sk, err := pkcs11key.GetKeyWithURIConfig(pkcs11UriConfig, true) if err != nil { t.Fatal(err) } defer sk.Close() cert, err := sk.Certificate() if err != nil { t.Fatal(err) } if cert != nil { t.Fatalf("expected certificate to be ignored while loading") } } func TestSignAndVerify(t *testing.T) { ctx := context.Background() tokens, err := GetTokens(ctx, modulePath) if err != nil { t.Fatal(err) } bTokenFound := false var slotID uint for _, token := range tokens { if token.TokenInfo.Label == tokenLabel { bTokenFound = true slotID = token.Slot break } } if !bTokenFound { t.Fatalf("token with label '%s' not found", tokenLabel) } err = importKey(slotID) if err != nil { t.Fatal(err) } defer deleteKey(slotID) pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig() err = pkcs11UriConfig.Parse(uri) if err != nil { t.Fatal(err) } sk, err := pkcs11key.GetKeyWithURIConfig(pkcs11UriConfig, true) if err != nil { t.Fatal(err) } defer sk.Close() sv, err := sk.SignerVerifier() if err != nil { t.Fatal(err) } v, err := sk.Verifier() if err != nil { t.Fatal(err) } sig, err := sv.SignMessage(bytes.NewReader([]byte("hello, world!"))) if err != nil { t.Fatal(err) } err = v.VerifySignature(bytes.NewReader(sig), bytes.NewReader([]byte("hello, world!"))) if err != nil { t.Fatal(err) } } var newPublicKeyAttrs = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, false), pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true), } var newPrivateKeyAttrs = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true), pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true), pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false), pkcs11.NewAttribute(pkcs11.CKA_SIGN, true), } func rsaImportAttrs(priv *rsa.PrivateKey) (pubAttrs, privAttrs []*pkcs11.Attribute) { pubAttrs = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(priv.E)).Bytes()), pkcs11.NewAttribute(pkcs11.CKA_MODULUS, priv.N.Bytes()), } privAttrs = []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(priv.E)).Bytes()), pkcs11.NewAttribute(pkcs11.CKA_MODULUS, priv.N.Bytes()), pkcs11.NewAttribute(pkcs11.CKA_PRIVATE_EXPONENT, priv.D.Bytes()), pkcs11.NewAttribute(pkcs11.CKA_PRIME_1, priv.Primes[0].Bytes()), pkcs11.NewAttribute(pkcs11.CKA_PRIME_2, priv.Primes[1].Bytes()), pkcs11.NewAttribute(pkcs11.CKA_EXPONENT_1, priv.Precomputed.Dp.Bytes()), pkcs11.NewAttribute(pkcs11.CKA_EXPONENT_2, priv.Precomputed.Dq.Bytes()), pkcs11.NewAttribute(pkcs11.CKA_COEFFICIENT, priv.Precomputed.Qinv.Bytes()), } return } func attrConcat(attrSets ...[]*pkcs11.Attribute) []*pkcs11.Attribute { ret := make([]*pkcs11.Attribute, 0) for _, attrs := range attrSets { ret = append(ret, attrs...) } return ret } func initPKCS11(modulePath string) (*pkcs11.Ctx, error) { ctx := pkcs11.New(modulePath) if ctx == nil { return nil, fmt.Errorf("unable to load PKCS#11 module") } err := ctx.Initialize() if err != nil { return nil, fmt.Errorf("unable to initialize PKCS#11 module") } return ctx, nil } func importKey(slotID uint) error { var pemBytes []byte var priv interface{} ctx, err := initPKCS11(modulePath) if err != nil { return err } defer func() { ctx.Finalize() ctx.Destroy() }() keyIDBytes, err := hex.DecodeString(keyID) if err != nil { return err } keyLabelBytes := []byte(keyLabel) r := strings.NewReader(rsaPrivKey) pemBytes, err = io.ReadAll(r) if err != nil { return fmt.Errorf("unable to read pem") } block, _ := pem.Decode(pemBytes) if block == nil { return fmt.Errorf("unable to decode pem") } priv, err = x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return fmt.Errorf("unable to parse pem") } privKey, ok := priv.(*rsa.PrivateKey) if !ok { return fmt.Errorf("unable to load key") } session, err := ctx.OpenSession(slotID, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) if err != nil { return fmt.Errorf("unable to open session") } defer ctx.CloseSession(session) err = ctx.Login(session, pkcs11.CKU_USER, pin) if err != nil { return fmt.Errorf("unable to login") } defer ctx.Logout(session) keyType := pkcs11.CKK_RSA pubTypeAttrs, privTypeAttrs := rsaImportAttrs(privKey) commonAttrs := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, keyType), pkcs11.NewAttribute(pkcs11.CKA_ID, keyIDBytes), pkcs11.NewAttribute(pkcs11.CKA_LABEL, keyLabelBytes), } pubAttrs := attrConcat(commonAttrs, newPublicKeyAttrs, pubTypeAttrs) privAttrs := attrConcat(commonAttrs, newPrivateKeyAttrs, privTypeAttrs) pubHandle, err := ctx.CreateObject(session, pubAttrs) if err != nil { return fmt.Errorf("unable to create public key") } _, err = ctx.CreateObject(session, privAttrs) if err != nil { ctx.DestroyObject(session, pubHandle) return fmt.Errorf("unable to create private key") } return nil } func deleteKey(slotID uint) error { var handles []pkcs11.ObjectHandle ctx, err := initPKCS11(modulePath) if err != nil { return err } defer func() { ctx.Finalize() ctx.Destroy() }() keyIDBytes, err := hex.DecodeString(keyID) if err != nil { return err } keyLabelBytes := []byte(keyLabel) session, err := ctx.OpenSession(slotID, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) if err != nil { return fmt.Errorf("unable to open session") } defer ctx.CloseSession(session) err = ctx.Login(session, pkcs11.CKU_USER, pin) if err != nil { return fmt.Errorf("unable to login") } defer ctx.Logout(session) maxHandlePerFind := 20 publicAttrs := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), pkcs11.NewAttribute(pkcs11.CKA_ID, keyIDBytes), pkcs11.NewAttribute(pkcs11.CKA_LABEL, keyLabelBytes), } if err = ctx.FindObjectsInit(session, publicAttrs); err != nil { return fmt.Errorf("unable to initialize find objects") } handles, _, err = ctx.FindObjects(session, maxHandlePerFind) if err != nil { return fmt.Errorf("unable to find objects") } err = ctx.FindObjectsFinal(session) if err != nil { return fmt.Errorf("unable to finalize find objects") } if len(handles) == 1 { ctx.DestroyObject(session, handles[0]) if err != nil { return fmt.Errorf("unable to destroy public key") } } privAttrs := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), pkcs11.NewAttribute(pkcs11.CKA_ID, keyIDBytes), pkcs11.NewAttribute(pkcs11.CKA_LABEL, keyLabelBytes), } if err = ctx.FindObjectsInit(session, privAttrs); err != nil { return fmt.Errorf("unable to initialize find objects") } handles, _, err = ctx.FindObjects(session, maxHandlePerFind) if err != nil { return fmt.Errorf("unable to find objects") } err = ctx.FindObjectsFinal(session) if err != nil { return fmt.Errorf("unable to finalize find objects") } if len(handles) == 1 { ctx.DestroyObject(session, handles[0]) if err != nil { return fmt.Errorf("unable to destroy private key") } } return nil } func must(err error, t *testing.T) { t.Helper() if err != nil { t.Fatal(err) } } func mustErr(err error, t *testing.T) { t.Helper() if err == nil { t.Fatal("expected error") } } const rsaPrivKey = `-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZJZ44vB04D2wm xz+3upmuWelrTWcceVC2v6fkBo9dIR9IejolFY+CsMF1Rc5LGXG3XStQHRrbmq1w UxC8jsIOK7gI2xI9IOwCgyaQun3J+1VQc6eZxHLGQfTTNq7Vx67VOG8V8d3RhN7L BvAMT5U55254bUgH0KVx5C1ybLcX6BdGaABCunh7tV+thgwEZSbr2/t0Tf8QneMr eHojTKZp7/d90TH8KF+/FiPWJWWv5OVhjpZPwTWqUgL+6pgrMKUSWD/92JSDIZe6 UjxASE4JgnJWMQUhkerJ7j5P16gjdAwJAAt5m3L6wdfVQG2aZ9CJzUowk12ly4Dc Dx73/UufAgMBAAECggEAF8iA/eHMqXk29UBZgDwV3PzIDhKaOoonBv0S3GzDgwW/ sWaBu9ISt9O4PKn6oEsXI2g2+D1X1bmpSWYvrRdNtdOgAohMBRn3/4Zx0OQ8JsU6 YOdp8fOMRp6uu/t/RrbqNTxLHnIxQ2N0K3SFEjQdOgxZEyOVAhYeKM0/FQtHOnzj WoyZHT8pV3mr6WnxBw/4u/1Ahfau7fs6aVJLECc9jGF/6e7aQeb+yEeLrHayml8e sbBx4l/1LqU/2S7SQrWtQ+fi+/MlgxvLh0XC7tTPP6I3cTetyMZime9EwwDiPebX PLUgo8Kf/sHzd/25G9M3Yz+UCLemcPSMUjBUQTPtYQKBgQD8lnpjekyeOjNCdRVP 5w6h1wGN4aC4bCksZ89HKpHc44+3AjDT/aVviory+CyOj05qbXDdpNnNh+jl5llM yDw15WIvSsXFx3UQ467VVrBKm7vr+k1LGgLJ2fSFbZUTyLvwW4NpP26KDW6SitZ8 B9lkepTZ0G4Eao51VgidHsulKQKBgQDcFJNAIctqUWDli4tA5L0G5tiypcAA7iIZ 0h2YK+7eOU2f3r8aaywbPhcRn+cKlrf3iV4BCZAv59WEJqq1HOlzU92jkmZspYPq 8kSZLaaiDIBw+vwV4prHDSdZFEY+hHq5eULPIgVm/M474JcghetkVt8pG3ee+Dml o6zUrZr7hwKBgQDCiXbrpObbuoF+PsTSTGfFl923k74ALDWt4KoQ6qV61bz7O3G1 5BYFiVOo/CD9Dzxa1b1mx6+ED5f9cOL4MwPEks2DFPircgoknucpomGWpMkgXyAm pnrdUcN0/Egj+6db4G+eoN8W7m9p6Ap3bmgtbge0lkYVmqfrkP6DXJOFuQKBgHA/ hkMFeYyGaRdqruGwSMEGaKvlYiKXUok8459DeReavn61y16cHujeKEHy/pImATqd s3Zv/DyS0BIQ7qxlTKRnt/m/p8HuQXRJkLdX009/dNsrB/vZkfvIN7N1ZcZpJ3cF 5A9lWMAIXN+pUythYofQzw1WVxKbpDtZWcM3sH5tAoGBAMHgZdtmIyllx/1BbYSg Emxj3LekvZL0e7afeod9f977ZETt/imaejnJNnGOPeSbtLSPfhwonLEp+5XmICzt lJZAF8iP2m1n9h8sZga5rZQ0JgiwVNFNwde4sp1pD5UcFrYepHRxKPo50eJi3rhR SwNAKWa96qm5o8BaQu/aRMRu -----END PRIVATE KEY-----` const rsaCert = `-----BEGIN CERTIFICATE----- MIIDazCCAlOgAwIBAgIUL7BdF7HSUwEAdqElJjVLQYd2OekwDQYJKoZIhvcNAQEL BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTExMDIxNzM3MzJaFw0zMTEw MzExNzM3MzJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDZJZ44vB04D2wmxz+3upmuWelrTWcceVC2v6fkBo9d IR9IejolFY+CsMF1Rc5LGXG3XStQHRrbmq1wUxC8jsIOK7gI2xI9IOwCgyaQun3J +1VQc6eZxHLGQfTTNq7Vx67VOG8V8d3RhN7LBvAMT5U55254bUgH0KVx5C1ybLcX 6BdGaABCunh7tV+thgwEZSbr2/t0Tf8QneMreHojTKZp7/d90TH8KF+/FiPWJWWv 5OVhjpZPwTWqUgL+6pgrMKUSWD/92JSDIZe6UjxASE4JgnJWMQUhkerJ7j5P16gj dAwJAAt5m3L6wdfVQG2aZ9CJzUowk12ly4DcDx73/UufAgMBAAGjUzBRMB0GA1Ud DgQWBBRokgD44sdsSGQEQcbJ3vrCrXTIcTAfBgNVHSMEGDAWgBRokgD44sdsSGQE QcbJ3vrCrXTIcTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA8 +CpbIGi4ycCcSeomBzGVXsTFgFutqqvh3BFQ1u6bPlV7hIlFd11zzgWBeKKxREJn z3SipT1qGX+uP4iVhUux94f2rQCV25mJNRKft2phAUylMr+laiO7IkHFB1zzJTfz Bi9gm55HGvGCIdSWFkLZ/MUNCMj3WtPrUYl5jqFgDDmCpLctmPoN4vxSa0of3apv ILH8jSsN5XbL8G1hsT/IGlRRbzoiLCKgCp6e6TjZSq/Y+JWGyw/+sZJMI8Mg4Mje 054uJhD29xmbfxdYxrMWLAFb6yoWVbDJPdECFf9uwOXyDZ8bGd48frTdUU3Rb+m3 5Hue2g5US98p2jnJiv75 -----END CERTIFICATE-----` cosign-2.5.0/test/testdata/000077500000000000000000000000001477503325500156035ustar00rootroot00000000000000cosign-2.5.0/test/testdata/attestations/000077500000000000000000000000001477503325500203255ustar00rootroot00000000000000cosign-2.5.0/test/testdata/attestations/vuln-predicate.json000066400000000000000000000007131477503325500241430ustar00rootroot00000000000000{ "invocation": { "parameters": null, "uri": "invocation.example.com/cosign-testing", "event_id": "", "builder.id": "" }, "scanner": { "uri": "fakescanner.example.com/cosign-testing", "version": "", "db": { "uri": "", "version": "" }, "result": null }, "metadata": { "scanStartedOn": "2022-04-12T00:00:00Z", "scanFinishedOn": "2022-04-12T00:10:00Z" } } cosign-2.5.0/test/testdata/bom-go-mod.cyclonedx.json000066400000000000000000000003241477503325500224210ustar00rootroot00000000000000{ "bomFormat": "CycloneDX", "specVersion": "1.4", "serialNumber": "urn:uuid:9b0c2427-be94-439c-82e5-8928db124270", "version": 1, "metadata": {}, "components": [], "dependencies": [] } cosign-2.5.0/test/testdata/bom-go-mod.spdx000066400000000000000000005217441477503325500204550ustar00rootroot00000000000000SPDXVersion: SPDX-2.2 DataLicense: CC0-1.0 SPDXID: SPDXRef-DOCUMENT DocumentName: github.com/sigstore/cosign DocumentNamespace: http://spdx.org/spdxpackages/github.com/sigstore/cosign-9a615223-fdf6-40cc-8156-666d7da86672 Creator: Tool: spdx-sbom-generator-0.0.1 Created: 2021-06-27T13:42:53Z ##### Package representing the github.com/sigstore/cosign PackageName: github.com/sigstore/cosign SPDXID: SPDXRef-Package-github.com.sigstore.cosign PackageVersion: ddd9132 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 0d8975f6ff7067f2adad485924e0854caec9c5d46306bf5b901d311c2fb05a7a PackageHomePage: https://github.com/sigstore/cosign PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-github.com.sigstore.cosign Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-k8s.io.apimachinery-v0.21.2 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.leodido.go-urn-v1.2.1 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.peterbourgon.ff.v3-v3.0.0 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.cyberphone.json-canonicalization-v0.0.0-20210303052042-6bc126869bf4 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.spf13.afero-v1.6.0 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.theupdateframework.go-tuf-v0.0.0-20201230183259-aee6270feb55 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.google.gofuzz-v1.2.0 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.prometheus.common-v0.29.0 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-go.uber.org.multierr-v1.7.0 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-golang.org.x.term-v0.0.0-20210615171337-6886f2dfbf5b Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-k8s.io.api-v0.21.2 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-k8s.io.client-go-v0.21.2 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.jedisct1.go-minisign-v0.0.0-20210414164026-819d7e2534ac Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.magiconair.properties-v1.8.5 Relationship SPDXRef-Package-github.com.sigstore.cosign DEPENDS_ON SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 ##### Package representing the github.com/google/go-containerregistry PackageName: github.com/google/go-containerregistry SPDXID: SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 PackageVersion: v0.5.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 9f611b3f2b625e9f4af823b9833dfd4317f575a270cd6c156ee9f5b89ec49454 PackageHomePage: https://github.com/google/go-containerregistry PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-cloud.google.com.go-v0.81.0 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.docker.cli-v0.0.0-20191017083524-a8ff7f821017 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.opencontainers.image-spec-v1.0.1 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.sirupsen.logrus-v1.8.1 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-k8s.io.klog.v2-v2.8.0 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.containerd.stargz-snapshotter.estargz-v0.4.1 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.docker.docker-v1.4.2-0.20200319182547-c7ad2b866182 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.docker.docker-credential-helpers-v0.6.3 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.spf13.cobra-v1.1.3 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.gorilla.mux-v1.8.0 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.docker.distribution-v2.7.1+incompatible Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c ##### Package representing the github.com/peterbourgon/ff/v3 PackageName: github.com/peterbourgon/ff/v3 SPDXID: SPDXRef-Package-github.com.peterbourgon.ff.v3-v3.0.0 PackageVersion: v3.0.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 717d2deecd326f89ac03355a0c60e7af8cec09cdf35c739c9db64677b7f767ae PackageHomePage: https://github.com/peterbourgon/ff/v3 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.peterbourgon.ff.v3-v3.0.0 DEPENDS_ON SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 Relationship SPDXRef-Package-github.com.peterbourgon.ff.v3-v3.0.0 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 ##### Package representing the github.com/pkg/errors PackageName: github.com/pkg/errors SPDXID: SPDXRef-Package-github.com.pkg.errors-v0.9.1 PackageVersion: v0.9.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e984effb1b0eef5e6cfefb3f37f23bfaee28d5180f11c64fb43a46b7f7421edb PackageHomePage: https://github.com/pkg/errors PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/docker/cli PackageName: github.com/docker/cli SPDXID: SPDXRef-Package-github.com.docker.cli-v0.0.0-20191017083524-a8ff7f821017 PackageVersion: v0.0.0-20191017083524-a8ff7f821017 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 0e16595382ffbd6bd8cd20be937e87ad48d5e20d678fb52b4913740565cb5e26 PackageHomePage: https://github.com/docker/cli PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/docker/docker-credential-helpers PackageName: github.com/docker/docker-credential-helpers SPDXID: SPDXRef-Package-github.com.docker.docker-credential-helpers-v0.6.3 PackageVersion: v0.6.3 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 0a83f71929507b20f2c695a3a5833519fcf606d470948b083847e31c09433bee PackageHomePage: https://github.com/docker/docker-credential-helpers PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/docker/docker PackageName: github.com/docker/docker SPDXID: SPDXRef-Package-github.com.docker.docker-v1.4.2-0.20200319182547-c7ad2b866182 PackageVersion: v1.4.2-0.20200319182547-c7ad2b866182 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: b48d221328853f35e846891d066cbfbbfd0f0e8e15d37745d68215d4bfa663c5 PackageHomePage: https://github.com/docker/docker PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/opencontainers/go-digest PackageName: github.com/opencontainers/go-digest SPDXID: SPDXRef-Package-github.com.opencontainers.go-digest-v1.0.0 PackageVersion: v1.0.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 06744ce2bb1c85d4117a2ba9e9a817665e52ed5f36e9722e8fada53867a9fae1 PackageHomePage: https://github.com/opencontainers/go-digest PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/opencontainers/image-spec PackageName: github.com/opencontainers/image-spec SPDXID: SPDXRef-Package-github.com.opencontainers.image-spec-v1.0.1 PackageVersion: v1.0.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e3790a1082e695077024de83b32cfc198508585d3e37d430aaa325cfbe0c4a0f PackageHomePage: https://github.com/opencontainers/image-spec PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/docker/distribution PackageName: github.com/docker/distribution SPDXID: SPDXRef-Package-github.com.docker.distribution-v2.7.1+incompatible PackageVersion: v2.7.1+incompatible PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 1e591e4b18738469da7205179340f527fe732e0c3cba080abd084828a3a16d7f PackageHomePage: https://github.com/docker/distribution PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the golang.org/x/sync PackageName: golang.org/x/sync SPDXID: SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c PackageVersion: v0.0.0-20210220032951-036812b2e83c PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: c2c8d8b8e332215da159aa126456187286e44d97353e0566e77e350e5a8522f7 PackageHomePage: https://golang.org/x/sync PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/cyberphone/json-canonicalization PackageName: github.com/cyberphone/json-canonicalization SPDXID: SPDXRef-Package-github.com.cyberphone.json-canonicalization-v0.0.0-20210303052042-6bc126869bf4 PackageVersion: v0.0.0-20210303052042-6bc126869bf4 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: c7ebd3e43ca55f29f282acbf5e9194741d64f027cf57cc6d8136c0e6a30451a6 PackageHomePage: https://github.com/cyberphone/json-canonicalization PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/asaskevich/govalidator PackageName: github.com/asaskevich/govalidator SPDXID: SPDXRef-Package-github.com.asaskevich.govalidator-v0.0.0-20210307081110-f21760c49a8d PackageVersion: v0.0.0-20210307081110-f21760c49a8d PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 903cd7f5e7c6ac40eb2de2d6dfe3be0622f9bce5ad6c422fad4966939fcabf94 PackageHomePage: https://github.com/asaskevich/govalidator PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/go-openapi/errors PackageName: github.com/go-openapi/errors SPDXID: SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 PackageVersion: v0.20.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 51ad642cf65adce9300b5ea390808246e8feeed0bcd81fbbb88e1b070a1238d2 PackageHomePage: https://github.com/go-openapi/errors PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the github.com/mitchellh/mapstructure PackageName: github.com/mitchellh/mapstructure SPDXID: SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 PackageVersion: v1.4.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 3b17e495d6eceeb7e78bedaca087bcfc81f454c9a8dbce3ccec9071a0ecea479 PackageHomePage: https://github.com/mitchellh/mapstructure PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/oklog/ulid PackageName: github.com/oklog/ulid SPDXID: SPDXRef-Package-github.com.oklog.ulid-v1.3.1 PackageVersion: v1.3.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: d906eb8403330115effa40cdde3173f4187286eea72acd78a7f0383c437b875d PackageHomePage: https://github.com/oklog/ulid PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the go.mongodb.org/mongo-driver PackageName: go.mongodb.org/mongo-driver SPDXID: SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 PackageVersion: v1.5.3 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: a4ccad268cfd8a059f9b68ccd6f1ca5ae83cdab8c974dad74d75e53b9748fcb9 PackageHomePage: https://go.mongodb.org/mongo-driver PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-github.com.go-stack.stack-v1.8.0 Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-github.com.golang.snappy-v0.0.3 Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-github.com.sirupsen.logrus-v1.8.1 Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 Relationship SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 ##### Package representing the github.com/go-stack/stack PackageName: github.com/go-stack/stack SPDXID: SPDXRef-Package-github.com.go-stack.stack-v1.8.0 PackageVersion: v1.8.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: fd3f25fb63423cea94320198b7efaa88ed8e38cd2862f1be66b644e263c751d9 PackageHomePage: https://github.com/go-stack/stack PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/go-openapi/strfmt PackageName: github.com/go-openapi/strfmt SPDXID: SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 PackageVersion: v0.20.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 60fb8854b5971069860ab2102510a7eb28790a49eb2fb9d1aeb98a3f34833525 PackageHomePage: https://github.com/go-openapi/strfmt PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.asaskevich.govalidator-v0.0.0-20210307081110-f21760c49a8d Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.oklog.ulid-v1.3.1 Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-stack.stack-v1.8.0 Relationship SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 ##### Package representing the github.com/josharian/intern PackageName: github.com/josharian/intern SPDXID: SPDXRef-Package-github.com.josharian.intern-v1.0.0 PackageVersion: v1.0.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 02952b87b56340b825e2381df45a0b5eff5dd1bf290fdfada7dbe6e993a42076 PackageHomePage: https://github.com/josharian/intern PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/mailru/easyjson PackageName: github.com/mailru/easyjson SPDXID: SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 PackageVersion: v0.7.7 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 827a48dc29ec1ff4c2b1ec78221db470416ed364dac6a878893e9d21cefa6918 PackageHomePage: https://github.com/mailru/easyjson PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 DEPENDS_ON SPDXRef-Package-github.com.josharian.intern-v1.0.0 ##### Package representing the gopkg.in/yaml.v2 PackageName: gopkg.in/yaml.v2 SPDXID: SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 PackageVersion: v2.4.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 3aed1e3fc7b0a044fb38213f8c11cb20d5b45fd673e45de14c537648c446e6be PackageHomePage: https://gopkg.in/yaml.v2 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/go-openapi/swag PackageName: github.com/go-openapi/swag SPDXID: SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 PackageVersion: v0.19.15 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e725f85ebbbcc1e303a950fe6dfb589e4a6003989316fda6ac5514b818bc0830 PackageHomePage: https://github.com/go-openapi/swag PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the github.com/google/trillian PackageName: github.com/google/trillian SPDXID: SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f PackageVersion: v1.3.14-0.20210413093047-5e12fb368c8f PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 4dab9fd4947a0cb0710d75145cf7eeb32462b10c5a422170dff8aa54f0b64ae3 PackageHomePage: https://github.com/google/trillian PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.olekukonko.tablewriter-v0.0.5 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-model-v0.2.0 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-go.uber.org.zap-v1.16.0 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.mattn.go-runewidth-v0.0.9 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.spf13.cobra-v1.1.3 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-go.opencensus.io-v0.23.0 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-go.uber.org.multierr-v1.7.0 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.gogo.protobuf-v1.3.2 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.spf13.pflag-v1.0.5 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-github.com.imdario.mergo-v0.3.9 Relationship SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f DEPENDS_ON SPDXRef-Package-google.golang.org.api-v0.46.0 ##### Package representing the github.com/containerd/stargz-snapshotter/estargz PackageName: github.com/containerd/stargz-snapshotter/estargz SPDXID: SPDXRef-Package-github.com.containerd.stargz-snapshotter.estargz-v0.4.1 PackageVersion: v0.4.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 7c5efb46f869997b7bd0902b05024d6b3d8575e9d42a5ea11e37855aa8a8f8ae PackageHomePage: https://github.com/containerd/stargz-snapshotter/estargz PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.containerd.stargz-snapshotter.estargz-v0.4.1 DEPENDS_ON SPDXRef-Package-github.com.opencontainers.go-digest-v1.0.0 Relationship SPDXRef-Package-github.com.containerd.stargz-snapshotter.estargz-v0.4.1 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-github.com.containerd.stargz-snapshotter.estargz-v0.4.1 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 ##### Package representing the github.com/sigstore/sigstore PackageName: github.com/sigstore/sigstore SPDXID: SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 PackageVersion: v0.0.0-20210609084117-386ea718fc64 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: ce32f696897cc9eea099740ba18e1f2e345516f1c1a8ddf294c2662a7fca2c68 PackageHomePage: https://github.com/sigstore/sigstore PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.google.go-containerregistry-v0.5.1 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.asaskevich.govalidator-v0.0.0-20210307081110-f21760c49a8d Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.spf13.cobra-v1.1.3 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.pierrec.lz4-v2.6.0+incompatible Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.errwrap-v1.1.0 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-gopkg.in.square.go-jose.v2-v2.5.1 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-multierror-v1.1.1 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-cleanhttp-v0.5.2 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.segmentio.ksuid-v1.0.3 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-cloud.google.com.go-v0.81.0 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.coreos.go-oidc.v3-v3.0.0 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.go-homedir-v1.1.0 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.skratchdot.open-golang-v0.0.0-20200116055534-eef842397966 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.spf13.viper-v1.7.1 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-retryablehttp-v0.6.8 Relationship SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 DEPENDS_ON SPDXRef-Package-github.com.golang.snappy-v0.0.3 ##### Package representing the github.com/go-openapi/runtime PackageName: github.com/go-openapi/runtime SPDXID: SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 PackageVersion: v0.19.29 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 06262c89eea66a0d240bb2e2dcf20608e29cee7d946804545266e2e195f0399c PackageHomePage: https://github.com/go-openapi/runtime PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.opentracing.opentracing-go-v1.2.0 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.PuerkitoBio.purell-v1.1.1 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.asaskevich.govalidator-v0.0.0-20210307081110-f21760c49a8d Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 Relationship SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 ##### Package representing the github.com/go-openapi/analysis PackageName: github.com/go-openapi/analysis SPDXID: SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 PackageVersion: v0.20.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 44e10c5f201ef8dd4a7700d7a8729665fe0b8e22a506c381cda0f1b95d2cbf7f PackageHomePage: https://github.com/go-openapi/analysis PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.asaskevich.govalidator-v0.0.0-20210307081110-f21760c49a8d Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 ##### Package representing the github.com/go-openapi/jsonpointer PackageName: github.com/go-openapi/jsonpointer SPDXID: SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 PackageVersion: v0.19.5 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 1d3f0415980a62cec07254fc3e99ff4aebaa1f510b37968d6c44f7755520f3f0 PackageHomePage: https://github.com/go-openapi/jsonpointer PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the github.com/PuerkitoBio/urlesc PackageName: github.com/PuerkitoBio/urlesc SPDXID: SPDXRef-Package-github.com.PuerkitoBio.urlesc-v0.0.0-20170810143723-de5bf2ad4578 PackageVersion: v0.0.0-20170810143723-de5bf2ad4578 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: cf5a45a083cd213c91fb7d67e61188f8eaa1d362903b9ec91d8cf8d2494a050e PackageHomePage: https://github.com/PuerkitoBio/urlesc PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the golang.org/x/text PackageName: golang.org/x/text SPDXID: SPDXRef-Package-golang.org.x.text-v0.3.6 PackageVersion: v0.3.6 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: d880df5dea6c92d960d64f6fe6c27d88f226838eccbf4736497a5c9f1f7072fd PackageHomePage: https://golang.org/x/text PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the golang.org/x/net PackageName: golang.org/x/net SPDXID: SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 PackageVersion: v0.0.0-20210525063256-abc453219eb5 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 7796fea5e6483954436c0df99da0a78100ee5157c3b2e8fd99dcdaf157e7e398 PackageHomePage: https://golang.org/x/net PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 DEPENDS_ON SPDXRef-Package-golang.org.x.term-v0.0.0-20210615171337-6886f2dfbf5b Relationship SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e ##### Package representing the github.com/PuerkitoBio/purell PackageName: github.com/PuerkitoBio/purell SPDXID: SPDXRef-Package-github.com.PuerkitoBio.purell-v1.1.1 PackageVersion: v1.1.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 8c01b4b8b4e77bd82080a9a2072c7a9b8539d43b88cd419ee9dc3a04d4b905e0 PackageHomePage: https://github.com/PuerkitoBio/purell PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/go-openapi/jsonreference PackageName: github.com/go-openapi/jsonreference SPDXID: SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 PackageVersion: v0.19.5 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: c9b645f4b15ac8886ae3f1f2ce467af096ecc6b74d0c89cfcb044ec90a05161a PackageHomePage: https://github.com/go-openapi/jsonreference PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.PuerkitoBio.urlesc-v0.0.0-20170810143723-de5bf2ad4578 Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.PuerkitoBio.purell-v1.1.1 Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 ##### Package representing the github.com/go-openapi/spec PackageName: github.com/go-openapi/spec SPDXID: SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 PackageVersion: v0.20.3 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: de8a34d1f7e6459c9343a159264930fcc394d1cf38d33505638e5713306cf684 PackageHomePage: https://github.com/go-openapi/spec PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-github.com.PuerkitoBio.purell-v1.1.1 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-github.com.PuerkitoBio.urlesc-v0.0.0-20170810143723-de5bf2ad4578 Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 ##### Package representing the github.com/go-openapi/loads PackageName: github.com/go-openapi/loads SPDXID: SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 PackageVersion: v0.20.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 93af64986aaf16f34f4a9fb02445573eda636219ecf4e5c03eece7d4b05a1231 PackageHomePage: https://github.com/go-openapi/loads PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonreference-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.asaskevich.govalidator-v0.0.0-20210307081110-f21760c49a8d Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.PuerkitoBio.purell-v1.1.1 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.PuerkitoBio.urlesc-v0.0.0-20170810143723-de5bf2ad4578 ##### Package representing the github.com/go-openapi/validate PackageName: github.com/go-openapi/validate SPDXID: SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 PackageVersion: v0.20.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 57a89cb5a4af0a5df24c668749126006f2c0beb29e77d6915cdf54f07f1671b4 PackageHomePage: https://github.com/go-openapi/validate PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.asaskevich.govalidator-v0.0.0-20210307081110-f21760c49a8d Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-go.mongodb.org.mongo-driver-v1.5.3 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.analysis-v0.20.1 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.jsonpointer-v0.19.5 Relationship SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 ##### Package representing the github.com/opentracing/opentracing-go PackageName: github.com/opentracing/opentracing-go SPDXID: SPDXRef-Package-github.com.opentracing.opentracing-go-v1.2.0 PackageVersion: v1.2.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: dceb5ae504b6c9ad9d36469642fa86f586698d17cc5fd156f9019c688a6b246d PackageHomePage: https://github.com/opentracing/opentracing-go PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/go-playground/locales PackageName: github.com/go-playground/locales SPDXID: SPDXRef-Package-github.com.go-playground.locales-v0.13.0 PackageVersion: v0.13.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 5d0fbcd46768d469dcb9c098b5332a765f0ba65aeb4d46837f3a58aadf2d5f7c PackageHomePage: https://github.com/go-playground/locales PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-playground.locales-v0.13.0 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 ##### Package representing the github.com/go-playground/universal-translator PackageName: github.com/go-playground/universal-translator SPDXID: SPDXRef-Package-github.com.go-playground.universal-translator-v0.17.0 PackageVersion: v0.17.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e4729059a54c41612abd564f297f8b81c23a652da796a100243904de0621e292 PackageHomePage: https://github.com/go-playground/universal-translator PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.go-playground.universal-translator-v0.17.0 DEPENDS_ON SPDXRef-Package-github.com.go-playground.locales-v0.13.0 ##### Package representing the github.com/leodido/go-urn PackageName: github.com/leodido/go-urn SPDXID: SPDXRef-Package-github.com.leodido.go-urn-v1.2.1 PackageVersion: v1.2.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 9ddea28cd322b11a0794bdbc9ef7c08e2e0a5756536c1e16dacfa5a2dacc1ed4 PackageHomePage: https://github.com/leodido/go-urn PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/go-playground/validator PackageName: github.com/go-playground/validator SPDXID: SPDXRef-Package-github.com.go-playground.validator-v9.31.0+incompatible PackageVersion: v9.31.0+incompatible PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: f408f3474f4e7fc4db8a260b696a4907cd03fecf1e216aca2304230e1514392d PackageHomePage: https://github.com/go-playground/validator PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/mitchellh/go-homedir PackageName: github.com/mitchellh/go-homedir SPDXID: SPDXRef-Package-github.com.mitchellh.go-homedir-v1.1.0 PackageVersion: v1.1.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 756f9483a6678379c27563b85c209466b5b22c2006c88ece4ac5eb122edf05e7 PackageHomePage: https://github.com/mitchellh/go-homedir PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/spf13/pflag PackageName: github.com/spf13/pflag SPDXID: SPDXRef-Package-github.com.spf13.pflag-v1.0.5 PackageVersion: v1.0.5 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 1da4c58c01b8de4af39bf5292cf75da8788105be7316cf33bfb9083bd7cc0f68 PackageHomePage: https://github.com/spf13/pflag PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/spf13/cobra PackageName: github.com/spf13/cobra SPDXID: SPDXRef-Package-github.com.spf13.cobra-v1.1.3 PackageVersion: v1.1.3 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 13dae614a8a2f787f92a9e954ca6e630257bb651bd16bb47db56cd9307682759 PackageHomePage: https://github.com/spf13/cobra PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.spf13.cobra-v1.1.3 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.go-homedir-v1.1.0 Relationship SPDXRef-Package-github.com.spf13.cobra-v1.1.3 DEPENDS_ON SPDXRef-Package-github.com.spf13.pflag-v1.0.5 Relationship SPDXRef-Package-github.com.spf13.cobra-v1.1.3 DEPENDS_ON SPDXRef-Package-github.com.spf13.viper-v1.7.1 Relationship SPDXRef-Package-github.com.spf13.cobra-v1.1.3 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 ##### Package representing the golang.org/x/sys PackageName: golang.org/x/sys SPDXID: SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 PackageVersion: v0.0.0-20210615035016-665e8c7367d1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 094383cb925a141474b7d2dcff3d41acda43f0054ad69c3edf280dd185ddb14d PackageHomePage: https://golang.org/x/sys PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/fsnotify/fsnotify PackageName: github.com/fsnotify/fsnotify SPDXID: SPDXRef-Package-github.com.fsnotify.fsnotify-v1.4.9 PackageVersion: v1.4.9 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 5f5fc2b994c0deb384de9002d35c224d469876760f4380d19fda6831c69ee708 PackageHomePage: https://github.com/fsnotify/fsnotify PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.fsnotify.fsnotify-v1.4.9 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 ##### Package representing the github.com/hashicorp/hcl PackageName: github.com/hashicorp/hcl SPDXID: SPDXRef-Package-github.com.hashicorp.hcl-v1.0.0 PackageVersion: v1.0.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 895bcea8d965251000f22fadd48cfafbe9540fd5d1bb53a412df05f4eb9ee209 PackageHomePage: https://github.com/hashicorp/hcl PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.hashicorp.hcl-v1.0.0 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the github.com/magiconair/properties PackageName: github.com/magiconair/properties SPDXID: SPDXRef-Package-github.com.magiconair.properties-v1.8.5 PackageVersion: v1.8.5 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 8279b773031b12daaff1682a11727d765b41ea7f8ba07d82f41c2151dabf50cf PackageHomePage: https://github.com/magiconair/properties PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/pelletier/go-toml PackageName: github.com/pelletier/go-toml SPDXID: SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 PackageVersion: v1.9.3 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: f21c71d15e543d7bf5ab0de02b01d438920f9cf8c82f6e0eea6ebf4e2f71dfca PackageHomePage: https://github.com/pelletier/go-toml PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 ##### Package representing the github.com/spf13/afero PackageName: github.com/spf13/afero SPDXID: SPDXRef-Package-github.com.spf13.afero-v1.6.0 PackageVersion: v1.6.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 0ad525eccf932adf4198a0f145cf92b2ee2dde1f372115a082060411ebc02567 PackageHomePage: https://github.com/spf13/afero PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.spf13.afero-v1.6.0 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.spf13.afero-v1.6.0 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 ##### Package representing the github.com/spf13/cast PackageName: github.com/spf13/cast SPDXID: SPDXRef-Package-github.com.spf13.cast-v1.3.1 PackageVersion: v1.3.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 62b31a6b804bd52025bc90c20ebfb9330cc7d6d763fbdc4275daf18351526921 PackageHomePage: https://github.com/spf13/cast PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.spf13.cast-v1.3.1 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the github.com/spf13/jwalterweatherman PackageName: github.com/spf13/jwalterweatherman SPDXID: SPDXRef-Package-github.com.spf13.jwalterweatherman-v1.1.0 PackageVersion: v1.1.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 3a1bde328c7d9a65571ab45c07aedcc3a5ef9259fba27695e8b9b1e74332fd2b PackageHomePage: https://github.com/spf13/jwalterweatherman PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.spf13.jwalterweatherman-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the github.com/subosito/gotenv PackageName: github.com/subosito/gotenv SPDXID: SPDXRef-Package-github.com.subosito.gotenv-v1.2.0 PackageVersion: v1.2.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 100a4810859d117550e36abab7faacf5469ec9a5063af11c2eaf515e4788250f PackageHomePage: https://github.com/subosito/gotenv PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the gopkg.in/ini.v1 PackageName: gopkg.in/ini.v1 SPDXID: SPDXRef-Package-gopkg.in.ini.v1-v1.62.0 PackageVersion: v1.62.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: cc6da295ac6329f0763b4d19e87016b801b044bf966e77dbe27bea5b7923d6d7 PackageHomePage: https://gopkg.in/ini.v1 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/spf13/viper PackageName: github.com/spf13/viper SPDXID: SPDXRef-Package-github.com.spf13.viper-v1.7.1 PackageVersion: v1.7.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 46ff1c376bdd16a5b299764c028eceeb618852e1e0b08e50d000e54e12000410 PackageHomePage: https://github.com/spf13/viper PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.golang.groupcache-v0.0.0-20210331224755-41bb18bfe9da Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.spf13.cast-v1.3.1 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.subosito.gotenv-v1.2.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-go.uber.org.atomic-v1.7.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.spf13.jwalterweatherman-v1.1.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.fsnotify.fsnotify-v1.4.9 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.gogo.protobuf-v1.3.2 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.spf13.afero-v1.6.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.magiconair.properties-v1.8.5 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-gopkg.in.ini.v1-v1.62.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-go.uber.org.multierr-v1.7.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-go.uber.org.zap-v1.16.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.hcl-v1.0.0 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 Relationship SPDXRef-Package-github.com.spf13.viper-v1.7.1 DEPENDS_ON SPDXRef-Package-github.com.spf13.pflag-v1.0.5 ##### Package representing the github.com/sigstore/rekor PackageName: github.com/sigstore/rekor SPDXID: SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 PackageVersion: v0.1.2-0.20210519014330-b5480728bde6 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 80f722ace48bbeb352bdf56fc02058f966d5cf924243194631b322f93a0208df PackageHomePage: https://github.com/sigstore/rekor PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.blang.semver-v3.5.1+incompatible Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.cavaliercoder.go-rpm-v0.0.0-20200122174316-8cb9fd9c31a8 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.howeyc.gopass-v0.0.0-20190910152052-7cb4b85ec19c Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.spf13.afero-v1.6.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.spf13.cast-v1.3.1 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-chi.chi-v4.1.2+incompatible Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.ghodss.yaml-v1.0.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-playground.validator-v9.31.0+incompatible Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-go.uber.org.zap-v1.16.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.magiconair.properties-v1.8.5 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.spf13.jwalterweatherman-v1.1.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-gopkg.in.ini.v1-v1.62.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.asaskevich.govalidator-v0.0.0-20210307081110-f21760c49a8d Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.jedisct1.go-minisign-v0.0.0-20210414164026-819d7e2534ac Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.spf13.pflag-v1.0.5 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.zalando.go-keyring-v0.1.1 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-golang.org.x.term-v0.0.0-20210615171337-6886f2dfbf5b Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.cyberphone.json-canonicalization-v0.0.0-20210303052042-6bc126869bf4 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.sigstore.sigstore-v0.0.0-20210609084117-386ea718fc64 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.spf13.viper-v1.7.1 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.go-homedir-v1.1.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.sassoftware.relic-v7.2.1+incompatible Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.spf13.cobra-v1.1.3 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 Relationship SPDXRef-Package-github.com.sigstore.rekor-v0.1.2-0.20210519014330-b5480728bde6 DEPENDS_ON SPDXRef-Package-github.com.fsnotify.fsnotify-v1.4.9 ##### Package representing the github.com/google/certificate-transparency-go PackageName: github.com/google/certificate-transparency-go SPDXID: SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 PackageVersion: v1.1.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 095ae65cdc8f710c01a841546db2d13d47decc49a7270c94e86351ef3a0cad8f PackageHomePage: https://github.com/google/certificate-transparency-go PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.golang.groupcache-v0.0.0-20210331224755-41bb18bfe9da Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.google.trillian-v1.3.14-0.20210413093047-5e12fb368c8f Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.spf13.cobra-v1.1.3 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-google.golang.org.api-v0.46.0 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.mattn.go-runewidth-v0.0.9 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-go.uber.org.multierr-v1.7.0 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-go.uber.org.zap-v1.16.0 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.olekukonko.tablewriter-v0.0.5 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-go.uber.org.atomic-v1.7.0 Relationship SPDXRef-Package-github.com.google.certificate-transparency-go-v1.1.1 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e ##### Package representing the google.golang.org/protobuf PackageName: google.golang.org/protobuf SPDXID: SPDXRef-Package-google.golang.org.protobuf-v1.26.0 PackageVersion: v1.26.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 121dc87761410a8622800b896b6f77b5b8ce26bd2484bcf85c8d6fd177116df1 PackageHomePage: https://google.golang.org/protobuf PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-google.golang.org.protobuf-v1.26.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-google.golang.org.protobuf-v1.26.0 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-google.golang.org.protobuf-v1.26.0 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced ##### Package representing the github.com/golang/protobuf PackageName: github.com/golang/protobuf SPDXID: SPDXRef-Package-github.com.golang.protobuf-v1.5.2 PackageVersion: v1.5.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 6f2a16bbef47f86415aa7ab887df08d30c1d4e79917c370fe163dbf5b8bde6b8 PackageHomePage: https://github.com/golang/protobuf PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.golang.protobuf-v1.5.2 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-github.com.golang.protobuf-v1.5.2 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 ##### Package representing the google.golang.org/genproto PackageName: google.golang.org/genproto SPDXID: SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced PackageVersion: v0.0.0-20210617175327-b9e0b3197ced PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: fb5b2471725d8be3b0a0d39775d6379681c484e5320f1c28007c425e0dccda4c PackageHomePage: https://google.golang.org/genproto PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 ##### Package representing the google.golang.org/grpc PackageName: google.golang.org/grpc SPDXID: SPDXRef-Package-google.golang.org.grpc-v1.38.0 PackageVersion: v1.38.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 28897ebd9d678ee15f24ce7808810c9c1ae39317687b00a4bb5555ae1627ee8d PackageHomePage: https://google.golang.org/grpc PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-cloud.google.com.go-v0.81.0 Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-github.com.gogo.protobuf-v1.3.2 Relationship SPDXRef-Package-google.golang.org.grpc-v1.38.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 ##### Package representing the github.com/go-chi/chi PackageName: github.com/go-chi/chi SPDXID: SPDXRef-Package-github.com.go-chi.chi-v4.1.2+incompatible PackageVersion: v4.1.2+incompatible PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 6c946a8dec9d21da32b4210fa7978a5cb94dfc2b1fb036486163d2da9c089ff9 PackageHomePage: https://github.com/go-chi/chi PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the go.uber.org/atomic PackageName: go.uber.org/atomic SPDXID: SPDXRef-Package-go.uber.org.atomic-v1.7.0 PackageVersion: v1.7.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 82f23ad2d95732cc976adb89249921f6115a4dd137b10fd3032b1bd71474c7f5 PackageHomePage: https://go.uber.org/atomic PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-go.uber.org.atomic-v1.7.0 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the go.uber.org/multierr PackageName: go.uber.org/multierr SPDXID: SPDXRef-Package-go.uber.org.multierr-v1.7.0 PackageVersion: v1.7.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 4bb6aa0b6cfb40ba982ee2226c46e4c582181690d25a68e8656b44e5013caeca PackageHomePage: https://go.uber.org/multierr PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-go.uber.org.multierr-v1.7.0 DEPENDS_ON SPDXRef-Package-go.uber.org.atomic-v1.7.0 ##### Package representing the go.uber.org/zap PackageName: go.uber.org/zap SPDXID: SPDXRef-Package-go.uber.org.zap-v1.16.0 PackageVersion: v1.16.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: cfd512fe5f2bc6c885d192cbe1db866a46527acce9adebaf598e8928418b9c08 PackageHomePage: https://go.uber.org/zap PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-go.uber.org.zap-v1.16.0 DEPENDS_ON SPDXRef-Package-go.uber.org.multierr-v1.7.0 Relationship SPDXRef-Package-go.uber.org.zap-v1.16.0 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-go.uber.org.zap-v1.16.0 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-go.uber.org.zap-v1.16.0 DEPENDS_ON SPDXRef-Package-go.uber.org.atomic-v1.7.0 ##### Package representing the github.com/blang/semver PackageName: github.com/blang/semver SPDXID: SPDXRef-Package-github.com.blang.semver-v3.5.1+incompatible PackageVersion: v3.5.1+incompatible PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 668ac29febf2c8c7e99d84eb10030d39ca9042980df35475ef75ea1b5145d258 PackageHomePage: https://github.com/blang/semver PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the golang.org/x/term PackageName: golang.org/x/term SPDXID: SPDXRef-Package-golang.org.x.term-v0.0.0-20210615171337-6886f2dfbf5b PackageVersion: v0.0.0-20210615171337-6886f2dfbf5b PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 8ee37192f7d4533352609092a0ce86b686643bae42cb6eb70eeaa09b46a50666 PackageHomePage: https://golang.org/x/term PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-golang.org.x.term-v0.0.0-20210615171337-6886f2dfbf5b DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 ##### Package representing the golang.org/x/crypto PackageName: golang.org/x/crypto SPDXID: SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e PackageVersion: v0.0.0-20210616213533-5ff15b29337e PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 711b1960550951ba9f896f4a6cf9b9277385e2acc66930be13a94323edd1c81f PackageHomePage: https://golang.org/x/crypto PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e DEPENDS_ON SPDXRef-Package-golang.org.x.term-v0.0.0-20210615171337-6886f2dfbf5b Relationship SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 ##### Package representing the github.com/howeyc/gopass PackageName: github.com/howeyc/gopass SPDXID: SPDXRef-Package-github.com.howeyc.gopass-v0.0.0-20190910152052-7cb4b85ec19c PackageVersion: v0.0.0-20190910152052-7cb4b85ec19c PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 9d00d401b09f8d272a15d65b242541ed5acbf7b91a9dda6a75e58e5e1248f79f PackageHomePage: https://github.com/howeyc/gopass PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/zalando/go-keyring PackageName: github.com/zalando/go-keyring SPDXID: SPDXRef-Package-github.com.zalando.go-keyring-v0.1.1 PackageVersion: v0.1.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 8a3f321827a4aeb85811bae37d83c6e438242a888f45552e22b596e8285168c3 PackageHomePage: https://github.com/zalando/go-keyring PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/sassoftware/relic PackageName: github.com/sassoftware/relic SPDXID: SPDXRef-Package-github.com.sassoftware.relic-v7.2.1+incompatible PackageVersion: v7.2.1+incompatible PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 1ba58336c70227b09e6b95fee0fe3dd77e3e7e75538635595eb8caba7788eadb PackageHomePage: https://github.com/sassoftware/relic PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/jedisct1/go-minisign PackageName: github.com/jedisct1/go-minisign SPDXID: SPDXRef-Package-github.com.jedisct1.go-minisign-v0.0.0-20210414164026-819d7e2534ac PackageVersion: v0.0.0-20210414164026-819d7e2534ac PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 5c51edc535c9ef254999ddd03ca153a667e97ceebc9d78810dd74407186dd5c6 PackageHomePage: https://github.com/jedisct1/go-minisign PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.jedisct1.go-minisign-v0.0.0-20210414164026-819d7e2534ac DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e ##### Package representing the github.com/ghodss/yaml PackageName: github.com/ghodss/yaml SPDXID: SPDXRef-Package-github.com.ghodss.yaml-v1.0.0 PackageVersion: v1.0.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 8a601d3cc973a9e45f3c1c56f28377edf7d28cabaaca1668dce782c4607afdaa PackageHomePage: https://github.com/ghodss/yaml PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/cavaliercoder/go-rpm PackageName: github.com/cavaliercoder/go-rpm SPDXID: SPDXRef-Package-github.com.cavaliercoder.go-rpm-v0.0.0-20200122174316-8cb9fd9c31a8 PackageVersion: v0.0.0-20200122174316-8cb9fd9c31a8 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 57cfc0c523cf3587fa4bc6430836c8da25b057b26826fb08b5d8b80964ae4ced PackageHomePage: https://github.com/cavaliercoder/go-rpm PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/googleapis/gax-go/v2 PackageName: github.com/googleapis/gax-go/v2 SPDXID: SPDXRef-Package-github.com.googleapis.gax-go.v2-v2.0.5 PackageVersion: v2.0.5 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: b8b7e67cfe0beb74db70984ea7278c8a2644f7190000b0230a603cf73db9be94 PackageHomePage: https://github.com/googleapis/gax-go/v2 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.googleapis.gax-go.v2-v2.0.5 DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 ##### Package representing the cloud.google.com/go PackageName: cloud.google.com/go SPDXID: SPDXRef-Package-cloud.google.com.go-v0.81.0 PackageVersion: v0.81.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 459733de8ac4d0d69eba97f5e4f4050395c9d9d05e6cba633e9247c7588232e9 PackageHomePage: https://cloud.google.com/go PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-go.opencensus.io-v0.23.0 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-github.com.googleapis.gax-go.v2-v2.0.5 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-google.golang.org.api-v0.46.0 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-github.com.golang.groupcache-v0.0.0-20210331224755-41bb18bfe9da Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-cloud.google.com.go-v0.81.0 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 ##### Package representing the google.golang.org/api PackageName: google.golang.org/api SPDXID: SPDXRef-Package-google.golang.org.api-v0.46.0 PackageVersion: v0.46.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 9c27d3b7b736de1570971331811b59458ba57cf5d2cb57263c5c1b9cc1c4c474 PackageHomePage: https://google.golang.org/api PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-go.opencensus.io-v0.23.0 Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-cloud.google.com.go-v0.81.0 Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-github.com.googleapis.gax-go.v2-v2.0.5 Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-google.golang.org.api-v0.46.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 ##### Package representing the golang.org/x/oauth2 PackageName: golang.org/x/oauth2 SPDXID: SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c PackageVersion: v0.0.0-20210514164344-f6687ab2804c PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 334a2a1b320de68c665dfdb61282dad09b343cd93b494ac1241522898bf253d3 PackageHomePage: https://golang.org/x/oauth2 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c DEPENDS_ON SPDXRef-Package-cloud.google.com.go-v0.81.0 Relationship SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c ##### Package representing the go.opencensus.io PackageName: go.opencensus.io SPDXID: SPDXRef-Package-go.opencensus.io-v0.23.0 PackageVersion: v0.23.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 7c86fbad22be2d27398183a1288e4c2aabd20d14271492cd47651f23d3139bd6 PackageHomePage: https://go.opencensus.io PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-google.golang.org.api-v0.46.0 Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-github.com.golang.groupcache-v0.0.0-20210331224755-41bb18bfe9da Relationship SPDXRef-Package-go.opencensus.io-v0.23.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 ##### Package representing the github.com/golang/groupcache PackageName: github.com/golang/groupcache SPDXID: SPDXRef-Package-github.com.golang.groupcache-v0.0.0-20210331224755-41bb18bfe9da PackageVersion: v0.0.0-20210331224755-41bb18bfe9da PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 03f65147877c2e0084097490c88ede91762bc954dd9dcf77ddaf822ffc1ac168 PackageHomePage: https://github.com/golang/groupcache PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/google/go-cmp PackageName: github.com/google/go-cmp SPDXID: SPDXRef-Package-github.com.google.go-cmp-v0.5.6 PackageVersion: v0.5.6 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e95afd232d9a38271d4252ccccc027ddc13aaea55345ca12be0df6462bb75687 PackageHomePage: https://github.com/google/go-cmp PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/hashicorp/errwrap PackageName: github.com/hashicorp/errwrap SPDXID: SPDXRef-Package-github.com.hashicorp.errwrap-v1.1.0 PackageVersion: v1.1.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 047a321b836f7f66764f46fc2887ac21a3eac0b22b6c8fb8896fab7db6da622f PackageHomePage: https://github.com/hashicorp/errwrap PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/hashicorp/go-cleanhttp PackageName: github.com/hashicorp/go-cleanhttp SPDXID: SPDXRef-Package-github.com.hashicorp.go-cleanhttp-v0.5.2 PackageVersion: v0.5.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 850ecf5976cd640a31d2c94b7fe0fddeb3b40186965f372ad54a254e3bd0dd8f PackageHomePage: https://github.com/hashicorp/go-cleanhttp PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/hashicorp/go-multierror PackageName: github.com/hashicorp/go-multierror SPDXID: SPDXRef-Package-github.com.hashicorp.go-multierror-v1.1.1 PackageVersion: v1.1.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: f903a790c3fd699e65977baa2c4bb911282266395b92a232e0ceeafc38b1eaf2 PackageHomePage: https://github.com/hashicorp/go-multierror PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.hashicorp.go-multierror-v1.1.1 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.errwrap-v1.1.0 ##### Package representing the github.com/hashicorp/go-retryablehttp PackageName: github.com/hashicorp/go-retryablehttp SPDXID: SPDXRef-Package-github.com.hashicorp.go-retryablehttp-v0.6.8 PackageVersion: v0.6.8 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 7b1c176e794c9eac1776346636ac0f6a6e4b592a092d6263d518e2a230d7461c PackageHomePage: https://github.com/hashicorp/go-retryablehttp PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.hashicorp.go-retryablehttp-v0.6.8 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-cleanhttp-v0.5.2 ##### Package representing the github.com/hashicorp/go-rootcerts PackageName: github.com/hashicorp/go-rootcerts SPDXID: SPDXRef-Package-github.com.hashicorp.go-rootcerts-v1.0.2 PackageVersion: v1.0.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: d42cdbf7b568f432127ee9d549060acbfb08ab2a527d059e0e12e84224d9b590 PackageHomePage: https://github.com/hashicorp/go-rootcerts PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.hashicorp.go-rootcerts-v1.0.2 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.go-homedir-v1.1.0 ##### Package representing the github.com/hashicorp/vault/sdk PackageName: github.com/hashicorp/vault/sdk SPDXID: SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 PackageVersion: v0.2.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 21e28009edebedb97dc5fc5d340838c747e25934dc1606af952be083cd9c6c66 PackageHomePage: https://github.com/hashicorp/vault/sdk PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.errwrap-v1.1.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-cleanhttp-v0.5.2 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.opencontainers.image-spec-v1.0.1 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.pierrec.lz4-v2.6.0+incompatible Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.docker.distribution-v2.7.1+incompatible Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.gorilla.mux-v1.8.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.opencontainers.go-digest-v1.0.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-sockaddr-v1.0.2 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.ryanuber.go-glob-v1.0.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.docker.docker-v1.4.2-0.20200319182547-c7ad2b866182 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-multierror-v1.1.1 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.hcl-v1.0.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-google.golang.org.grpc-v1.38.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-gopkg.in.square.go-jose.v2-v2.5.1 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.golang.snappy-v0.0.3 Relationship SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-retryablehttp-v0.6.8 ##### Package representing the github.com/golang/snappy PackageName: github.com/golang/snappy SPDXID: SPDXRef-Package-github.com.golang.snappy-v0.0.3 PackageVersion: v0.0.3 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: ec86cb9791c62c2bc5f0f9dd332d61198c4cf6f40818e67135a45cde051b40b4 PackageHomePage: https://github.com/golang/snappy PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/pierrec/lz4 PackageName: github.com/pierrec/lz4 SPDXID: SPDXRef-Package-github.com.pierrec.lz4-v2.6.0+incompatible PackageVersion: v2.6.0+incompatible PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 4de292b0e083fe4176c7b02ae284a4b4c24c88744cfa0398f94b0d2229184192 PackageHomePage: https://github.com/pierrec/lz4 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/hashicorp/go-sockaddr PackageName: github.com/hashicorp/go-sockaddr SPDXID: SPDXRef-Package-github.com.hashicorp.go-sockaddr-v1.0.2 PackageVersion: v1.0.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 3b91d4c2b863af23838e2f6b983ffe9c2cf4741995ce97e6ecf781876443ccc9 PackageHomePage: https://github.com/hashicorp/go-sockaddr PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.hashicorp.go-sockaddr-v1.0.2 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.errwrap-v1.1.0 ##### Package representing the github.com/ryanuber/go-glob PackageName: github.com/ryanuber/go-glob SPDXID: SPDXRef-Package-github.com.ryanuber.go-glob-v1.0.0 PackageVersion: v1.0.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e4a59b9bcddd0bfaebb26f2cd34aae2c85f80acf9d97c5cc6e98a262a3deefbd PackageHomePage: https://github.com/ryanuber/go-glob PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the golang.org/x/time PackageName: golang.org/x/time SPDXID: SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba PackageVersion: v0.0.0-20210220033141-f8bda1e9f3ba PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 9eb556e6f8c487b07629dbd90cf04320501894e9f491d2a22b89593e28e24df2 PackageHomePage: https://golang.org/x/time PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the gopkg.in/square/go-jose.v2 PackageName: gopkg.in/square/go-jose.v2 SPDXID: SPDXRef-Package-gopkg.in.square.go-jose.v2-v2.5.1 PackageVersion: v2.5.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: b35860f881c48c56d045f6600a446bc8ccb4d849932abdb2a4b5bb8d3efd14a2 PackageHomePage: https://gopkg.in/square/go-jose.v2 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/hashicorp/vault/api PackageName: github.com/hashicorp/vault/api SPDXID: SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 PackageVersion: v1.1.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 7384684d18b6cdf97e9ac93b246657168a65f656a5ef7501e5972de8f20b25b6 PackageHomePage: https://github.com/hashicorp/vault/api PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-cleanhttp-v0.5.2 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-multierror-v1.1.1 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-retryablehttp-v0.6.8 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.go-rootcerts-v1.0.2 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.vault.sdk-v0.2.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.errwrap-v1.1.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.hashicorp.hcl-v1.0.0 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-github.com.hashicorp.vault.api-v1.1.0 DEPENDS_ON SPDXRef-Package-gopkg.in.square.go-jose.v2-v2.5.1 ##### Package representing the github.com/theupdateframework/go-tuf PackageName: github.com/theupdateframework/go-tuf SPDXID: SPDXRef-Package-github.com.theupdateframework.go-tuf-v0.0.0-20201230183259-aee6270feb55 PackageVersion: v0.0.0-20201230183259-aee6270feb55 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: ed391d786e0359002979453c3fc82a8eadf5e3e275ef4a15027a9e9147644262 PackageHomePage: https://github.com/theupdateframework/go-tuf PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.theupdateframework.go-tuf-v0.0.0-20201230183259-aee6270feb55 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e ##### Package representing the github.com/coreos/go-oidc/v3 PackageName: github.com/coreos/go-oidc/v3 SPDXID: SPDXRef-Package-github.com.coreos.go-oidc.v3-v3.0.0 PackageVersion: v3.0.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 9b01858cab936242c2c4957f7c158cd678af5eb7151bd2af80f33eef9ec12ad2 PackageHomePage: https://github.com/coreos/go-oidc/v3 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.coreos.go-oidc.v3-v3.0.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.coreos.go-oidc.v3-v3.0.0 DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c Relationship SPDXRef-Package-github.com.coreos.go-oidc.v3-v3.0.0 DEPENDS_ON SPDXRef-Package-gopkg.in.square.go-jose.v2-v2.5.1 Relationship SPDXRef-Package-github.com.coreos.go-oidc.v3-v3.0.0 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 ##### Package representing the github.com/sigstore/fulcio PackageName: github.com/sigstore/fulcio SPDXID: SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca PackageVersion: v0.0.0-20210405115948-e7630f533fca PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e9fed9477691b8fbffc2f6bef6ca60b9f167db0cb53898280b49cfd930a54e05 PackageHomePage: https://github.com/sigstore/fulcio PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-google.golang.org.genproto-v0.0.0-20210617175327-b9e0b3197ced Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-go.uber.org.zap-v1.16.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.pelletier.go-toml-v1.9.3 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-cloud.google.com.go-v0.81.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.go-openapi.loads-v0.20.2 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.go-openapi.errors-v0.20.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.go-openapi.strfmt-v0.20.1 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.mailru.easyjson-v0.7.7 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.spf13.afero-v1.6.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.spf13.jwalterweatherman-v1.1.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.spf13.cobra-v1.1.3 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.mitchellh.mapstructure-v1.4.1 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.go-openapi.spec-v0.20.3 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.go-openapi.runtime-v0.19.29 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.spf13.viper-v1.7.1 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.go-openapi.swag-v0.19.15 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.go-chi.chi-v4.1.2+incompatible Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.fsnotify.fsnotify-v1.4.9 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.coreos.go-oidc.v3-v3.0.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.magiconair.properties-v1.8.5 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.segmentio.ksuid-v1.0.3 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.mitchellh.go-homedir-v1.1.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-gopkg.in.ini.v1-v1.62.0 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.spf13.pflag-v1.0.5 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.spf13.cast-v1.3.1 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.skratchdot.open-golang-v0.0.0-20200116055534-eef842397966 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-github.com.go-openapi.validate-v0.20.2 Relationship SPDXRef-Package-github.com.sigstore.fulcio-v0.0.0-20210405115948-e7630f533fca DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e ##### Package representing the github.com/segmentio/ksuid PackageName: github.com/segmentio/ksuid SPDXID: SPDXRef-Package-github.com.segmentio.ksuid-v1.0.3 PackageVersion: v1.0.3 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 58179fe001a088e616b1848aec64d6fd5c8a8e79e88856e58812b58f3835500d PackageHomePage: https://github.com/segmentio/ksuid PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/skratchdot/open-golang PackageName: github.com/skratchdot/open-golang SPDXID: SPDXRef-Package-github.com.skratchdot.open-golang-v0.0.0-20200116055534-eef842397966 PackageVersion: v0.0.0-20200116055534-eef842397966 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 0977118b561a46782a4ec19fcf21d79280b78fde20bc3c77219e70d5aa878e46 PackageHomePage: https://github.com/skratchdot/open-golang PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/gogo/protobuf PackageName: github.com/gogo/protobuf SPDXID: SPDXRef-Package-github.com.gogo.protobuf-v1.3.2 PackageVersion: v1.3.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 95d3fe25fc8e393da0e7135467b6699f4d0bf5f62ac93014def07a6ebc840536 PackageHomePage: https://github.com/gogo/protobuf PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the gopkg.in/inf.v0 PackageName: gopkg.in/inf.v0 SPDXID: SPDXRef-Package-gopkg.in.inf.v0-v0.9.1 PackageVersion: v0.9.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 771fadad552618ff0ae1e08892c4c5e06bab75c14304e2dccdfa390dc2db2a86 PackageHomePage: https://gopkg.in/inf.v0 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the k8s.io/apimachinery PackageName: k8s.io/apimachinery SPDXID: SPDXRef-Package-k8s.io.apimachinery-v0.21.2 PackageVersion: v0.21.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 99a73f202aa86722321431e4bd1cd3f88ed1d4e680cddbe4d1d16c1794a02c4e PackageHomePage: https://k8s.io/apimachinery PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-k8s.io.klog.v2-v2.8.0 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-sigs.k8s.io.structured-merge-diff.v4-v4.1.0 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-gopkg.in.inf.v0-v0.9.1 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-sigs.k8s.io.yaml-v1.2.0 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.json-iterator.go-v1.1.11 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.google.gofuzz-v1.2.0 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.gogo.protobuf-v1.3.2 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.spf13.pflag-v1.0.5 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.modern-go.reflect2-v1.0.1 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.golang.groupcache-v0.0.0-20210331224755-41bb18bfe9da Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.googleapis.gnostic-v0.4.1 Relationship SPDXRef-Package-k8s.io.apimachinery-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 ##### Package representing the github.com/google/gofuzz PackageName: github.com/google/gofuzz SPDXID: SPDXRef-Package-github.com.google.gofuzz-v1.2.0 PackageVersion: v1.2.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 9f90c7a974671a73dfacee0304a7b34959a807ae0dced51d0aa6f4883b296827 PackageHomePage: https://github.com/google/gofuzz PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/go-logr/logr PackageName: github.com/go-logr/logr SPDXID: SPDXRef-Package-github.com.go-logr.logr-v0.4.0 PackageVersion: v0.4.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 729410377448d47553cbc0b4dd2894ca3c1aa69e6426e18aa31f2bae3ab6a1c8 PackageHomePage: https://github.com/go-logr/logr PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the k8s.io/klog/v2 PackageName: k8s.io/klog/v2 SPDXID: SPDXRef-Package-k8s.io.klog.v2-v2.8.0 PackageVersion: v2.8.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: f3843055d8b518fac6bd96d36d10cda14ef347848023df47e0ae510cfc668c9d PackageHomePage: https://k8s.io/klog/v2 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-k8s.io.klog.v2-v2.8.0 DEPENDS_ON SPDXRef-Package-github.com.go-logr.logr-v0.4.0 ##### Package representing the github.com/modern-go/concurrent PackageName: github.com/modern-go/concurrent SPDXID: SPDXRef-Package-github.com.modern-go.concurrent-v0.0.0-20180306012644-bacd9c7ef1dd PackageVersion: v0.0.0-20180306012644-bacd9c7ef1dd PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: c6081e6ce83b52eb5415b1eaaa3920fcbdff17d1c3fa789b5b89d2cbbc6ace2e PackageHomePage: https://github.com/modern-go/concurrent PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/modern-go/reflect2 PackageName: github.com/modern-go/reflect2 SPDXID: SPDXRef-Package-github.com.modern-go.reflect2-v1.0.1 PackageVersion: v1.0.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 95aa5db9774824ade90b1b53c29f23e331139bed8b757f2c715ea0efc7399fc8 PackageHomePage: https://github.com/modern-go/reflect2 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/json-iterator/go PackageName: github.com/json-iterator/go SPDXID: SPDXRef-Package-github.com.json-iterator.go-v1.1.11 PackageVersion: v1.1.11 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 254625d8d2b1f7077ee5b424343f8a20c77e36b8d825fae1edf817949c9b3e84 PackageHomePage: https://github.com/json-iterator/go PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.json-iterator.go-v1.1.11 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-github.com.json-iterator.go-v1.1.11 DEPENDS_ON SPDXRef-Package-github.com.google.gofuzz-v1.2.0 Relationship SPDXRef-Package-github.com.json-iterator.go-v1.1.11 DEPENDS_ON SPDXRef-Package-github.com.modern-go.concurrent-v0.0.0-20180306012644-bacd9c7ef1dd Relationship SPDXRef-Package-github.com.json-iterator.go-v1.1.11 DEPENDS_ON SPDXRef-Package-github.com.modern-go.reflect2-v1.0.1 ##### Package representing the sigs.k8s.io/structured-merge-diff/v4 PackageName: sigs.k8s.io/structured-merge-diff/v4 SPDXID: SPDXRef-Package-sigs.k8s.io.structured-merge-diff.v4-v4.1.0 PackageVersion: v4.1.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 223818142048052ae7e0e13055d154f4e681983fa3fabd380ed59d459f9bbea7 PackageHomePage: https://sigs.k8s.io/structured-merge-diff/v4 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-sigs.k8s.io.structured-merge-diff.v4-v4.1.0 DEPENDS_ON SPDXRef-Package-github.com.modern-go.concurrent-v0.0.0-20180306012644-bacd9c7ef1dd Relationship SPDXRef-Package-sigs.k8s.io.structured-merge-diff.v4-v4.1.0 DEPENDS_ON SPDXRef-Package-github.com.modern-go.reflect2-v1.0.1 Relationship SPDXRef-Package-sigs.k8s.io.structured-merge-diff.v4-v4.1.0 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-sigs.k8s.io.structured-merge-diff.v4-v4.1.0 DEPENDS_ON SPDXRef-Package-github.com.google.gofuzz-v1.2.0 Relationship SPDXRef-Package-sigs.k8s.io.structured-merge-diff.v4-v4.1.0 DEPENDS_ON SPDXRef-Package-github.com.json-iterator.go-v1.1.11 ##### Package representing the k8s.io/api PackageName: k8s.io/api SPDXID: SPDXRef-Package-k8s.io.api-v0.21.2 PackageVersion: v0.21.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 0f91c64066f5e8b2de142b2d3b155d66d6edcf720aa0f30af9929be542aa6ffe PackageHomePage: https://k8s.io/api PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-k8s.io.api-v0.21.2 DEPENDS_ON SPDXRef-Package-k8s.io.apimachinery-v0.21.2 Relationship SPDXRef-Package-k8s.io.api-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.gogo.protobuf-v1.3.2 ##### Package representing the github.com/googleapis/gnostic PackageName: github.com/googleapis/gnostic SPDXID: SPDXRef-Package-github.com.googleapis.gnostic-v0.4.1 PackageVersion: v0.4.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 2e1455e8d4d3a3ca2c1429f4dfd223227c5d875a0b9fec12c7a8b6540e606a58 PackageHomePage: https://github.com/googleapis/gnostic PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.googleapis.gnostic-v0.4.1 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.googleapis.gnostic-v0.4.1 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 ##### Package representing the sigs.k8s.io/yaml PackageName: sigs.k8s.io/yaml SPDXID: SPDXRef-Package-sigs.k8s.io.yaml-v1.2.0 PackageVersion: v1.2.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 76d8cd575a926fc427aa57cf0574548aac399ee84e3e313e841c1cccf99ffac7 PackageHomePage: https://sigs.k8s.io/yaml PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-sigs.k8s.io.yaml-v1.2.0 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-sigs.k8s.io.yaml-v1.2.0 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the k8s.io/client-go PackageName: k8s.io/client-go SPDXID: SPDXRef-Package-k8s.io.client-go-v0.21.2 PackageVersion: v0.21.2 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 49c966ba6a48e4cb8e520ae8c8285d0d19fdf7706b4ba3f7e50acdcddaa560f3 PackageHomePage: https://k8s.io/client-go PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.googleapis.gnostic-v0.4.1 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.imdario.mergo-v0.3.9 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.Azure.go-autorest.autorest-v0.11.18 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.Azure.go-autorest.autorest.adal-v0.9.13 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.gogo.protobuf-v1.3.2 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.google.gofuzz-v1.2.0 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.spf13.pflag-v1.0.5 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-golang.org.x.term-v0.0.0-20210615171337-6886f2dfbf5b Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-k8s.io.apimachinery-v0.21.2 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-cloud.google.com.go-v0.81.0 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.golang.groupcache-v0.0.0-20210331224755-41bb18bfe9da Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-sigs.k8s.io.yaml-v1.2.0 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-k8s.io.utils-v0.0.0-20201110183641-67b214c5f920 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-k8s.io.klog.v2-v2.8.0 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-sigs.k8s.io.structured-merge-diff.v4-v4.1.0 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-k8s.io.api-v0.21.2 Relationship SPDXRef-Package-k8s.io.client-go-v0.21.2 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 ##### Package representing the github.com/davecgh/go-spew PackageName: github.com/davecgh/go-spew SPDXID: SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 PackageVersion: v1.1.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 77d53af0a568b8c213c0a761194a7cb44abeb0c005359ec1b41ffdab10d7bb13 PackageHomePage: https://github.com/davecgh/go-spew PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the k8s.io/utils PackageName: k8s.io/utils SPDXID: SPDXRef-Package-k8s.io.utils-v0.0.0-20201110183641-67b214c5f920 PackageVersion: v0.0.0-20201110183641-67b214c5f920 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e27d3708c47807fa1e142d0d0043af60498463f429ea0932ffc50249e3efa6c6 PackageHomePage: https://k8s.io/utils PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-k8s.io.utils-v0.0.0-20201110183641-67b214c5f920 DEPENDS_ON SPDXRef-Package-k8s.io.klog.v2-v2.8.0 Relationship SPDXRef-Package-k8s.io.utils-v0.0.0-20201110183641-67b214c5f920 DEPENDS_ON SPDXRef-Package-github.com.spf13.afero-v1.6.0 Relationship SPDXRef-Package-k8s.io.utils-v0.0.0-20201110183641-67b214c5f920 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 ##### Package representing the github.com/Azure/go-autorest/autorest/date PackageName: github.com/Azure/go-autorest/autorest/date SPDXID: SPDXRef-Package-github.com.Azure.go-autorest.autorest.date-v0.3.0 PackageVersion: v0.3.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e6ac66ef043540fb1bd849b36ae1582dc360d9a274cbd9cc9768d1659f9e59fa PackageHomePage: https://github.com/Azure/go-autorest/autorest/date PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/Azure/go-autorest/logger PackageName: github.com/Azure/go-autorest/logger SPDXID: SPDXRef-Package-github.com.Azure.go-autorest.logger-v0.2.1 PackageVersion: v0.2.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 6f980aaf27fee26a7f7017d638e0b9092a26c1c7af79b8263bf4a6e2a20452c6 PackageHomePage: https://github.com/Azure/go-autorest/logger PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/Azure/go-autorest/tracing PackageName: github.com/Azure/go-autorest/tracing SPDXID: SPDXRef-Package-github.com.Azure.go-autorest.tracing-v0.6.0 PackageVersion: v0.6.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: e9b2a988aca23d841384a17068de9dcd59ccb7032cd6eadf05430edd426134c2 PackageHomePage: https://github.com/Azure/go-autorest/tracing PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/form3tech-oss/jwt-go PackageName: github.com/form3tech-oss/jwt-go SPDXID: SPDXRef-Package-github.com.form3tech-oss.jwt-go-v3.2.2+incompatible PackageVersion: v3.2.2+incompatible PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 4a559f8950d5014570c484552049fa65e1c9eaa3ee5aac02920020103d345f9c PackageHomePage: https://github.com/form3tech-oss/jwt-go PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/Azure/go-autorest/autorest/adal PackageName: github.com/Azure/go-autorest/autorest/adal SPDXID: SPDXRef-Package-github.com.Azure.go-autorest.autorest.adal-v0.9.13 PackageVersion: v0.9.13 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 1d8888e699a769d032389aa3de1f49bbdda2bf7c250baf1376146ec54228851b PackageHomePage: https://github.com/Azure/go-autorest/autorest/adal PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest.adal-v0.9.13 DEPENDS_ON SPDXRef-Package-github.com.Azure.go-autorest.autorest.date-v0.3.0 Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest.adal-v0.9.13 DEPENDS_ON SPDXRef-Package-github.com.Azure.go-autorest.tracing-v0.6.0 Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest.adal-v0.9.13 DEPENDS_ON SPDXRef-Package-github.com.form3tech-oss.jwt-go-v3.2.2+incompatible Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest.adal-v0.9.13 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest.adal-v0.9.13 DEPENDS_ON SPDXRef-Package-github.com.Azure.go-autorest.logger-v0.2.1 ##### Package representing the github.com/Azure/go-autorest/autorest PackageName: github.com/Azure/go-autorest/autorest SPDXID: SPDXRef-Package-github.com.Azure.go-autorest.autorest-v0.11.18 PackageVersion: v0.11.18 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 7b85debcce807a016fbdae358971274f74c396ab20b58a73e8fa69fcaed58912 PackageHomePage: https://github.com/Azure/go-autorest/autorest PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest-v0.11.18 DEPENDS_ON SPDXRef-Package-github.com.Azure.go-autorest.autorest.adal-v0.9.13 Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest-v0.11.18 DEPENDS_ON SPDXRef-Package-github.com.Azure.go-autorest.logger-v0.2.1 Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest-v0.11.18 DEPENDS_ON SPDXRef-Package-github.com.Azure.go-autorest.tracing-v0.6.0 Relationship SPDXRef-Package-github.com.Azure.go-autorest.autorest-v0.11.18 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e ##### Package representing the github.com/imdario/mergo PackageName: github.com/imdario/mergo SPDXID: SPDXRef-Package-github.com.imdario.mergo-v0.3.9 PackageVersion: v0.3.9 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 0ee96fdd2abc1d04b65d2bc2fcc2de4ae71b231e9543893db38016cd6822ae5c PackageHomePage: https://github.com/imdario/mergo PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/OneOfOne/xxhash PackageName: github.com/OneOfOne/xxhash SPDXID: SPDXRef-Package-github.com.OneOfOne.xxhash-v1.2.8 PackageVersion: v1.2.8 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 72ca3234e0a2f98565da9eef27df8a5a3f8652e74066cd454feba34fe160787e PackageHomePage: https://github.com/OneOfOne/xxhash PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/open-policy-agent/opa PackageName: github.com/open-policy-agent/opa SPDXID: SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 PackageVersion: v0.29.4 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: d45f3fe23dd8e18a1bd1ef0aa85ab059ed17d7a27bfd3d060d0c625186729110 PackageHomePage: https://github.com/open-policy-agent/opa PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.yashtewari.glob-intersection-v0.0.0-20180916065949-5c77d914dd0b Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.xeipuuv.gojsonreference-v0.0.0-20180127040603-bd5ef7bd5415 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.spf13.cobra-v1.1.3 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.peterh.liner-v0.0.0-20170211195444-bf27d3ba8e1d Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.gorilla.mux-v1.8.0 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-golang.org.x.time-v0.0.0-20210220033141-f8bda1e9f3ba Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.prometheus.common-v0.29.0 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.olekukonko.tablewriter-v0.0.5 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.fsnotify.fsnotify-v1.4.9 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.xeipuuv.gojsonpointer-v0.0.0-20190905194746-02993c407bfb Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.gobwas.glob-v0.2.3 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.prometheus.procfs-v0.6.0 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-go.uber.org.automaxprocs-v1.4.0 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.spf13.pflag-v1.0.5 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.sirupsen.logrus-v1.8.1 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.rcrowley.go-metrics-v0.0.0-20200313005456-10cdbea86bc0 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.ghodss.yaml-v1.0.0 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-github.com.OneOfOne.xxhash-v1.2.8 Relationship SPDXRef-Package-github.com.open-policy-agent.opa-v0.29.4 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 ##### Package representing the github.com/xeipuuv/gojsonpointer PackageName: github.com/xeipuuv/gojsonpointer SPDXID: SPDXRef-Package-github.com.xeipuuv.gojsonpointer-v0.0.0-20190905194746-02993c407bfb PackageVersion: v0.0.0-20190905194746-02993c407bfb PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 63a2e8a1e3a190e1bc3280e3940d846c646ef3e908736ae72db9076029fcc706 PackageHomePage: https://github.com/xeipuuv/gojsonpointer PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/xeipuuv/gojsonreference PackageName: github.com/xeipuuv/gojsonreference SPDXID: SPDXRef-Package-github.com.xeipuuv.gojsonreference-v0.0.0-20180127040603-bd5ef7bd5415 PackageVersion: v0.0.0-20180127040603-bd5ef7bd5415 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 0200ca87486b0155e8c199eec9323c321665bb4938eccbac72d8547ed2082d6b PackageHomePage: https://github.com/xeipuuv/gojsonreference PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/rcrowley/go-metrics PackageName: github.com/rcrowley/go-metrics SPDXID: SPDXRef-Package-github.com.rcrowley.go-metrics-v0.0.0-20200313005456-10cdbea86bc0 PackageVersion: v0.0.0-20200313005456-10cdbea86bc0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: bd748d8975427958e55da8adae7bcbe43186d696e49a4804df848d113506ca45 PackageHomePage: https://github.com/rcrowley/go-metrics PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/mattn/go-runewidth PackageName: github.com/mattn/go-runewidth SPDXID: SPDXRef-Package-github.com.mattn.go-runewidth-v0.0.9 PackageVersion: v0.0.9 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: c0e249b2e49600457b866fa58f7075c833cd697601317bc0d8814817a4546987 PackageHomePage: https://github.com/mattn/go-runewidth PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/olekukonko/tablewriter PackageName: github.com/olekukonko/tablewriter SPDXID: SPDXRef-Package-github.com.olekukonko.tablewriter-v0.0.5 PackageVersion: v0.0.5 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 9e2c85817b12fce5ea554bb854c62ce8c10ae5124532ea9c3abee1c8d76f4105 PackageHomePage: https://github.com/olekukonko/tablewriter PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.olekukonko.tablewriter-v0.0.5 DEPENDS_ON SPDXRef-Package-github.com.mattn.go-runewidth-v0.0.9 Relationship SPDXRef-Package-github.com.olekukonko.tablewriter-v0.0.5 DEPENDS_ON SPDXRef-Package-github.com.olekukonko.tablewriter-v0.0.5 ##### Package representing the github.com/gobwas/glob PackageName: github.com/gobwas/glob SPDXID: SPDXRef-Package-github.com.gobwas.glob-v0.2.3 PackageVersion: v0.2.3 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 15b4a39767c64f15c6f6bc35d96f85768413a25bdfa4e46fa49a6374c9b7968b PackageHomePage: https://github.com/gobwas/glob PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/yashtewari/glob-intersection PackageName: github.com/yashtewari/glob-intersection SPDXID: SPDXRef-Package-github.com.yashtewari.glob-intersection-v0.0.0-20180916065949-5c77d914dd0b PackageVersion: v0.0.0-20180916065949-5c77d914dd0b PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 4ff4c9ffae6f6732584eb088757d9fcb30d0dc960c5b4e00c0cd4e05bacc04e4 PackageHomePage: https://github.com/yashtewari/glob-intersection PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/sirupsen/logrus PackageName: github.com/sirupsen/logrus SPDXID: SPDXRef-Package-github.com.sirupsen.logrus-v1.8.1 PackageVersion: v1.8.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: adea913fb1c1be8bf12e8b69d1da7f226c014c55caf8e56335bbe4e114e41b50 PackageHomePage: https://github.com/sirupsen/logrus PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.sirupsen.logrus-v1.8.1 DEPENDS_ON SPDXRef-Package-github.com.davecgh.go-spew-v1.1.1 Relationship SPDXRef-Package-github.com.sirupsen.logrus-v1.8.1 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.sirupsen.logrus-v1.8.1 DEPENDS_ON SPDXRef-Package-golang.org.x.crypto-v0.0.0-20210616213533-5ff15b29337e ##### Package representing the github.com/gorilla/mux PackageName: github.com/gorilla/mux SPDXID: SPDXRef-Package-github.com.gorilla.mux-v1.8.0 PackageVersion: v1.8.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 2dffd95dd340d7e6948873a146da12fd4146c08eb6eb8c420f76e8d9a742814c PackageHomePage: https://github.com/gorilla/mux PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/beorn7/perks PackageName: github.com/beorn7/perks SPDXID: SPDXRef-Package-github.com.beorn7.perks-v1.0.1 PackageVersion: v1.0.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 3f909457dc97a398f2d35bf26e5a7ba7af74a4b47f83b79dea360415ed24a98e PackageHomePage: https://github.com/beorn7/perks PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/cespare/xxhash/v2 PackageName: github.com/cespare/xxhash/v2 SPDXID: SPDXRef-Package-github.com.cespare.xxhash.v2-v2.1.1 PackageVersion: v2.1.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 7d5ac3d632362d4e19965eb9745b4092e9e62bd9bccde15d4975cc5a97649c63 PackageHomePage: https://github.com/cespare/xxhash/v2 PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/prometheus/client_model PackageName: github.com/prometheus/client_model SPDXID: SPDXRef-Package-github.com.prometheus.client-model-v0.2.0 PackageVersion: v0.2.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 4cc56642e11499313e9e0c16d30a8ba751e2f9c591dc6a1e7d675cf0e09e03d0 PackageHomePage: https://github.com/prometheus/client_model PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.prometheus.client-model-v0.2.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-github.com.prometheus.client-model-v0.2.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 ##### Package representing the github.com/prometheus/client_golang PackageName: github.com/prometheus/client_golang SPDXID: SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 PackageVersion: v1.11.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: ed14dd907bac61d8ae65bcb0211771c661e1cf1f7fd2b1394b9b124893705177 PackageHomePage: https://github.com/prometheus/client_golang PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.beorn7.perks-v1.0.1 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.modern-go.reflect2-v1.0.1 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.matttproud.golang-protobuf-extensions-v1.0.1 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.json-iterator.go-v1.1.11 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.prometheus.common-v0.29.0 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.modern-go.concurrent-v0.0.0-20180306012644-bacd9c7ef1dd Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-model-v0.2.0 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.prometheus.procfs-v0.6.0 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-golang.org.x.text-v0.3.6 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-google.golang.org.protobuf-v1.26.0 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.cespare.xxhash.v2-v2.1.1 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 ##### Package representing the github.com/matttproud/golang_protobuf_extensions PackageName: github.com/matttproud/golang_protobuf_extensions SPDXID: SPDXRef-Package-github.com.matttproud.golang-protobuf-extensions-v1.0.1 PackageVersion: v1.0.1 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: d8a0ce3a4b0ec610a7ee215ad6b63ee7f83fa14f7ecea46ad86dad3f8f3b99e7 PackageHomePage: https://github.com/matttproud/golang_protobuf_extensions PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the github.com/prometheus/common PackageName: github.com/prometheus/common SPDXID: SPDXRef-Package-github.com.prometheus.common-v0.29.0 PackageVersion: v0.29.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: dab655f661cec4b716fb97640b5c38306ce26552865eeacab4a3bc73d43966e7 PackageHomePage: https://github.com/prometheus/common PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.gogo.protobuf-v1.3.2 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.prometheus.procfs-v0.6.0 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.matttproud.golang-protobuf-extensions-v1.0.1 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-golang-v1.11.0 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.prometheus.client-model-v0.2.0 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-golang.org.x.net-v0.0.0-20210525063256-abc453219eb5 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.beorn7.perks-v1.0.1 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.go-stack.stack-v1.8.0 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.golang.protobuf-v1.5.2 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.sirupsen.logrus-v1.8.1 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-gopkg.in.yaml.v2-v2.4.0 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-golang.org.x.oauth2-v0.0.0-20210514164344-f6687ab2804c Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-github.com.pkg.errors-v0.9.1 Relationship SPDXRef-Package-github.com.prometheus.common-v0.29.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c ##### Package representing the github.com/prometheus/procfs PackageName: github.com/prometheus/procfs SPDXID: SPDXRef-Package-github.com.prometheus.procfs-v0.6.0 PackageVersion: v0.6.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 86bd64823e49f5c1bedae11007fb801c88d3b28ac5e712d4002355a3101a2ddd PackageHomePage: https://github.com/prometheus/procfs PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION Relationship SPDXRef-Package-github.com.prometheus.procfs-v0.6.0 DEPENDS_ON SPDXRef-Package-github.com.google.go-cmp-v0.5.6 Relationship SPDXRef-Package-github.com.prometheus.procfs-v0.6.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sync-v0.0.0-20210220032951-036812b2e83c Relationship SPDXRef-Package-github.com.prometheus.procfs-v0.6.0 DEPENDS_ON SPDXRef-Package-golang.org.x.sys-v0.0.0-20210615035016-665e8c7367d1 ##### Package representing the github.com/peterh/liner PackageName: github.com/peterh/liner SPDXID: SPDXRef-Package-github.com.peterh.liner-v0.0.0-20170211195444-bf27d3ba8e1d PackageVersion: v0.0.0-20170211195444-bf27d3ba8e1d PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 246b8db15eff422e821053ea3d13e8d076779b5c62fcdbefdd1f18b71235b67f PackageHomePage: https://github.com/peterh/liner PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION ##### Package representing the go.uber.org/automaxprocs PackageName: go.uber.org/automaxprocs SPDXID: SPDXRef-Package-go.uber.org.automaxprocs-v1.4.0 PackageVersion: v1.4.0 PackageSupplier: NOASSERTION PackageDownloadLocation: NOASSERTION FilesAnalyzed: false PackageChecksum: SHA256: 32be51bf668d3280000ab7aa1f970025fb49ef841201405a37d4cc98aaf7c71f PackageHomePage: https://go.uber.org/automaxprocs PackageLicenseConcluded: NOASSERTION PackageLicenseDeclared: NOASSERTION PackageCopyrightText: NOASSERTION PackageLicenseComments: NOASSERTION PackageComment: NOASSERTION cosign-2.5.0/test/testdata/bom-go-mod.spdx.json000066400000000000000000000006551477503325500214160ustar00rootroot00000000000000{"spdxVersion":"SPDX-2.2","dataLicense":"CC0-1.0","SPDXID":"SPDXRef-DOCUMENT","name":"SBOM-SPDX-34f1a7f5-03ff-4277-9021-8c04f8777803","documentNamespace":"https://spdx.org/spdxdocs/k8s-releng-bom-16f4e288-6bdf-4b89-a79a-9ffd56ad33e0","creationInfo":{"licenseListVersion":"","creators":["Organization: Kubernetes Release Engineering","Tool: sigs.k8s.io/bom/pkg/spdx"],"created":"2022-06-07T22:14:56Z","comment":""},"packages":[]} cosign-2.5.0/test/testdata/policies/000077500000000000000000000000001477503325500174125ustar00rootroot00000000000000cosign-2.5.0/test/testdata/policies/cue-fails.cue000066400000000000000000000005571477503325500217670ustar00rootroot00000000000000import "time" before: time.Parse(time.RFC3339, "2049-10-09T17:10:27Z") // Test with invalid predicate type. It should be this, so change it //predicateType: "https://cosign.sigstore.dev/attestation/v1" predicateType: "https://cosignnotreally.sigstore.dev/attestation/v1" // The predicate must match the following constraints. predicate: { Timestamp: after scanFinishedOn: after } } cosign-2.5.0/test/testdata/policies/cue-vuln-works.cue000066400000000000000000000010121477503325500230030ustar00rootroot00000000000000import "time" before: time.Parse(time.RFC3339, "2022-04-15T17:10:27Z") after: time.Parse(time.RFC3339, "2022-03-09T17:10:27Z") // The predicateType field must match this string predicateType: "https://cosign.sigstore.dev/attestation/vuln/v1" predicate: { invocation: { uri: "invocation.example.com/cosign-testing" } scanner: { uri: "fakescanner.example.com/cosign-testing" } metadata: { scanStartedOn: after scanFinishedOn: after } } cosign-2.5.0/test/testdata/policies/cue-works.cue000066400000000000000000000004251477503325500220300ustar00rootroot00000000000000import "time" before: time.Parse(time.RFC3339, "2049-10-09T17:10:27Z") // The predicateType field must match this string predicateType: "https://cosign.sigstore.dev/attestation/v1" // The predicate must match the following constraints. predicate: { Timestamp: ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ɰA ! ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUUUVWWWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} #(A jM@ Ak"$ B7A#!  Ak"$ )! )!A ! } B|!  SE@A! |  |1B,QE@A! { BSE@A! z B!B! BSE@A! x ! ! PPE@A! v B!A! t 1BR@A ! t 1BR@A ! s 1BR@A ! r 1B.R@A ! q B!A! o B|!  SE@A! n  |1B=QE@A! m  7X  7x BS@A! l  XE@A! k B XE@A! j B||!B }!  B?B|! B|" XE@A! i  78  7h  }"B|!  7(  B }B?|!  7` BQE@A! h 1BQE@A! g 1BQE@A! f B! BQE@A0! d 1BQE@A0! c 1"BQE@A0! b 1BQE@A0! a B')!B!A)! _ | < B|!  SE@A ! ^ B')!B')!  TE@A! ] B!  |B< E@A.! \ B! B')! B')!  T@A(! Z A! Y B')!B')!  TE@A! Y |1!A-! W  <'B')!  7PB!A2! V B|!  SE@A;! U B')! B')!  TE@A! T B! |" )! )! QE@A1! S  7@  7H  7 B| 7 B| 7 Ak"$ B7A*#! R B|1@A8! Q 1'! )P! )X! )x! )@! )8! )h!A1! O B')!B')! )@ TE@A! O  )H|B<B')!B')! )@ TE@A! N  )H| 1'< )X! )x!A ! L Ak"$ B7A#! L B7 B|B7 Ak"$ B7A#! K  )h7 B| )87 Ak"$ B7A#! J B7 B|B7 Ak"$ B7A#! I Ak"$ B7A#! H )X! )x!A ! F Ak"$ B€7A#! F B7 B|B7 Ak"$ BÀ7A#! E  )`7 B| )(7 Ak"$ BĀ7A#! D B7 B|B 7 Ak"$ Bŀ7A#! C  )h7 B| )87 Ak"$ Bƀ7A#! B B7 B|B7 Ak"$ Bǀ7A#! A Ak"$ BȀ7A#! @ )X! )x!A ! > BQE@A! > 1BQE@A! = 1"BQE@A! < 1BQE@A! ; B!A#! 9  70  7 Ak"$ Bπ7A#! 9 B7 B|B!7 Ak"$ BЀ7A#! 8  )7 B| )07 Ak"$ Bр7A#! 7 B7 B|B7 Ak"$ BҀ7A#! 6 Ak"$ BӀ7A#! 5 )X! )x!A ! 3 B!A! 2  XE@A! 2 B|" XE@A! 1  }B|!  B }B?|! ! !A ! / B!A! . B')!B')!B SE@A! .  7XB!A! , B |! B| A#! B|1E@A! +  7P  7p B|1"E@A! * B|)"P@ Ak"$ B߀7A#! * 1E@A! ) E@A! ( B|)"P@ Ak"$ B〄7A#! (  < B|" S@A! & Aj"$ Aj"$A B|1E@A! $ B|)!  7x B|)!  7H Ak"$ B逄7A#! # B7 B|B7 Ak"$ Bꀄ7A#! "  )x7 B| )H7 Ak"$ B뀄7A#! ! B7 B|B7 Ak"$ B쀄7A#! Ak"$ B퀄7A#!  )X! )p! )P!A!  B|)!  7x B|)!  7H Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )x7 B| )H7 Ak"$ B񀄀7A#!  B7 B|B7 Ak"$ B򀄀7A#!  Ak"$ B󀄀7A#!  )X! )p! )P!A!   )@7 B| 7 Ak"$ B7A#!   )@7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#! B7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   A ]#!@@@  #(M@ Ak"$ B7A#!  B|B< Aj"$A A i~#!@@@  )P@ Ak"$ B7A#!  )5! B| > Aj"$A A i~#!@@@  )P@ Ak"$ B7A#!  )1! B| < Aj"$A A h#!@@@  )P@ Ak"$ B7A#!  ) )1 1< Aj"$A A h#!@@@  )P@ Ak"$ B7A#!  ) )1 1< Aj"$A A h#!@@@  )P@ Ak"$ B7A#!  ) )5 5> Aj"$A A h#!@@@  )P@ Ak"$ B7A#!  ) )5 5> Aj"$A A ^#!@@@  )P@ Ak"$ B7A#!  ) 5> Aj"$A A ^#!@@@  )P@ Ak"$ B7A#!  ) 1< Aj"$A A #!@@@@@@@@@   )P@ Ak"$ B7A#!  )) )QE@A!  B.5PP@A!  ) )7 B |B< Aj"$A ) ) Ak"$ B7#!A!  B |B< Aj"$A A #!@@@@@@  )P@ Ak"$ B7A#!  )5 5BQE@A!  ) 5> B|B< Aj"$A B|B< Aj"$A A i~#!@@@  )P@ Ak"$ B7A#!  ))! B| 7 Aj"$A A i~#!@@@  )P@ Ak"$ B7A#!  ))! B| 7 Aj"$A A z~#!@@@  )P@ Ak"$ B7A#!  )) )|! ) 7 B| 7 Aj"$A A i~#!@@@  )P@ Ak"$ B7A#!  )5! B| > Aj"$A A i~#!@@@  )P@ Ak"$ BĀ7A#!  ))! B| 7 Aj"$A A i~#!@@@  )P@ Ak"$ BȀ7A#!  ))! B| 7 Aj"$A A z~#!@@@  )P@ Ak"$ B̀7A#!  )5 4|! ) > B| > Aj"$A A z~#!@@@  )P@ Ak"$ BЀ7A#!  )) )|! ) 7 B| 7 Aj"$A A z~#!@@@  )P@ Ak"$ BԀ7A#!  )) )|! ) 7 B| 7 Aj"$A A w~#!@@@  )P@ Ak"$ B؀7A#!  )5! ) 5> B| > Aj"$A A w~#!@@@  )P@ Ak"$ B܀7A#!  ))! ) )7 B| 7 Aj"$A A #!@@@@@@  )P@ Ak"$ B7A#!  )) )QE@A!  ) )7 B |B< Aj"$A B |B< Aj"$A A ^#!@@@  )P@ Ak"$ B7A#!  ) 5> Aj"$A A ^#!@@@  )P@ Ak"$ B7A#!  ) )7 Aj"$A A #!@@@@@@  )P@ Ak"$ B7A#!  )5 5BQE@A!  ) 5> B|B< Aj"$A B|B< Aj"$A A #!@@@@@@  )P@ Ak"$ B7A#!  )) )QE@A!  ) )7 B |B< Aj"$A B |B< Aj"$A A ^#!@@@  )P@ Ak"$ B7A#!  ) )7 Aj"$A A i~#!@@@  )P@ Ak"$ B7A#!  ))! B| 7 Aj"$A A '~#! )" )7 Aj"$A E Q@B @ P@B 1 1R@B B|! B|! B}! /#!  ) ) ))#!< Aj"$A 0#!  ) )#))#!< Aj"$A  !@@ AG" AqAGq@@ - F  Aj"AG" Aj"AqAGq  A!  - "F@ ! Al!@@ AK@ !@ ( s"AxqAxs AwjqE@ Aj! A|j"AK    !   E@A!  @ - F  Aj! Aj" A! A  D~#!  ) - ),#!!B  )} P7 Aj"$A ~#!@@@@  #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 B|B7 Ak"$ B7A#!  )! B8| 7 A j"$ Aj"$A A ]#!@@@  #(M@ Ak"$ B7A#!  B|B< Aj"$A A ~#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )0! )0! B| B BQ< Aj"$A A ~#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )2! )2! B| B BQ< Aj"$A A ~#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )4! )4! B| B BQ< Aj"$A A ~#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))! ))! B|  Q< Aj"$A A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )) ))QE@A!  )) ))Q! B| < Aj"$A B!A!  A }#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )*! )*! B|  [< Aj"$A A |#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )+! )+! B|  a< Aj"$A A }#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )B|*! )*! )B|*! )*! B|  [  [< Aj"$A A |#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )B|+! )+! )B|+! )+! B|  a  a< Aj"$A A ~#!@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!  )0P@ Ak"$ B7A#!  )()! )()! )0)!  )0)Q@A!  B! B8| < A j"$ Aj"$A  7 B| 7 B| 7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@   #(M@ Ak"$ Bā7A#!  A k"$ )(P@ Ak"$ Bā7A#!  )0P@ Ak"$ Bā7A#!  )()! )()! )0)! )0)!  Q@A!  B! B8| < A j"$ Aj"$A  7 B| 7 B| 7 Ak"$ Bā7A=#!  B|1!A!  A ~#!@@@@@@@@   #(M@ Ak"$ Bȁ7A#!  A k"$ )(P@ Ak"$ Bȁ7A#!  )0P@ Ak"$ Bȁ7A#!  )()! )()! )0)! )0)!  Q@A!  B! B8| < A j"$ Aj"$A  7 B| 7 B| 7 Ak"$ Bȁ7A<#!  B|1!A!  A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B́7A#!  A8k"$ )@PPE@A! )@)"PPE@A !  )@1B BPPE@A! B| )H )PQ< A8j"$ Aj"$A )!  )H7 B| )P7 $  Ak"$ B́7Av!A #! B|1! B| < A8j"$ Aj"$A B|B< A8j"$ Aj"$A  )@7 Ak"$ B́7A#!  B|)! B|)! B7 B|B7 B|B7 B| 7 B | 7 Ak"$ B́7A#!  B0|)!  B(|)7 B| 7 Ak"$ B́7A#!  B|)! B7 B| 7 Ak"$ B́7A#!   A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ BЁ7A#!  A8k"$ )@PPE@A ! )@)"P@ Ak"$ BЁ7A#! )"PPE@A !  1B BPPE@A! B| )H )PQ< A8j"$ Aj"$A )!  )H7 B| )P7 $  Ak"$ BЁ7Av!A #! B|1! B| < A8j"$ Aj"$A B|B< A8j"$ Aj"$A  7 Ak"$ BЁ7A#!  B|)! B|)! B7 B|B7 B|B7 B| 7 B | 7 Ak"$ BЁ7A#!  B0|)!  B(|)7 B| 7 Ak"$ BЁ7A#!  B|)! B7 B| 7 Ak"$ BЁ7A#!   A #!@@@@  #(M@ Ak"$ Bԁ7A#!  Ak"$ B.7 B|B 7 B|B 7 Ak"$ Bԁ7A#!  B.B.)B7B.B.)B7B.B.)B7B.B.)B7 Aj"$ Aj"$A A ~#!@@@@@@@  Ak"$#)0"P@ Ak"$ B؁7A#!  )"P@ Ak"$ B؁7A#!  )P@ Ak"$ B؁7A#!  )-"P@ Ak"$ B؁7A#!   ))! ) !  7  7 )-B|!  7-  )-RE@A!  Aj"$ Aj"$A ) !  )7 B| 7 Ak"$ B؁7A#!  A!  A #!@@@@@@@@  Ak"$B.1@A!   )7 B| ) 7 Ak"$ B܁7A(#!  Aj"$ Aj"$A  )7 B| ) 7 Ak"$ B܁7A?#!  A!  A ~#!@@@@@@@@@@@@@  A(k"$B.1E@A ! )0PPE@A ! #)0!  7 P@ Ak"$ B7A#!  )B|7  4B|> )"P@ Ak"$ B7A#! B7 Ak"$ B7A#! ) B<  )07 B| )87 Ak"$ B7A#!  B|4!  > ) B< ) ) 4B|> Ak"$ B7A#!  B| 4> A(j"$ Aj"$A B7 B|B 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@   Ak"$ )PPE@A!   )7 Ak"$ B7A#!  B|1@A!   B'7 Ak"$ B7A#!  B|)"PPE@A!  )! )! B SE@A!  )!B!A ! B|! )"P@ Ak"$ B7A#!  )! )! !  XE@A! T@A!  )! )!  XE@A! T@A!  B|" S@A !  B |B< Aj"$ Aj"$A B |B< Aj"$ Aj"$A B!B!A !  B |B< Aj"$ Aj"$A B |B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@   A(k"$  )87 Ak"$ B7A#!  1E@A!   )07 Ak"$ B7A#!  B|1@A!  #)0"P@ Ak"$ B7A#!  )#QE@A ! A(j"$ Aj"$A )P#Q@A! 4BPP@A!  )07 Ak"$ B7A#! B|1E@A!  A(j"$ Aj"$A B|B7 B|B7 B |B7 B|B7 B| )87 B | )07  B|7 Ak"$ B7A#!  A(j"$ Aj"$A A(j"$ Aj"$A A(j"$ Aj"$A A(j"$ Aj"$A A #!@@@@@@@@@@@@@   A k"$ )(P@ Ak"$ B7A#! )()P@A !  )87 Ak"$ B7A#! B|1E@A !  )07 Ak"$ B7A#!  B|1E@A !  A j"$ Aj"$A  )(7 B| )87 B| )@7 B| )H7 Ak"$ B7A#!  A j"$ Aj"$A A j"$ Aj"$A A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@  A0k"$ )8P@ Ak"$ B7A#!  )8)P@A!   )H7 Ak"$ B7A#! B|1E@A!  )@7 Ak"$ B7A#! B|1@A! B! )H!A!   7  7( )8)!  )87 B| 7 B|B7 B| 7 Ak"$ B7A#!  ) B|! )( )8)|!  )PS@A !  A0j"$ Aj"$A A0j"$ Aj"$A A0j"$ Aj"$A A0j"$ Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ o  !"#$%%&''()*+,-./0012234 Ak"$ )P@ Ak"$ B7A#! 6 ))" )X@A! 5  )}" )T! )1BB!  ) ! P@A! 4  7 B'7 Ak"$ B7A#! 3 B|)"PPE@A! 2 )! )! B SE@A! 0 )!B!A! . B|! )"P@ Ak"$ B7A#! .  )! )! !  XE@A! - T@A! ,  )! )!  XE@A! + T@A! * B|" S@A ! ) BȊ))"P@ Ak"$ B7A#! )  ! B"BTE@A! (  B|)"B| B BB|)"P@ Ak"$ B7A#! ( P@ Ak"$ B7A#! (   B|7 Ak"$ B7A #! ' B|1BQ@A! & BȊ))"P@ Ak"$ B7A#! &  )! B"BTE@A! %  B|)"PPE@A! $  BB|! BB! B|! )! )!B!A;! ! B|!   |TE@A!  78 P@ Ak"$ B7A#!  1! BB! B B BT!  XE@A!  BBPP@A!   BBTE@A!  B|!A:!   RE@A!   B|!B!A:!   7 B| > B | > B| 7 Ak"$ B΀7A#!  B$|5! B |5! B|)! B(|)! )! )! )8!A:!   >4  >0  7H  7@ ) |)!  7 Ak"$ BҀ7A#!  B|1@A!  )@! 50! 54! )H! )! )! )8!A!  Aj"$ Aj"$A B!B!B!B!A9!  B|A#! B|B7 B| )7 B| )7 B| )7 B| )7  B|7 Ak"$ Bۀ7A#!  Aj"$ Aj"$A   }! )!   |7 B| 7 B| } )|7 B| )7 Ak"$ Bހ7A#!  Aj"$ Aj"$A   }! )!   |7 B| 7 B| } )|7 B| )7 Ak"$ B7A#!  Aj"$ Aj"$A )!A! B!B!A ! )) !  )7 B| 7 B| )7 B| 7 Ak"$ B7A#! Aj"$ Aj"$A Aj"$ Aj"$A B7 B|B$7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@   A8k"$ )PB!BBT! B "BB ! )P }" )X|!  7X )H |! )@ |!  70B!B!A!  B|!  TE@A!  B?PE@A! P@ Ak"$ B7A#! 1! B|! PPE@A! Bx|!A! BBPPE@A!  7  >  7(  7  |)!  7 Ak"$ B7A#!  B|1@A!  )(! )0! ) ! )X! )! 5!A!  BB!A!  A8j"$ Aj"$A B7 B|B$7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2  #(M@ Ak"$ B7A#!  Ak"$ )pP@ Ak"$ B7A#!  )p)"P@A/!   )X@A.!   )}" )T! )p1"B"B!  ) ! P@A,!  BB"BQE@A!  )p! )x! )!B!A !  B|!  }!  )@TE@A!  )0"P@ Ak"$ B7A#!   )T@A!  )0"P@ Ak"$ B7A#!   )"   T!  }!  }!  |!  XE@A!  Aj"$ Aj"$A  70  7  7P  7  7 B| 7 B| 7 B| 7 Ak"$ B7A#!  )! )p! )P! ) ! )0!A !  Aj"$ Aj"$A BQE@A0!  )p)@! )p)8!B SE@A!   7@ )x! )!B!A!  B|! )"P@ Ak"$ B7A#!   )T@A(!  )"   T!  }!  }!  |!  X@A'! B|!  }!  S@A! A! Aj"$ Aj"$A  78  7`  7(  7  7H  7X  7 B| 7 B| 7 B| 7 Ak"$ B7A#! )! )@! )`! )H! )(! )8! )X!A"!  )p) !  )x7 B| 7 B| )7 B| 7 Ak"$ B7A#!  Aj"$ Aj"$A Aj"$ Aj"$A Aj"$ Aj"$A BЏ7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ =  !"# #(M@ Ak"$ B7A#! % A8k"$ )@P@ Ak"$ B7A#! % )@)0"P@ Ak"$ B7A#! % )!B X@A;! $ B 1T@A9! #   )HBTE@A3! " )H ~!B! @A7! B T@A7!  )HBS@A7!   7( P@A)!  )PE@A"!   B|7 B|B7 B|B< Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!  B.5P! B|! P@A !   7 P@ Ak"$ B7A#!   )()=B.5PP@A!   )(7  )H7 B| 7 A8j"$ Aj"$A B | )( Ak"$ B7#!A!  B|  Ak"$ B7#!A!   7 B7 Ak"$ B7A#!  B|)!  70  ) 7 B| )(7 B|B< Ak"$ B7A#!  B.5P! B|)! P@A'!  )0 7 )0!A!  )0B|  Ak"$ B7#!A&! B7 B|B7 B|B< Ak"$ B7A#! B|)"P@ Ak"$ B7A#! B.5P! B|! P@A1!  7A!   Ak"$ B7#!A! P@A ! B  )HT! )H ~!A !  B7 B|B 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B7 B|B&7 Ak"$ B7A#!   A #!@@@@  A(k"$  )07 B| )87 B|B< B| )(7 Ak"$ B7A#!  A(j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$%&'()*+,,-.//00111112344445566667788889999:;<<<<==>?@@@@AABBBCDEFGHIJKLLLLMMNOPQR #(M@ Ak"$ B7A#! T Ak"$ )PPE@A! S 1"@A ! R )5PE@A ! Q  ))"PE@A! P ))8P! E@A ! N B|B< Aj"$ Aj"$A B.)PP@A! L B!  70 )B|!  7@  7 Ak"$ B7A#! J )5PP@A! I  )B8|7 Ak"$ B7A#! H B|)"PP@A! G )) ))T@A! F 1"@A! E   )@7 Ak"$ B7A#! D B|B< Aj"$ Aj"$A #7H Ak"$ B7A#! B )"P@ Ak"$ B7A#! B B7( )0PPE@A"! A B7( B.5PP@A! ?  )7 B7@  )H7 B<4 B|!B.5PP@A! =  )7P )HP@ Ak"$ B7A#! < )HB|!B.5P! )HB|! P@A! ; )H 7 )HB7 B.5PP@A! 9 B7  78  7`  7X  7P )B|! ))P"PPE@A! 7 B.5PP@A! 6  7  7 ) 7P  )HB|7 B|B< Ak"$ B7A#! 4 BЉ7 B| )@7 B|B< B|B< B|B7 Ak"$ B7A#! 3 )8 )H)R@A! 2 B.5PP@A! 1 )HB7 )HB< )815!B.5PP@A! / )HB7  </ )8)(!B S@A! - B.5PP@A! , )8B7P  )87 Ak"$ B7A#! * 1/"E@A! ) B|B< Aj"$ Aj"$A )`B Ak"$ B€7#!A>! &   )0}7 B|B7 Ak"$ BĀ7A#! & 1/!A B|B7A! ) B<5  B|)7 B| 7  )8B8|7 Ak"$ B7A#! ( B|)"PPE@A,! '  7 )"PP@A ! & )(PP@A! % )"P@ Ak"$ B7A#! % B.5PP@A! $  7A ! " B|  Ak"$ B7#!A ! !  Ak"$ B7A#! ! ) )7( ) !A!   )8) 7 B| 7 Ak"$ B7A#!  B.5PP@A$!  ) B7 ) !A!  ) B|B Ak"$ B7#!A#!  B<5  B|)7 B| 7  )8B|7 Ak"$ B7A#!  B|)"PPE@A!  B.5PP@A>!  B7 )(PP@A8!  )"P@ Ak"$ B7A#!  B.5PP@A6!   7A&!  B|  Ak"$ B7#!A&!   7 Ak"$ B7A#!  ) )7( )!A2!  B|B Ak"$ B7#!A1!    )(7 Ak"$ BĀ7A#!  A! P@ Ak"$ Bǀ7A#! B7  7 B|B7 Ak"$ Bɀ7A#!  B|)"P@A!  "PPE@A! B| )7A!  A0j"$ Aj"$A   )(7 Ak"$ Bڀ7A#!  B7 B|B 7 Ak"$ B܀7A#!  B7 B|B 7 Ak"$ Bހ7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  ))PE@A!   )B|7 Ak"$ B7A#!  B|)! B | P< Aj"$ Aj"$A  )7 Ak"$ B7A#!  B|)! B | P< Aj"$ Aj"$A A #!@@@@  A k"$  )(7 B| )07 B|B< Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"""#$%%&'()*+,-.///012233333456666778888999:::;<====>>?@ABBBBCCDEFGHIJKLMNOPQRSTUVW #(M@ Ak"$ B7A#! Y Ak"$ )PPE@A! X 1"E@A! W B! @A! U B.)PP@A! T B!  70 )B|!  7H  7 Ak"$ B7A#! R )5PPE@A! Q ))P@A! P  )B|7 Ak"$ B7A#! O B|)"PP@A! N ))PPE@A#! M  ))0! )P! ))  )3~|! P@A! L  )) 7 B| 7 Ak"$ B7A#! K ))0B|! ) 70  ))QE@A! J )B70 ) ))B|7  )H7 Ak"$ B7A#! H B|B< B|B< Aj"$ Aj"$A  7@  )) 7 B| )7 B| 7 Ak"$ B7A#! F )@!A! D 1"@A*! D   )H7 Ak"$ B7A#! C B|B< B|B< Aj"$ Aj"$A #7P Ak"$ B7A#! A )"P@ Ak"$ B7A#! A B7( )0PPE@A/! @ B7( B.5PP@A! >  )7 B7@ )PP@ Ak"$ B7A#! = )PB|!B.5PP@A! < )P 7  )P7 B<4 B|!B.5P! )PB|! P@A! :  )7P )PB7 B.5PP@A! 8 B7  78  7h  7`  7X )B|! ))@"PPE@A! 6 B.5PP@A! 5  7  7 ) 7@  )PB|7 B|B< Ak"$ B€7A#! 3 BЉ7 B| )H7 B|B< B|B< B|B7 Ak"$ BÀ7A#! 2 )8 )P)R@A! 1 B.5PP@A! 0 )PB7 )PB< )8)(!B S@A! . )815!  </B.5PP@A! - )PB7 )8B7P  )87 Ak"$ Bʀ7A#! + B|B< B| 1/< Aj"$ Aj"$A )XB Ak"$ B̀7#! )`B Ak"$ B̀7#!A! (   )0}7 B|B7 Ak"$ Bπ7A#! ( A! & )hB Ak"$ Bр7#!A! % B|  Ak"$ BԀ7#! B|  Ak"$ BՀ7#!   Ak"$ Bր7#!A! $ B.5PP@A! $ B7 ) 78 ) 7@A! " B|B Ak"$ Bڀ7#! )B8|  Ak"$ Bۀ7#!   Ak"$ B܀7#!A! ! B|B Ak"$ Bހ7#!A;!  ) Ak"$ Bဤ7#! B Ak"$ B․7#!A7!    Ak"$ B䀤7#!  )P Ak"$ B値7#!A5!  B| ) Ak"$ B瀤7#! B|B Ak"$ B耤7#!A1!  B|B7 B|B7 B|B7 B| )7  )7 B| 7 B| )7 B| B|7 B |B7 Ak"$ Bꀤ7A#!  B|B< B|B< Aj"$ Aj"$A   )H7 Ak"$ B7A#!  )PP@A!  B|B< B|B< Aj"$ Aj"$A  )) 7 B| )7 Ak"$ B󀤂7A#!  A!   Ak"$ B7A#!  )!A!   )B|7 Ak"$ B7A#!  B|5P@A!   )7 Ak"$ B7A#!  B|1E@A!  )PP@A!  B|B< B|B< Aj"$ Aj"$A  )) 7 B| )7 Ak"$ B7A#!  A! 1!A! B|B< B|B< Aj"$ Aj"$A  )7 Ak"$ B7A#! B|1! 1!A!  1"@A!  B|B< B|B< Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!  B7 B|B7 B|B< B|B< B|B7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@ .  #(M@ Ak"$ B7A#!  A(k"$ )0P@ Ak"$ B7A#!  )0)PE@A!  )@PP@A!  )8P@ Ak"$ B7A#!  B.5PP@A!  )8B7 )HP@ Ak"$ B7A#!  )8)!  7 )H$ )H) Ak"$ B7Av!A #!  ) P@ Ak"$ B7A#!  B.5PP@A!  ) )87 )8B<5 )8)(PP@A!   ) 7 B| )PB|7 Ak"$ B7A#!  A(j"$ Aj"$A  Ak"$ B7A#!  )8 )7(A! ) B| )8 Ak"$ B7#!A! )8B|B Ak"$ B7#!A !  )0) 7 B| )87 B| )@7 Ak"$ B7A#! A!   )0)0! )@P! )0)  )03~|! P@A*!  )8P@ Ak"$ B7A#!  )0) ! )8)!  7 B| 7 B| 7 Ak"$ B7A#!  )0)0B|! )0 70  )0)QE@A)!  )0B70 )0 )0)07(A!   7  )0) 7 B| )@7 B| 7 Ak"$ B7A#!  )!A$!  A #!@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  )B<  )B|7 B|B< Ak"$ B7A#!    ) 7 Ak"$ B7A#!  B(|B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )(!A!  )(! P@ Ak"$ B7A#!  )"PPE@A!  )"PPE@A! B.5PP@A! B7  7 B7  7 14E@A! )"P@ Ak"$ B7A#!  B|7 B|B> B |B> Ak"$ B7A$#! B|1E@A!  B0| )7 A j"$ Aj"$A B|B Ak"$ B7#!   Ak"$ B7#! B|B Ak"$ B7#!A !  B.5PP@A!  B7 B7A !  B Ak"$ B7#! B|B Ak"$ B7#!A !  B0|B7 A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$B')!B')! PPE@A!  B SE@A !   7( )X!B!A!  B|! )! )!  SE@A!   |1B=Q@A !  B! @A ! B|" S@A! B|B7 B|B7 Aj"$ Aj"$A B|" XE@A!  }B|! B|  B }B?|7 B| 7 Aj"$ Aj"$A  70  7@  78  7   7 B| )P7 B| 7 Ak"$ B7A*#!  B|1! )@! )(! )X! )0! ) ! )8!A!  B!A!   7 B| 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ !  #(AjM@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  ))"PP@A!  B !B!  7`  7  ))7 Ak"$ B7A#!  ))"P! B|)! B|)! PE@A!   7  7p  7 Ak"$ B7A#!  )) ! B|)! B|)! PE@A!   7  7h B|A #! B|B7 B|B7 B| )7 B| )`7 B|B7 B|B7( B| 7 B| 7 B|B7 B|B7H B| )7 B| )p7 B7 B| B|7 B|B7 B|B7 Ak"$ B7A#!  B |)! B(|)! )h )pQ@A !  B| 7 B| 7 Aj"$ Aj"$A  7  7X  )7 B| )7 B| )h7 Ak"$ B7A*#!  B|1@A!  )X! )!A !   ))7 Ak"$ B7A#!  B|)!  7x B|)!  7  ))7 Ak"$ B7A#!  )x B|)RE@A!  B7 B| )7 B| )X7 B|B7 B |B 7 Ak"$ B7A#!  B0|)! B(|)! ! ! !A !  )7 B| )x7 Ak"$ B7A*#! B|1E@A! B7 B| )7 B| )X7 B|B7 B |B7 Ak"$ B7A#! B0|)! B(|)!A!  B|A #! B|B7 B|B7 B| 7 B| 7 B|Bނ7 B|B7( B| )7 B| )p7 B|B7 B|B7H ))! )) ! B| 7 B| 7X B7 B| B|7 B|B7 B|B7 Ak"$ B7A#!  B |)! B(|)! B| 7 B| 7 Aj"$ Aj"$A B7 B|B7 B|B7 B| )7 B | )`7 B(|B7 B0|B 7 B8| 7 B| 7 Ak"$ B7A#!  B|)! B|)! B| 7 B| 7 Aj"$ Aj"$A  7 Ak"$ B7A#!  B|)! B|)!A!  A ~#!@@@@  #(M@ Ak"$ B7A#!  A8k"$ B7 B|Bј7 B|B7 B| )@7 B | )H7 Ak"$ B7A#!  )(! )0! B| 7 B| 7 A8j"$ Aj"$A A o#!@@@  #(M@ Ak"$ B7A#!  B| )7 B | )7 Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ {  !""###$%&&&&'()*++++,--....///011111112334566789::;<=>>?@ABCD #(AjM@ Ak"$ BĂ7A#! F Ak"$ 1B"BTE@A! E B!B' |")! )! 1"E@A! D )BSE@A! C B' |")! )!  7  7 B|B7 B|A #!  B|7 B|Bј7 B|B7 Ak"$ BĂ7A#! A )! )! 1! )! )!B!B!B! B|! A! ? B|! !  SE@A! >  TE@A! =  7  |1" B%RE@A! < B|! T@A ! ; | <A! 9  7x  <G B7 B| 7 B| 7 B| 7 B | 7 Ak"$ BĂ7A#! 9 B0|)! B8|)! B(|)! B|! )! )! 1! )! )! )x! )! 1G! A ! 7 B|" TE@A! 7  7  |1" BQE@A! 6  E@A?! 5 BSE@A?! 4 B|! T@A8! 3 |B-<B }! B|B7 B|B7 B|B7 B! A ! 0 B| | B0|< B|! ! B XE@A"! / B !  B ~}! BT@A! . A! - BTE@A! - B| | B0|< }B|!  7HB }! B }B?! T@A1! ,  7  7`  |7 B| B| |7 B| 7 Ak"$ BĂ7A#! + )H! )`! )! )! )! )! ! ! ! )! )! 1!A! (  7  7h  7 B7 B| 7 B| 7 B| 7 B | 7 Ak"$ BĂ7A#! ( B8|)! B(|)! )! )! )h! A'! & B7 B| 7 B| 7 B| 7 B | 7 Ak"$ BĂ7A#! & B0|)! B8|)! B(|)! B|! )! )! 1! )! )! )! )!A! $ ! !A! # BQE@A! #  BSE@A! " B|! T@A! ! |B-<B }! B|B7 B|B7 B|B7 B! A!  B| | B0|< B|! ! B XE@A!  B !  B ~}! BT@A!  A!  BTE@A!  B| | B0|< }B|!  7PB }! B }B?! T@A!   7p  7  |7 B| B| |7 B| 7 Ak"$ BԀĂ7A#!  )P! )p! )!A*!   7  7X  7 B7 B| 7 B| 7 B| 7 B | 7 Ak"$ BڀĂ7A#!  B8|)! B(|)! )! )! )X! A!  B7 B| 7 B| 7 B| 7 B | 7 Ak"$ BހĂ7A#!  B0|)! B8|)! B(|)! B|! )! )! 1! )! )! )! )!A!  ! !A!  ! ! !A*!  B7 B| 7 B| 7 Ak"$ BĂ7A#!  B|)! B |)! B| 7 B| 7 Aj"$ Aj"$A  7 B|B7 Ak"$ BĂ7A#!   7 B|B7 Ak"$ BĂ7A#!  7 B|B7 Ak"$ BĂ7A#!  7 B|B7 Ak"$ BĂ7A#!  7 B| 7 Ak"$ BĂ7A#!   7 B| 7 Ak"$ BĂ7A#!   7 B|B7 Ak"$ BĂ7A#!   A ~}|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ v  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstu #(M@ Ak"$ BȂ7A#! w Ak"$ )PPE@A! v )5"B XE@A9! u BXE@A ! t BXE@A! s BōQE@A! r B )QE@A ! q )1!  < Ak"$ BȂ7A#! p  1< Ak"$ BȂ7A#! o Ak"$ BȂ7A#! n Aj"$ Aj"$A  )7 B| )7 Ak"$ BȂ7A#! k A ! i BQE@A ! i B )QE@A ! h )+!  9h Ak"$ BȂ7A#! g  +h9 Ak"$ BȂ7A#! f Ak"$ BȂ7A#! e A ! c B߄QE@A! c B )QE@A ! b )1!  < Ak"$ BȂ7A#! a  17 Ak"$ BȂ7A#! ` Ak"$ BȂ7A#! _ A ! ] BQE@A ! ] B )QE@A ! \ )B|*!  8 )*!  8 Ak"$ BȂ7A#! [  *9 B| * 9 Ak"$ BȂ7A#! Z Ak"$ BȂ7A#! Y A ! W B XE@A-! W BƱQE@A'! V B )QE@A ! U ))!  7P Ak"$ BȂ7A#! T  )P7 Ak"$ BȂ7A#! S Ak"$ BȂ7A#! R A ! P B QE@A ! P B )QE@A ! O ))!  7p Ak"$ BȂ7A#! N  )p7 Ak"$ BȂ7A#! M Ak"$ BȂ7A#! L A ! J B QE@A3! J B )QE@A ! I )*!  8, Ak"$ BȂ7A#! H  *,9 Ak"$ BȂ7A#! G Ak"$ BȂ7A#! F A ! D B QE@A ! D B )QE@A ! C )B|+!  9@ )+!  90 Ak"$ BȂ7A#! B  +09 B| +@9 Ak"$ BȂ7A#! A Ak"$ BȂ7A#! @ A ! > BЫ XE@A! > B XE@A! = B QE@A! < B )QE@A ! ; )4!  >$ Ak"$ BȂ7A#! :  4$7 Ak"$ BȂ7A#! 9 Ak"$ BȂ7A#! 8 A ! 6 B QE@A ! 6 B )QE@A ! 5 ))!  7H Ak"$ BĀȂ7A#! 4  )H7 Ak"$ BŀȂ7A#! 3 Ak"$ BƀȂ7A#! 2 A ! 0 B QE@A! 0 B )QE@A ! / )0!  < Ak"$ BʀȂ7A#! .  07 Ak"$ BˀȂ7A#! - Ak"$ B̀Ȃ7A#! , A ! * BЫ QE@A ! * B )QE@A ! ) )5!  >( Ak"$ BЀȂ7A#! (  5(7 Ak"$ BрȂ7A#! ' Ak"$ BҀȂ7A#! & A ! $ BXE@A! $ B QE@A! # B )QE@A ! " ))!  7X Ak"$ B׀Ȃ7A#! !  )X7 Ak"$ B؀Ȃ7A#! Ak"$ BـȂ7A#!  A !  BQE@A !  B )QE@A !  ))!  78 ))!  7x Ak"$ B݀Ȃ7A#!   )x7 B| )87 Ak"$ BހȂ7A#!  Ak"$ B߀Ȃ7A#!  A !  B΁QE@A!  B )QE@A !  )2!  = Ak"$ BȂ7A#!   27 Ak"$ BȂ7A#!  Ak"$ BȂ7A#!  A !  BQE@A!  B )QE@A !  )3!  = Ak"$ BȂ7A#!   37 Ak"$ BȂ7A#!  Ak"$ BȂ7A#! A ! BͺQE@A ! B )QE@A ! ))!  7` Ak"$ BȂ7A#!  )`7 Ak"$ BȂ7A#!  Ak"$ BȂ7A#!  A !  Ak"$ BȂ7A#!  B7 B|B7 Ak"$ BȂ7A#!  Ak"$ BȂ7A#!  A !  A 6~}|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$$$%&'()*+,---./012344456789:;<<<=>?@ABCCCDEFGHIJKLMMMNOPQRSTTTUVWXYZ[\\\]^_`abcccdefghijklllmnopqrssstuvwxyz{|}}}~ #(AjM@ Ak"$ B̂7A#!  Ak"$  B|)7 Ak"$ B̂7A#!  B|)"P@ Ak"$ B̂7A#!  1! )!  7p )!  70 BXE@A!  BXE@A8!  BXE@A%!  BQ@A!  BQ@A!  B|)!  7 Ak"$ B̂7A#!  Bޔ 7 B|B7 Ak"$ B̂7A#!   )p7 B| )07 Ak"$ B̂7A#!  B7 B|B7 Ak"$ B̂7A#!   )7 Ak"$ B̂7A#!  Ak"$ B̂7A#!  Aj"$ Aj"$A B|)"P@ Ak"$ B̂7A#!  )!  7h Ak"$ B̂7A#!   )p7 B| )07 Ak"$ B̂7A#!  Bޔ 7 B|B7 Ak"$ B̂7A#!   )h7 Ak"$ B̂7A#! ~ B7 B|B7 Ak"$ B̂7A#! } Ak"$ B̂7A#! | A! z B|)"P@ Ak"$ B̂7A#! { 1!  < Ak"$ B̂7A#! z  )p7 B| )07 Ak"$ B̂7A#! y Bޔ 7 B|B7 Ak"$ B̂7A#! x  1< Ak"$ B̂7A#! w B7 B|B7 Ak"$ B̂7A#! v Ak"$ B̂7A#! u A! s BQE@A/! s B|)"P@ Ak"$ B̂7A#! s 0!  < Ak"$ B̂7A#! r  )p7 B| )07 Ak"$ B̂7A#! q Bޔ 7 B|B7 Ak"$ B̂7A#! p  07 Ak"$ B̂7A#! o B7 B|B7 Ak"$ B̂7A#! n Ak"$ B̂7A#! m A! k B|)"P@ Ak"$ B̂7A#! l 2!  = Ak"$ B̂7A#! k  )p7 B| )07 Ak"$ B̂7A#! j Bޔ 7 B|B7 Ak"$ B̂7A#! i  27 Ak"$ B̂7A#! h B7 B|B7 Ak"$ B̂7A#! g Ak"$ B̂7A#! f A! d BXE@A! d BQE@A! c B|)"P@ Ak"$ B̂7A#! c 4!  >, Ak"$ B̂7A#! b  )p7 B| )07 Ak"$ B̂7A#! a Bޔ 7 B|B7 Ak"$ B̂7A#! `  4,7 Ak"$ B̂7A#! _ B7 B|B7 Ak"$ B̂7A#! ^ Ak"$ B€̂7A#! ] A! [ B|)"P@ Ak"$ BĀ̂7A#! \ )!  7` Ak"$ Bƀ̂7A#! [  )p7 B| )07 Ak"$ Bǀ̂7A#! Z Bޔ 7 B|B7 Ak"$ BȀ̂7A#! Y  )`7 Ak"$ Bɀ̂7A#! X B7 B|B7 Ak"$ Bʀ̂7A#! W Ak"$ Bˀ̂7A#! V A! T BQE@A! T B|)"P@ Ak"$ B΀̂7A#! T )!  7X Ak"$ BЀ̂7A#! S  )p7 B| )07 Ak"$ Bр̂7A#! R Bޔ 7 B|B7 Ak"$ BҀ̂7A#! Q  )X7 Ak"$ BӀ̂7A#! P B7 B|B7 Ak"$ BԀ̂7A#! O Ak"$ BՀ̂7A#! N A! L B|)"P@ Ak"$ B׀̂7A#! M 1!  < Ak"$ Bـ̂7A#! L  )p7 B| )07 Ak"$ Bڀ̂7A#! K Bޔ 7 B|B7 Ak"$ Bۀ̂7A#! J  17 Ak"$ B܀̂7A#! I B7 B|B7 Ak"$ B݀̂7A#! H Ak"$ Bހ̂7A#! G A! E B XE@A! E B XE@A! D B QE@A! C B|)"P@ Ak"$ B̂7A#! C 3!  = Ak"$ B̂7A#! B  )p7 B| )07 Ak"$ B̂7A#! A Bޔ 7 B|B7 Ak"$ B̂7A#! @  37 Ak"$ B̂7A#! ? B7 B|B7 Ak"$ B̂7A#! > Ak"$ B̂7A#! = A! ; B|)"P@ Ak"$ B̂7A#! < 5!  >( Ak"$ B̂7A#! ;  )p7 B| )07 Ak"$ B̂7A#! : Bޔ 7 B|B7 Ak"$ B̂7A#! 9  5(7 Ak"$ B̂7A#! 8 B7 B|B7 Ak"$ B̂7A#! 7 Ak"$ B̂7A#! 6 A! 4 B QE@A! 4 B|)"P@ Ak"$ B̂7A#! 4 )!  7P Ak"$ B̂7A#! 3  )p7 B| )07 Ak"$ B̂7A#! 2 Bޔ 7 B|B7 Ak"$ B̂7A#! 1  )P7 Ak"$ B̂7A#! 0 B7 B|B7 Ak"$ B̂7A#! / Ak"$ B̂7A#! . A! , B|)"P@ Ak"$ B̂7A#! - )!  7H Ak"$ B̂7A#! ,  )p7 B| )07 Ak"$ B̂7A#! + Bޔ 7 B|B7 Ak"$ B̂7A#! *  )H7 Ak"$ B̂7A#! ) B7 B|B7 Ak"$ B̂7A#! ( Ak"$ B̂7A#! ' A! % BXE@A! % B QE@A! $ B|)"P@ Ak"$ B̂7A#! $ *!  8$ Ak"$ B̂7A#! #  )p7 B| )07 Ak"$ B̂7A#! " Bޔ 7 B|B7 Ak"$ B̂7A#! !  *$9 Ak"$ B̂7A#! B7 B|B7 Ak"$ B̂7A#!  Ak"$ B̂7A#!  A!  B|)"P@ Ak"$ B̂7A#!  +!  9@ Ak"$ B̂7A#!   )p7 B| )07 Ak"$ B̂7A#!  Bޔ 7 B|B7 Ak"$ B̂7A#!   +@9 Ak"$ B̂7A#!  B7 B|B7 Ak"$ B̂7A#!  Ak"$ B̂7A#!  A!  BQ@A!  BQ@A!  BQE@A !  B|)"P@ Ak"$ B̂7A#!  )!  7x )!  7h Ak"$ B̂7A#!   )p7 B| )07 Ak"$ B̂7A#!  B7 B|B7 Ak"$ B̂7A#!   )x7 B| )h7 Ak"$ B̂7A#!  B7 B|B7 Ak"$ B̂7A#!  Ak"$ B̂7A#! A! B|)"P@ Ak"$ B̂7A#! +!  9@ B|+!  98 Ak"$ B̂7A#!  )p7 B| )07 Ak"$ B̂7A#!  +@9 B| +89 Ak"$ B̂7A#! Ak"$ B̂7A#!  A!  B|)"P@ Ak"$ B̂7A#!  *!  8$ B|*!  8 Ak"$ B̂7A#!   )p7 B| )07 Ak"$ B̂7A#!   *$9 B| * 9 Ak"$ B̂7A#!  Ak"$ B̂7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -  !"#$%&'()*+, #(AjM@ Ak"$ BЂ7A#! . Ak"$  )7 Ak"$ BЂ7A#! - )! )!  7 B| 7 Ak"$ BЂ7A#! , )!  7H )!  7  7 B| 7 B|B(< Ak"$ BЂ7A-#! + )"BS@A*! * B|" )HXE@A(! ) B|! )H W@A!! (  )HXE@A&! '  XE@A$! & ) |"1B.R@A!! % 1B(R@A!! $ 1B*R@A!! #  78 )H }!  7P B~|!  7@B }! ) B? |!  7  7 B| 7 B|B)< Ak"$ BЂ7A-#! " B|)"BS@A! ! B|! )@ W@A!  )@XE@A!   XE@A!  ) |"1B)R@A!  1B.R@A!  B|A#! B|B7 B|B 7 B| )7 B| )87 B|B7 B|B7( B| )7 B| 78 B|B7 B|B7H )P }B||! B| )B }B? |7 B| 7X B|B7 B|B7h B| )7 B| 7x B|B7 B|B7 B7 B| B|7 B|B 7 B|B 7 Ak"$ BЂ7A#!  B(|)!  B |)7 B| 7 Ak"$ BЂ7A#!  B|)! B7 B| 7 Ak"$ BЂ7A#!  B7 B|B7 B|B.7 B| )7 B | )@7 Ak"$ BЂ7A#!  B0|)!  B(|)7 B| 7 Ak"$ BЂ7A#!   7 B| 7 Ak"$ BЂ7A#!   7 B| )@7 Ak"$ BЂ7A#!   B|7 B|B7 B|B7 B| )7 B | )@7 Ak"$ BЂ7A#!  B0|)!  B(|)7 B| 7 Ak"$ BЂ7A#!  B7 B|B7 B|B17 B| )7 B | )H7 Ak"$ BЂ7A#! B0|)!  B(|)7 B| 7 Ak"$ BЂ7A#!  7 B| 7 Ak"$ BЂ7A#!  7 B| )H7 Ak"$ BЂ7A#!   7 B| )H7 Ak"$ BЂ7A#!   B|7 B|B7 B|B7 B| )7 B | )H7 Ak"$ BЂ7A#!  B0|)!  B(|)7 B| 7 Ak"$ BЂ7A#!   A  ~#!@@@@@@@@@@@@@@@@@@ `  #(M@ Ak"$ BԂ7A#!  ) )B.)~|! )! )! )!A!  P@ Ak"$ BԂ7A#!  1! 1! B! 1" B ! 1" B ! 1" B ! 1" B( ! 1" B0 ! 1!  B8 !  Bχj~" B! B! B'~!  1B8 1B0 1 B 1 1 B 1 B 1 B 1 B(!  B'~" B! BBץ~!  1B8 1B0 1B( 1B 1B 1B 1 1B!  Bץ~" B! BB[~!  1B8 1B0 1B( 1B 1B 1B 1 1B!  B[~" B! BBχj~! B`|! B |! B X@A!     ! P@A6! BT@A! BX@A! BX@A7! B X@A!  B.) ~!B.) ~!B.) ~!A!  P@ Ak"$ BԂ7A#!   |"Bp|"P@ Ak"$ BԂ7A#!  Bx|"P@ Ak"$ BԂ7A#!  1! 1!  B! 1!  B! 1"B ! 1"B ! 1"B( ! 1"B0 ! 1!   B8! Bχj~"B! 1B8 1B0 1 B 1 1 B 1 B 1 B 1 B(  B!B'~! Bχj~! 1B8 1B0 1B( 1B 1B 1B 1 1B B! BB'~! Bχj~"B! BB'~ 1B0 1B( 1B 1B 1 1B 1B 1B8! Bχj~"B! BB'~!  BBץ~! B | B 7 Aj"$A P@ Ak"$ BԂ7A#!   |Bx|"P@ Ak"$ BԂ7A#!  1! 1!  B! 1!  B! 1"B ! 1"B ! 1"B( ! 1"B0 ! 1!   B8! Bχj~"B! 1B0 1B( 1B 1B 1 1B 1B 1B8  B!B'~! Bχj~"B! BB'~!A6!  P@ Ak"$ BʀԂ7A#!   |B||"P@ Ak"$ B΀Ԃ7A#!  1! 1!  B! 1!  B! 1!  B B! 1B 1 1B 1BBB ! Bχj~"B! BB'~!A6!  P@ Ak"$ B׀Ԃ7A#!   |B|"P@ Ak"$ BۀԂ7A#!   1  B|1B!  1B! Bχj~"B! BB'~!A6!  A ~#!@@@   #(M@ Ak"$ B؂7A#!  )P@ Ak"$ B؂7A#!  B.)! )1! )1! )1! )1! ) B|! B B  BB" " B Bχj~"B B!B'~" BBץ~! B|  B 7 Aj"$A A  ~#!@@@   #(M@ Ak"$ B܂7A#!  )P@ Ak"$ B܂7A#!  B.)! )1! )1! )1! )1! )1! )1! )1! )1! ) B|!  B" B! B B! B  B BB"B  Bχj~"B B!B'~" BBץ~! B|  B 7 Aj"$A A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )(BS@A !  B )(WE@A ! )(y!B }"B||!B BXE@A ! B,!B! ) P@ Ak"$ B7A#! B |"BTE@A!  ) B|7 B|B7 Ak"$ B7A#! Aj"$ Aj"$A B{|"BT! )( B? "  B?B<|BB}! ! !A!  B! )(!A!  ) P@ Ak"$ B7A#!   ) B-|7 B|B7 Ak"$ B7A#!  Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Q  !"#$%%%&&&''(()*+,-./ #(M@ Ak"$ B7A#! 1 Ak"$ )HP@ Ak"$ B7A#! 1 )H)8! )H)@P@A! 0 )PP@ Ak"$ B7A#! 0 )P1BBPE@A ! / 1X"E@A7! . B|B7 Aj"$ Aj"$A B'7 Ak"$ B7A#! ,  B|)7 B| )H7 B| )P7 Ak"$ B7A#! + B|)"PPE@A! * P@ Ak"$ B7A#! * )PP@A! ) 1X"E@A1! ( B|B7 Aj"$ Aj"$A B| 7 Aj"$ Aj"$A  B.7 Ak"$ B7A#! % B')7 B| )H7 B| )P7 Ak"$ B7A#! $ B|)"PPE@A! #  7( B.7 Ak"$ B7A#! " )(!A !  )H)@B|BB |7 B|B7 B|B/7 Ak"$ B7A#! B|)"P@ Ak"$ B7A#! B.5PP@A-!   )H7  )P7  78 B>  7 Ak"$ B7A#!   )87 Ak"$ B7A#!   B.7 Ak"$ B7A#!  )8!A !   )H Ak"$ B7#! B| )P Ak"$ B7#!A$!   7 Ak"$ B7A#!  B|)!  70 B|)!  7 B7 Ak"$ B7A#!  B|)!B.5PP@A!   )P7  )H7  ) 7 B.5PP@A!   )07A!   4!  )H7 B| > Ak"$ B7A#!   B|)7 Ak"$ B7A#!  B|)!  70 B|)!  7 B7 Ak"$ B7A#!  B|)!B.5PP@A!   )P7  )H7  ) 7 B.5PP@A!  )07A! B| )P Ak"$ B€7#! B| )H Ak"$ BÀ7#!A5! B| )P Ak"$ Bŀ7#! B| )H Ak"$ Bƀ7#!A?! B| )0 Ak"$ BȀ7#!A!  B| )0 Ak"$ Bʀ7#!A!  B7 B| 7 Ak"$ B̀7A#!  B7 B| 7 Ak"$ B΀7A#!  B7 B|B7 Ak"$ BЀ7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! A(k"$ )0P@ Ak"$ B7A#! )8P@ Ak"$ B7A#! )@P@ Ak"$ B7A#! )0)B|!  7 )85 )@5B !B!A !  )B|! ) )| )!  7  7   )0B| B|7 Ak"$ B7A#!  B|)"PPE@A!  ) )8QE@A !  ) )@QE@A !  B| 7 A(j"$ Aj"$A B|B7 A(j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  4BPP@A!  B')"P@ Ak"$ B7A#!  )! )"BB~ X@A !  7 B| )H7 Ak"$ B7A#! Aj"$ Aj"$A  7  BB|B7 B|B7 B|B< Ak"$ B7A#! B|)!  7( P@ Ak"$ B7A#!  ) )B7 B0|B7 B8|B7 B0|B7 B8| 7  B0|7 Ak"$ B7A#! )() ) )R@A!  B'7 B| )(7 Ak"$ B7A#!  B')!A!  B7 B|B'7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ )P@ Ak"$ B7A#! ) P@ Ak"$ B7A#! ) )"P@ Ak"$ B7A#! ) )"P@ Ak"$ B7A#! ))B|! 5 5B ! )! ) !B!A!  B|!  | ! !  B| B|")" Q@A!  PP@A!   7 B| 7 Ak"$ B7A(#!  ) ))B|7 Aj"$ Aj"$A Aj"$ Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Y  !"#$$$%%&'()*+,-./012345678 #(A8jM@ Ak"$ B7A#! : Ak"$ )P@ Ak"$ B7A#! : ))!  7x ))!  7X  7 Ak"$ B7A#! 9 )xP@ Ak"$ B7A#! 9 )"P@ Ak"$ B7A#! 9 )x)@! 3!  5|! BXE@A! 8 BXE@A! 7  7  =&  7  7( )B|!  7 )x!B!B! B! A)! 5 )8B|! )X! )! 3&! )@! )p! SE@A! 4 TE@A! 3  78  B|!  7` 4!  7 B| > Ak"$ B7A#! 2  )`4! B|)!  7  )X7 B| > Ak"$ B7A#! 1 B|) )hQ@A! 0 B! E@A! .  )7 Ak"$ B7A#! - B|)! B|)! P@A! , )P@ Ak"$ B7A#! ,  )1BBPPE@A>! + B! E@A! ) )`4!  )X7 B| > Ak"$ B7A#! ( B|)! )0PE@A8! ' )0B|! )(! )x! )8! 3&! )! )! ! )!  SE@A! % )@! )8!  TE@A! $  70  78  7 B!  7P |!  7 4!  7 B| > Ak"$ B7A#! #  )4! B|)!  7h  )x7 B| > Ak"$ B7A#! " B|)!  7  7 Ak"$ B7A#! ! B|)!  7 B|)!  7H  )7 Ak"$ B7A#! B|)! B|)! P@A!   7p  7@ )X! )! 3&! )8! A!  ) )P|!B.5PP@A Ak"$ Bŀ7A#!   B|)7 Ak"$ Bǀ7A#!  B|)! B|)!A!   )7 Ak"$ Bɀ7A#!  B|)! B|)! )H Q@A!  B!A!   7 B| )7 Ak"$ B̀7A*#!  B|1!A! )B7 B| )7 B| )H7 Aj"$ Aj"$A  )x)07 Ak"$ Bπ7A#! B|)! B|)!A6! ) 7 B|B7 B|B7 Aj"$ Aj"$A  7 B| 7 Ak"$ BҀ7A#!  7 B| 7 Ak"$ BԀ7A#!   7 B|B7 Ak"$ Bր7A#!   7 B|B7 Ak"$ B؀7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#!   B'7 Ak"$ B7A#!  )"PPE@A!  )! )! B SE@A!   7B!A! B|! )"P@ Ak"$ B7A#! )! )!B S@A! B|" S@A !  B.7 Ak"$ B7A#! Aj"$ Aj"$A  7(  78  7 B!A!  )0B|! !  7  70  )7 Ak"$ B7A#!  )B|" ) S@A!  )! )8! )(!A!  B!B!A !  A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! A k"$ )0PE@A!  B.! B8| 7 A j"$ Aj"$A B')! B7 B| 7 B|B< Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!   )07B.5PP@A !   )(7A!   )( Ak"$ B7#!A!  A ~#!@@@@@  #(M@ Ak"$ B7A#!  A(k"$ )0P@ Ak"$ B7A#!   )0)7 B| )07 B|B< Ak"$ B7A#!  )!  7 )0)!  7 B| )87 B| 7 Ak"$ B7A#!  B| )07 B| ) 7 A(j"$ Aj"$A A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ )0PPE@A!   )(7 B| )07 B|B< Ak"$ B7A#!  B|)"PPE@A!  B| 7 B| )87 B|B< A j"$ Aj"$A B|B7 B|B7 B|B< A j"$ Aj"$A B|B7 B|B7 B|B< A j"$ Aj"$A A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$B')!  7B!A!  B|! P@ Ak"$ B7A#!   )TE@A !  B| B|)! PPE@A!   7 ) P@ Ak"$ B7A#!  ) )!  7 ) $  Ak"$ B7Av!A #!  )! )!A!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A8k"$ )HP@ Ak"$ B7A#!  )H)B|! )H 7 )HB B!  7 BB" )HR@A !   )@7 Ak"$ B7A#!  )H B|)7  )@7 B| ) 7 Ak"$ B7A!#!  B|1E@A !  A8j"$ Aj"$A  7(  70 Ak"$ B7A#!  B7 B|B,7 Ak"$ B7A#!  )H7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )(7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  ) 7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )07 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! A0k"$  )87 Ak"$ B7A#!  B|)"P@A !   7 BB!  7( P@ Ak"$ B7A#!    7 Ak"$ B7A#!  B|)!  )87 B| ) 7 B| 7 Ak"$ B7A!#!  B|1E@A!  B| )(7 A0j"$ Aj"$A B|B7 A0j"$ Aj"$A A ~#!@@@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$ ) BBBB ) R@A! Aj"$ Aj"$A Ak"$ B7A#! ) !  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B١7 B|B7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$  )7 B|B7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$ )P@ Ak"$ B7A#! ))BQ@A !  #)0"P@ Ak"$ B7A#!  4"BS@A!   B|> )B7 Aj"$ Aj"$A B7 B|B 7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$  )7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ )P@ Ak"$ B7A#! ))P@A!  #)0"P@ Ak"$ B7A#!   4B|>#P@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  4BS@A !  )B7 Aj"$ Aj"$A B7 B|B 7 Ak"$ B7A#!  Bʶ7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! A k"$ )(P@ Ak"$ B7A#! )()"BQ@A ! )(B7 P@A!  A j"$ Aj"$A B')! B7 B| 7 B| )(7 Ak"$ B7A#!   B|))7 B|B7 Ak"$ B7A#!  A!  Bؾ7 B|B7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!"####$$%&&&''()* #(M@ Ak"$ B7A#! , Ak"$#)0"P@ Ak"$ B7A#! , # )Q@A! + #7XB )pW@A#P@ Ak"$ B7A#! ) B')!#)0!  7H B7 B| 7 B| )h7 Ak"$ B7A#! ( B|)"P@ Ak"$ B7A#! ( B.5PP@A8! '  )X7 )HP@ Ak"$ B7A#! &  )H4! )H B|> B"BQE@A!! % #P@ Ak"$ B7A#! % #1E@A!! $ #Bu7 B7 B|B7 B|B< B|B< B|B7 Ak"$ B7A#! " #)0"P@ Ak"$ B7A#! "   4B|>#P@ Ak"$ B7A#! " B')!#)0!  7P B7 B| 7 B| )h7 Ak"$ B7A#! ! )PP@ Ak"$ B7A#! !  )P4! )P B|> BBQE@A! #P@ Ak"$ B7A#! #1E@A!  #Bu7A!   )X Ak"$ B7#!A!  B|B< Aj"$ Aj"$A  Ak"$ B7A#!  )pB=#!B|!B B S! )!  7(  7 Ak"$ BÀ7A#!  #)0"P@ Ak"$ Bŀ7A#!  B|4!  >$  4B|>#P@ Ak"$ Bʀ7A#!  B')!#)0!  7@ B7 B| 7 B| )h7 Ak"$ Bπ7A#!  B|)"P@ Ak"$ BЀ7A#!  )( )p|!  70B.5PP@A!   )X7 B')! B7 B| 7 B| )h7 Ak"$ BԀ7A#!  B|)"P@ Ak"$ BՀ7A#!   )07B.5PP@A!   )X7 )@P@ Ak"$ Bڀ7A#!   )@4! )@ B|> B"BQE@A!  #P@ Ak"$ Bဴ7A#!  #1E@A!  #Bu7 B7 B|B7 B|B< B|B< B|B7 Ak"$ B怴7A#!   4$> Ak"$ B瀴7A#!  B.4"BPP@A!  #)0"P@ Ak"$ B쀴7A#!    4B|>#P@ Ak"$ B7A#!  B')!#)0!  78 B7 B| 7 B| )h7 Ak"$ B7A#! B')! B7 B| 7 B| )h7 Ak"$ B7A#! )8P@ Ak"$ B7A#!  )84! )8 B|> BBQE@A! #P@ Ak"$ B7A#! #1E@A! #Bu7 )hP@ Ak"$ B7A#! )h)! B| BQ< Aj"$ Aj"$A  > Ak"$ B7A#!  B.B>A!   )X Ak"$ B7#!A!   )X Ak"$ B7#!A!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ Ak"$ B7A#! B')! )!  7 B |A #! B7 B| 7 B| B |7 Ak"$ B7A#! A !  B |7 Ak"$ B7A#! B |)"PPE@A!  B(|)"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  )! )! )PE@A !   )WE@A !  B7  7 B|B7 Ak"$ B7A#!  A !  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$B )SE@A !  B.4"BPP@A!  )B=SE@A!  B!  7 Ak"$ B7A#!  B. B|4> B')!B')! P@A!  B|B|)"P@ Ak"$ B7A#! 1E@A! )! B | 7 B(|B< Aj"$ Aj"$A B |B7 B(|B< Aj"$ Aj"$A B> B|B7 Ak"$ B7A#!  B |B7 B(|B< Aj"$ Aj"$A )BSE@A!  )B=#!!A!  B!A!   > Ak"$ B7A#!  B.B>A!  A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B|Bp|!  7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ )  #(M@ Ak"$ Bă7A#!  Ak"$ B7 Ak"$ Bă7A#!  B|)!B.5P"P@A#!  #7  7@ B<B')"B|!B')!B')!  T@A!  B' B|7  B|!B.5PP@A!   7 B')"P@ Ak"$ Bă7A#!  $ ) Ak"$ Bă7Av!A #!  B.4"BPP@A!  )@B< B7 B|B7 B|B< B|B< B|B7 Ak"$ Bă7A#!  B')!B')! B|" TE@A'!   B|!B.5PP@A!  B7 B')!B')B|" XE@A%!  B' 7  B|Bp|7 Ak"$ Bă7A#!  Aj"$ Aj"$A B Ak"$ Bă7#!A!   > Ak"$ Bă7A#!  B.B>A !   Ak"$ Bă7#!A! B7 B| 7 B| 7 B| 7 B | 7 Ak"$ Bă7A#! B(|)! B0|)!B' B8|)7B.5PP@A!! B' 7 ! )@!A!  B'  Ak"$ Bă7#!A !  # Ak"$ Bă7#!A!   7 B| 7 Ak"$ Bă7A#!   7 B| 7 Ak"$ Bă7A#!   A ~#!@@@@@@@@@@@   #(M@ Ak"$ Bȃ7A#! Ak"$ )P@A! )BQ@A!  B')!B')! ) TE@A!   )B|")! )! B | 7 B | 7 Aj"$ Aj"$A B |B7 B |B7 Aj"$ Aj"$A B |B7 B |B7 Aj"$ Aj"$A  )7 B| 7 Ak"$ Bȃ7A#!   A #!@@@@  #(M@ Ak"$ B̃7A#!  Ak"$  )7 Ak"$ B̃7A#!  Aj"$ Aj"$A A #!@@@@  #(M@ Ak"$ BЃ7A#!  Ak"$  )7 Ak"$ BЃ7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ =  !"#$%&'()*+,-./0123456789: #(M@ Ak"$ Bԃ7A#! < Ak"$B'3BR@A;! ; Ak"$ Bԃ7A#! : B!A! 8 B/ B~|B' B|3> B|! BS@A! 7 B.)"P@A9! 6 B T@A0! 5 B T@A'! 4 B| PP@A ! 3 B.)"B| PP@A! 2 B TE@A ! 1 B.B7 B.)PP@A! / A! . B. B|7 B.)!B B BTB.)R@A! - B)7 Ak"$ Bԃ7A#! , Ak"$ Bԃ7A#! + B. )7B!A! )  7 B*7 Ak"$ Bԃ7A#! ) B|)"P@ Ak"$ Bԃ7A#! )  )B(B7 B))7B) 7 )B|! B W@A! ' Aj"$ Aj"$A Ak"$ Bԃ7A#! % B7 B|B7 Ak"$ Bԃ7A#! $ B.)7 Ak"$ Bԃ7A#! # B7 B|B7 Ak"$ Bԃ7A#! " Ak"$ Bԃ7A#! ! B7 B|B7 Ak"$ Bԃ7A#! Ak"$ Bԃ7A#!  B7 B|B7 Ak"$ Bԃ7A#!  B.)7 Ak"$ Bԃ7A#!  B7 B|B7 Ak"$ Bԃ7A#!  Ak"$ Bԃ7A#!  Bݧ7 B|B7 Ak"$ Bԃ7A#!  Ak"$ Bԃ7A#!  B7 B|B7 Ak"$ Bԃ7A#!  B.)7 Ak"$ Bԃ7A#!  B7 B|B%7 Ak"$ Bԃ7A#!  B 7 Ak"$ Bԃ7A#!  B7 B|B7 Ak"$ Bԃ7A#!  Ak"$ Bԃ7A#!  Bݧ7 B|B7 Ak"$ Bԃ7A#!  Ak"$ Bԃ7A#!  B7 B|B7 Ak"$ Bԃ7A#! B.)7 Ak"$ Bԃ7A#! B7 B|B$7 Ak"$ Bԃ7A#! B 7 Ak"$ Bԃ7A#! B7 B|B7 Ak"$ Bԃ7A#! Ak"$ Bԃ7A#!  Bݧ7 B|B7 Ak"$ Bԃ7A#!  B7 B|B7 Ak"$ Bԃ7A#!  B7 B|B7 Ak"$ Bԃ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ r  !"#$%&'()*+,--.//012333444566789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVW #(A(jM@ Ak"$ B؃7A#! Y Ak"$ )P@ Ak"$ B؃7A#! Y   )B|7 )B|B~!  7( B| 7 B|B7 B|B/7 Ak"$ B؃7A#! X ) "PP@A! W )! )(!A ! U  )7  ) )}7  )7  7 ! )"PPE@A! T  7p )" }  1" |" TE@A! S B!  Q@A! Q PPE@A ! P  7  7 B| 7 B|B7 Ak"$ B؃7A#! O )! )(! )p! )!A ! M    17 ! P@A! L !  |" TE@A! K B!B! PP@A! I BPP@A! H  7x  7P B/7 B| 7 Ak"$ B؃7A#! G )x! )P!  7  7h "B! )!A'! D  B| )X7  )7 B| )87 Ak"$ B؃7A(#! D )XB|! )! )h! )"! )!    |B|BXE@A! B  7X )"PPE@A B |)"PPE@A:! =  78 ))" ))QE@A6! < B"PE@A3! ; B.)!  70  7 B|B7 B|B/7 Ak"$ B؃7A#! 9 B|)"PPE@A! 8 ))! ))! ) 7 ) )0B7 ))!    S! ))" R@A8! 7 ))! ))! B|" XE@A! 6 ) 7 ))!  T@A%! 5 A! 4  7 B| 7 B| B7 Ak"$ B؃7A#! 4 A6! 2 BС7 B|B7 B|B/7 Ak"$ B؃7A#! 2 B|)"PP@A0! 1 A! 0 B7 B|B7 B|B7 Ak"$ B؃7A#! 0 B|)"PPE@A! /  7  )B|7 B| 7 Ak"$ B؃7A(#! . )! )h! )X! )! )!A*! , B| 7 B| 7 Aj"$ Aj"$A B BXE@A! + B!!B!A! ) B B|BX! B B !BB !A! ( B7 B| 7 B|B7 Ak"$ Bǀ؃7A#! ( B|)! B |)! PPE@A! '  7  7h )B|!  7  7 Ak"$ Bɀ؃7A#! & B|)"P@ Ak"$ Bʀ؃7A#! &  )7 B< B))7B) 7  )7 Ak"$ B̀؃7A#! % B|)"P@ Ak"$ B̀؃7A#! %  )h )|7 B))7B) 7 )! )! )h!A! # B|B7 B|B7 Aj"$ Aj"$A B B|BXE@A! " B!A!  7@  7h  7 B| 7 Ak"$ BԀ؃7A#! B|)! )@! )! )(! )p! )h!A!  ! !B!A!  )(!A!!   7 B| 7 Ak"$ B؀؃7A#!   7 B| 7 Ak"$ Bڀ؃7A#!  B7 B|B"7 Ak"$ B܀؃7A#!  B7 B|B,7 Ak"$ Bހ؃7A#!   7 B|B7 Ak"$ B؃7A#!  Bĺ7 B|B7 Ak"$ B؃7A#!  B7 B|B'7 Ak"$ B؃7A#!  B7 B|B!7 Ak"$ B؃7A#!   7H  7h  7  7` Ak"$ B؃7A#! B7 B|B!7 Ak"$ B؃7A#!  )H7 Ak"$ B؃7A#! B7 B|B7 Ak"$ B؃7A#!  )h7 Ak"$ B؃7A#!  B7 B|B7 Ak"$ B؃7A#!   )7 B| )`7 Ak"$ B؃7A#!  Ak"$ B؃7A#!  Ak"$ B؃7A#!  B7 B|B.7 Ak"$ B؃7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B܃7A#! A0k"$  )87 )H )@|!  7( B| 7 Ak"$ B܃7A#! )""P@A! )HB| P@A!  7  B7B )H}  )H|B|!  7 B|  }7 Ak"$ B܃7A#!  )@ )|! )H ) )@||" }PP@A !  )! B| 7 B| )@7 A0j"$ Aj"$A  B7 B|  }7 Ak"$ B܃7A#!  A !  B| 7 B| )(7 A0j"$ Aj"$A B|B7 B|B7 A0j"$ Aj"$A A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! )P@ Ak"$ B7A#!  ))@!B } !B' B~B:|1! B|B9!  B|"BSE@A!   ))0|! ))8!  TE@A!  B|"B?PE@A !   R@A !  B|! )  B BT7@ ) 70 ) )3`B|=` ))h! ))! B|  ~ |7 Aj"$A B|B7 Aj"$A B|B7 Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +  ! #(M@ Ak"$ B7A#! # A0k"$ )8P@ Ak"$ B7A#! # 1@B"BTE@A)! " )8B(| B|!  7( )!  7  7 Ak"$ B7A#! ! ) P@ Ak"$ B7A#! ! B|)! ) )8!  QE@A! ) 3`" R@A !   )87 B| 1@< Ak"$ B7A#!  )()!  7  7 Ak"$ B7A#!  B|)! ) !B! P@ Ak"$ B7A#!  )8 X@A!  )h! 3`B|! )!  =`  ~ |! B! )8" T@A!  B| 7 B| 7 B| < A0j"$ Aj"$A ) !B!A !   =  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   3B7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  BӮ7 B|B7 Ak"$ B7A#!   7  = Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  37 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B17 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A (~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!"""#$$%&'((()*+++,-.///0001223456789:;<=>?@ABCCCCCCCCCDDDDDDEEEFGGHIJKLLLMNOPQRSTTTTUVVVVWXXXXYZ[[[[[[\\\]^_```aabbcdeffffghiiiijkkllmnopqrstuvwxyz{|}~ #(AjM@ Ak"$ B7A#!  Ak"$B.5BQ@A!  )P@A!  B.1E@A!  B.4BPPE@A!  )PPE@A ! ~ )BPE@A ! } B!  )7 B| 7 B|B/7 Ak"$ B7A#! { B|)! B| 7 Aj"$ Aj"$A )BPE@A ! y B!A! w BB )BP!A! v B!A! u B.1E@A! u B.)!#) QE@A! t B.B.)B|7 B.5PPE@A! r #)0"P@ Ak"$ B7A#! r )"PPE@A! q P@ Ak"$ B7A#! q ) )}!  7 BS@A! p #)0"P@ Ak"$ B7A#! p   4B|>#)0"P@ Ak"$ B7A#! p #P@ Ak"$ B7A#! p 4"BPP@A! o )P"#Q@A! n B>#)0"P@ Ak"$ B7A#! n  )! "PPE@A! m )@! PPE@A! k )PPE@A! j ))P!  7p  7H  <-  7x )BXE@A! h E@A! g )BTE@A! f )! )BPE@A! e  B|Bx!  )|"BXE@A! c )"PPE@A! b  |!  7  ) B|7 B> 4!  B|> BBQE@A! a #P@ Ak"$ BĀ7A#! a #1E@A! ` #Bu7 B| 7 Aj"$ Aj"$A )P!  70  7 Ak"$ Bʀ7A#! ] B|)"P@A! \ )0!B! "P@ Ak"$ B̀7A#! [ B7 B7 ) )H)TE@A! Z )H 7 )H )7 B!  7`  7X  <,  70 1-"E@A! W B! )!  7@  78 Ak"$ Bր7A#! U B.5PP@A! T )pB> )p4! )p B|> B"BQE@A! S #P@ Ak"$ Bۀ7A#! S #1E@A! R #Bu7 B.1E@A! P B.4BPP@A! O B.1E@A! N B.)!#) QE@A! M B. )XB.)|7 B')!B SE@A! K BRE@A! J )H)! )X TE@A! I )H  )X}7 )xPPE@A! G )x )x) )X )@}}7 1,"@A! E B| )`7 Aj"$ Aj"$A B7 B|B7 B|B> Ak"$ B7A#! C B|1E@A! B B7 B|B7 B|B> Ak"$ B7A#! A A! ? #)0"P@ Ak"$ B7A#! @   4B|>#P@ Ak"$ B7A#! @ #)0!  7h  7 B| )`7 B| )X7 Ak"$ B7A#! ? )hP@ Ak"$ B7A#! ?  )h4! )h B|> BBQE@A! > #P@ Ak"$ B7A#! > #1E@A! = #Bu7A! ;  )`7 B| )X7 B| )7 Ak"$ B7A#! ; A! 9 )`!  )07 B| 7 B| )X7 B| )87 Ak"$ B7A#! 9 A! 7 B')!  7 B| 7B )  )Q!  7@ B| 7 B| )7 Ak"$ B7A#! 7 )P@ Ak"$ B7A#! 7 ))" )@TE@A! 6 ))! )@ } |B PP! )H  )H)|7 ! )@!A! 3 ))!A! 2 )H)P@A! 2 A! 1  )H7 B|B< Ak"$ B7A#! 1 B|)! B |1! B|)!A! / )BPE@A! /  B|B|!A>! - )BPE@A>! -  B|B~!A>! + )BXE@A! +  )B|B"BTE@A! * B' |1! B"BTE@A! ( B' B|3! B*| < B B*|1"B! BTE@A! '  =.  <+ B(| B|)!  70  7 Ak"$ B7A#! & B|)"P@A! % )0!B! ! 1"E@A! # P@ Ak"$ B7A#! # 1dPP@A! " 3.!A!  7`  <,  7P  7 B| 3.7 Ak"$ B7A#! )`! )P! 1,!A!   )H7 B| 1+< Ak"$ B7A#!  B|)! B |1! B|)!A!   )Bx|B"BTE@A!  B' |1!A!   7 B| )7 B| 1< B| < Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!  B70 B=` )! )h!B!A!  B!A4!  B.)!A0!   7x  7 Ak"$ Bȁ7A#!  )x!A!  #7 )!A!  B!A!  B|B.7 Aj"$ Aj"$A  7 B|B7 Ak"$ B́7A#!   7 B|B7 Ak"$ Bρ7A#!   7 B|B7 Ak"$ Bс7A#!  7 B|B7 Ak"$ BӁ7A#! B7 B|B47 Ak"$ BՁ7A#! B7 B|B7 Ak"$ Bׁ7A#!  B7 B|B7 Ak"$ Bف7A#!  B7 B|B27 Ak"$ Bہ7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!   )()7 B| )(7 B|B< Ak"$ B7A#!  )! B0| 7 A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )0BQ@A!  )(P@ Ak"$ B7A#!   )()" )0BTE@A!  )0 ~!B! @A! B T@A! )0BS@A!  7 B| )(7 B|B< Ak"$ B7A#! B|)! B8| 7 A j"$ Aj"$A P@A!  B  )0T! )0 ~!A !  )(P@ Ak"$ B7A#!   )()7 B| )(7 B|B< Ak"$ B7A#!  B|)! B8| 7 A j"$ Aj"$A B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!   )! "PPE@A! )@! P"PE@A! B')"BQE@A! B!  7  )(7 B| )07 Ak"$ B7A#!  Aj"$ Aj"$A  7  7 Ak"$ B7A#!  B|4! )!A!  B.)!A !  B7 B|B87 Ak"$ B7A#!   A ~|#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$B8 ) SE@A! B8! #)0"P@ Ak"$ B7A#!  5! 5!  > B ! B   BB!  >  |BB|B! B| 9 B|)"B/B!B' B|+! B|"B!TE@A!  B4BBx| BB?B' B|+ D>D:@!D cE@A! D! B(|  D9B.濢#!B|> Aj"$ Aj"$A ) P@A!  ) !A!  B(|B> Aj"$ Aj"$A  7 B|B!7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  A8k"$ B|B7 B|A#! B|B7 B| )@7 B | )H7 B(| )P7 B0| B|7  B|7 Ak"$ B7A#!  B|)! B| 7 A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ [  !!!!"##$%&'()*+ #(M@ Ak"$ B7A#! - Ak"$ )PP@A! , )XPPE@A! + )XB| )XPP@A! * B )XT@A! ) )X! B )PX@A! '  7 #)0"P@ Ak"$ B7A#! '   4B|>#P@ Ak"$ B7A#! ' #)0!  78 PPE@A! & )"PPE@A! % "P@ Ak"$ B7A#! %  B,|!  70  )|B|!B }!  7(  !  7B )P |TE@A?! # B7 B|B/7 Ak"$ B7A#! " )0 B|)7 )0)PPE@A! ! )0)"P@ Ak"$ B7A#! ! B.)!  7 )0)! B.7 B| 7 B| 7 Ak"$ B7A%#! B|1E@A!   )0 ) B| )(7 )0)! )0)! )0 )P |7 )8P@ Ak"$ B7A#!   )84! )8 B|>  |! BBQE@A0!  #P@ Ak"$ B7A#!  #1E@A0!  #Bu7  7@ )0B.Q@A8!  )`B/R@A5!  B| 7 Aj"$ Aj"$A  )`7 B| )P7 Ak"$ B7A#!  B/7 B|B )P}7 Ak"$ B7A#!  )@!A4!   B.7 Ak"$ B7A#!  )@!A3!  )PP@A#!  A!   B.7 Ak"$ Bŀ7A#!  ) ! )8!B.!A!   )P7 B| )`7 Ak"$ Bʀ7A#!  B|)! B| 7 Aj"$ Aj"$A B!A! )0B.QE@A!  B.7 Ak"$ Bр7A#! B7 B|B7 Ak"$ BԀ7A#! B7 B|B#7 Ak"$ Bր7A#!  B7 B|B*7 Ak"$ B؀7A#!  B7 B|B7 Ak"$ Bڀ7A#!   A ~#!@@@@@@@@@@@   Ak"$ B.7 Ak"$ B7A'#! )! )!A!  "P@ Ak"$ B7A#!  )! PPE@A !   XE@A!   B|TE@A!  B |B< Aj"$ Aj"$A B |B< Aj"$ Aj"$A A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!   )8 )()|B|"B )8}" )0|! )() T@A!  )( 7B.)! )()!  |B~|B }!  T@A !  ! B| 7 A j"$ Aj"$A  7  7  )@7 B|  }7 Ak"$ B7A#!  )( )7 )!A !  B|B7 A j"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )1 "BT@A !  #)0"P@ Ak"$ B7A#!   5! 5!  > B " ! B BB !  > Bq|B!  |B B BTB|BPE@A !  ) )3 B|= Aj"$A ) )3 B|= Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ h  !!!"""""#$$%%%%%%%%&&' #(M@ Ak"$ B7A#! ) Ak"$ )XP@ Ak"$ B7A#! ) )X)("P"PE@A! ( )"PPE@A! ' )`3R!  |Bx|"P@ Ak"$ B7A#! ' )`P@ Ak"$ B7A#! '  )P! PE@A! & B.5PP@A! % B7 )X)("P@ Ak"$ B7A#! $ B.5PP@A! # B7  7@  )X7 Ak"$ B7A#! ! )`P@ Ak"$ B7A#! ! )`)@"P@ Ak"$ B7A#! ! )PE@A-!  )X)(PPE@A!  )X)("P@ Ak"$ B7A#!  )PPE@A!  )X)(")"P@ Ak"$ B7A#!  P@ Ak"$ B7A#!  )! )! )! B|!  T@A=!   B|7  B|!B.5PP@A:!   )@7 )h )`3R|Bx|"P@ Ak"$ B7A#!  B.5PP@A7!   )@7 B| )@7 Aj"$ Aj"$A  )@ Ak"$ B7#!A5!   )@ Ak"$ B7#!A-!   7H B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B7A#!  B(|)! B0|)! )H B8|)7B.5PP@A!  )H 7 ! ! )H!A+!  )H  Ak"$ B€7#!A!  B7 Ak"$ Bŀ7A#!  )X)("P@ Ak"$ Bƀ7A#!  B.5P! B|)! P@A!   7A%!   Ak"$ Bʀ7#!A%! B7 Ak"$ B̀7A#! B.5P! B|)! P@A! )X 7(A"! )XB(|  Ak"$ Bπ7#!A"!  B|B Ak"$ BҀ7#!A!  B Ak"$ BՀ7#!A!  B.5P!  |! P@A!   7A!  B|  Ak"$ B݀7#!A!  )`P@ Ak"$ B߀7A#!  )`)@"P@ Ak"$ B‐7A#!    )7 B| 7 B|B< Ak"$ B怐7A#!  B|)!A!  A ~#!@@@@   #(M@ Ak"$ B7A#!  Ak"$ B7 Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  )! 5! 5!  > B ! B BB  !  >   |> B| 7 Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ <  #(M@ Ak"$ B7A#!  A8k"$ )@P@ Ak"$ B7A#!  )@)@"P@ Ak"$ B7A#!   )! )H BTE@A8!   )H~!B! E@A6!  B! )PPPE@A2!  )P! #)0"P@ Ak"$ B7A#!   5! 5!  > B ! B BB  !  > P@ Ak"$ B7A#!    |> B!A!  B|! B SE@A!!  B?B!B B BTBB ~!  T@A!   < BPP@A#!  B| 7 A8j"$ Aj"$A  7P  )@7 B| < B|B7 Ak"$ B7A#!  B |)! B|)!B.5PP@A0!  )P 7 PP@A)!  )P!A"!  70 B7 Ak"$ B7A#! B.5P! B|)! P@A-! )P 7(  )07A'! )PB(|  Ak"$ B7#! B| )0 Ak"$ B7#!A'!  )PB|  Ak"$ B7#!A&!   7( B7 Ak"$ B7A#!  B|)! )(!A!  B T@A !  )H!A !  )HP@A!  B )H T!  )H~!A !  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ^  !!"#$%&'() #(M@ Ak"$ B7A#! + A(k"$ 18B! 18B?B!B ! BT! B !B XE@A! * )0P@ Ak"$ B7A#! * )0)@"P@ Ak"$ B7A#! * 18B||B?B! B B BT|! )!  ~! BTE@A! ) BXE@A! (  B|B"BTE@A! ' B' |1"BTE@A! & B' B|3!  RE@A! $ PPE@A! #  !  7  7 )@PPE@A>! ! )0P@ Ak"$ B7A#! ! )0)@"P@ Ak"$ B7A#! !  )~! )PPE@A!  B|! BTE@A5!   |1! B RE@A!  PE@A!  B|B7 B|B7 A8j"$ Aj"$A  7  )@1P ~!  |B|! )@5TB"BPPE@A%!  P@ Ak"$ B7A#!  )!  7( )@)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  )!  )P7 B| 7 $  Ak"$ B7Av!A #! B|1@A,! 1! ) ! )0!A!  )@1PB )@1Q ) ~|!  )0|B|! )@5TBBPPE@A4! P@ Ak"$ B7A#! )! B| )(7 B| 7 A8j"$ Aj"$A  )@3R|Bx|"P@ Ak"$ B7A#!   )! PPE@A!   70B!A!  !A!  B!A!  B|B7 B|B7 A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!!!!""""""#$$$%%&'''''''''(((((())))))***+++++,---..////01222222222234566677777788889:;;<=>?@AB #(M@ Ak"$ B7A#! D Ak"$ )PPE@A! C )1"B"BPP@A! B )xP@ Ak"$ B7A#! B )x)H"P@ Ak"$ B7A#! B )! )5 !  )7 B| 7 $  Ak"$ B7Av!A #! A B|)!  70 ) )1B< ))PPE@A! @ )x! )!A!! > B|! BTE@A! = P@ Ak"$ B7A#! =  |" 1! B RE@A! <  BXE@A! ; PPE@A! : PE@A ! 9  )PP@A3! 8  )B|! 1 ! B SE@A/! 7 B?B! B B BTBB ~!  TE@A/! 6  7 B| 7 Ak"$ B7A#! 5 )x! )! )0"! )x! )! 1 "B?B!B ! BT!  B B|! )PP@A! 3  )!  3R~! B8"B|  BBT!  <'  |!B!B!B! A! 1  3 ! B T! B BB! B B BTB X@A! 1 PPE@A! 0  7X  7P 5TB"BPP@A! /  5TBBPP@A! .  )07 B| 7 B| )7 Ak"$ B7A#! - )PP@ Ak"$ B7A#! - )P 1'< ) ))B|7 )X! )1"BBP@A! + ) B{< )x5TBBPPE@A! * P@ Ak"$ B€7A#! * )! B| 7 Aj"$ Aj"$A  7H )8"P@ Ak"$ Bǀ7A#! (   )7 B| 7 B|B< Ak"$ Bˀ7A#! ' )XP@ Ak"$ B̀7A#! ' B.5P! B|)! P@A! & )X 7 )x! )H!A:! # )X  Ak"$ BԀ7#!A! "  7H )0"P@ Ak"$ B׀7A#! #   )7 B| 7 B|B< Ak"$ Bۀ7A#! " )HP@ Ak"$ B݀7A#! " B.5P! B|)! P@A! ! )H 7 )! )P! 1'! )X! ! )x!A8!  )H  Ak"$ B怨7#!A!   7 B| 7 B| 7 Ak"$ B耨7A#!  B|)"P@ Ak"$ B逨7A#!   )x1PB! B|" |! )x! )! 1'!A4!   1P! ~! 1Q ~ B|! " |B|! |B|! !A!   7(  7X  7`  7P 1P~!   |B|! 5TB" BPPE@A!  P@ Ak"$ B7A#!  )!  7@ )0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  )!  )7 B| 7 $  Ak"$ B7Av!A #!  B|1@A!  )0! )x! )! )h! 1'! )(! )P! )`! )X! A !   )x5TBBPP@A!   )h )x1Q )(~ )x1PB||B|!A>!   )x)07 B| )@7 B| )7 Ak"$ B7A#!  A!   3R|Bx|" P@ Ak"$ B7A#!   )" P!  PE@A!  !  7hB! A !   78  7 B| 7 B| 7 Ak"$ B7A#!  )0! )x! )! )8!A(! )x)@"P@ Ak"$ B7A#!   )7 B| 7 B|B< Ak"$ B7A#! B.5P! B|)! P@A! ) 7 )0!A !  )B|  Ak"$ B7#!A!  B7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ M  ! #(M@ Ak"$ B7A#! # Ak"$ ) PPE@A! " ) )PE@A! ! Aj"$ Aj"$A )(P@ Ak"$ B7A#! B.5PP@A!  )( )7 )( ) 7 )( ) 1 <JB.5P! ) )! P@A!  )( 7 )P@ Ak"$ B7A#!  ))@"P@ Ak"$ B7A#!  )PE@A!   ) )(PPE@A!  ) )("P@ Ak"$ B7A#!  )PPE@A:!  ) )("P@ Ak"$ B7A#!  B.5P! )! P@A8!  )( 70 ) )("P@ Ak"$ B7A#!  B.5P! )! P@A6!  )( 78 #)0"P@ Ak"$ B7A#!   5! 5!  > B !  ! B!  B" B!  >  |B!B ) 1 TE@A,!  #)0"P@ Ak"$ B7A#!   5! 5!  > B !   BB B!  >  |BB |! ) 1 "B?B!B !  B BTB|! )( 7@ ) 1 ! )(  B BTB<H )( )()@7P ) 1BBBR@A4!   )(7 Ak"$ B7A#!  Aj"$ Aj"$A  ) B|7 B|B< Ak"$ B7A#!  A2! )(B8|  Ak"$ B7#!A! )(B0|  Ak"$ B7#!A! B7 Ak"$ B7A#! ) )("P@ Ak"$ B7A#! B.5P! B|)! P@A!  7A!    Ak"$ B7#!A!  B7 Ak"$ BÀ7A#!  B.5P! B|)! P@A!  ) 7(A!  ) B(|  Ak"$ Bƀ7#!A!  )(B |  Ak"$ Bɀ7#!A !  )(B| ) Ak"$ Bˀ7#! )(B| ) Ak"$ B̀7#!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"##$$%%&'((((()***++++++,,,---...../00000000001122334555567777777788888888888999:::::;<<==>>???????@ABCCCDEF #(M@ Ak"$ B7A#! H Ak"$ )xP@ Ak"$ B7A#! H )x)"P@ Ak"$ B7A#! H 1"B"BP"P@A! G  7P )x)!  7@ )x)P! )x)(! )x1K! )x)X! )x!A! E B|! BBTE@A! D P@ Ak"$ B7A#! D  1H|B!   B|" 1" BX@A! C BQ@A! B P@ Ak"$ B7A#! B  B! 1P! ~!  ! |B|! 5T"B"BPPE@A! A P@ Ak"$ B7A#! A )!  </  7H B 1Q ~|" |B|!  7X BRE@A$! ?  1BBPP@A$! >  7` BBPPE@A! = B! @A! ; 1JB|B!  B BT 1BBR@A! : 1" BRE@A! 9 BRE@A! 8 B! @A8! 6  7 B| 7 B| 7 Ak"$ B7A#! 5 B|)! B |)! PP@A-! 4 )P! )@! )8! )h! 1/! )0! )x!A! 2 B.5PP@A5! 2 )x 7 )x 7 )x )87P )x)( )hRE@A2! 0 B.5PP@A3! / )x )h7( )x 1/B|<K )x )07X Aj"$ Aj"$A )xB(| )h Ak"$ B7#!A2! + )x  Ak"$ B7#! )xB|  Ak"$ B7#!A/! * B.5PP@A! *  7  5TBBPPE@A?! ( P@ Ak"$ B7A#! ( )! B.5PP@A! &  7A/! $ B| Ak"$ B€7#!A/! #  Ak"$ BĀ7#!A:! "  5TBBPPE@A! " B! P! A'!  )0"P@ Ak"$ Bʀ7A#! )"P@ Ak"$ B̀7A#! )!  7 B| 7 $  Ak"$ B΀7Av!A #!  B|1! )P! )@! )8! )h! 1/! )0! )x! )X! )H! A!  )H"P@ Ak"$ BЀ7A#!  )! 5 !  7 B| 7 $  Ak"$ BҀ7Av!A #!  B|)! )x1JB?B! B B BTB|!  )0RE@A!  )P! )@! )8! )h! 1/! )0! )x!A!  )P! )@! )8! )h! 1/! )0! )x! )X! )`! )H! A$!  )0"P@ Ak"$ B߀7A#!  )"P@ Ak"$ Bူ7A#!  )!  7 B| 7 $  Ak"$ B〰7Av!A #!  B|1! )P! )@! )8! )h! 1/! )0! )x! )X! )`! )H! A"!   3R|Bx|" P@ Ak"$ B瀰7A#!  P@ Ak"$ B逰7A#!   )!B! PPE@A!   78  70  7hA!   )@QE@A!  1I@A!   )"PPE@A!  1J 1 QE@A!  )"P@ Ak"$ B7A#!   1 ! 1BBPPE@A!  P@ Ak"$ B7A#!  B?B! B B BTB|! 3R!  ~!  |"1B~|BBTE@A!   )  ~|!B! B|! 1JB?B! B B BTQE@A! B<IB! !B!A! !A! B|!A!  P@ Ak"$ B7A#!  )  3R~|!B!A!  B.5PP@A!  B7 B7 Aj"$ Aj"$A B Ak"$ B7#! B|B Ak"$ B7#!A!  B7 B|B&7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3  #(M@ Ak"$ B7A#! Ak"$ )PP@ Ak"$ B7A#!  )P)B|! )P1 !B SE@A0!  B?B!B B BTBB ~!  TE@A0!  B!  </ )P)!  70 )P1 !  )H7 B|  |< B|B7 Ak"$ B7A#!  B |)! B|)! )P1! )P )P1 1/|< B|! )P B  BBPP<B.5PP@A-!  )P )07 )P 7 )PB7 )PB= )P)("PPE@A!  )"PPE@A!  )PP@A1!  B.5PP@A+!   7 )P)("P@ Ak"$ B7A#!  B.5PP@A)!  B7 PPE@A!  )P)(PPE@A#!  )P)("P@ Ak"$ B7A#!  B.5PP@A !   7 Aj"$ Aj"$A B|  Ak"$ B7#!A!  78 B7 Ak"$ B7A#! B.5P! B|)! P@A'! )P 7( )8!A!  )PB(|  Ak"$ B7#!A&!  B Ak"$ B7#!A!  B|  Ak"$ B7#!A!  )PB| )0 Ak"$ B7#! )PB|  Ak"$ B7#!A!  )P )P1B<B!A !  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )(P@ Ak"$ B7A#!   )(1 ! )(1!  ) 7 B| )(7 B|! B!   BPPB?B! B| )0B B BTB|7 Ak"$ B7A#!   )()PP@A!  Aj"$ Aj"$A )() !  ) 7 B| )(7 B| 7 Ak"$ B7A#!  A!  A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !""""########$$%%%%%&'())))))))**++++++,---.//00122234444445566789: #(A0jM@ Ak"$ B7A#! < Ak"$ )P@ Ak"$ B7A#! < )P@ Ak"$ B7A#! <  ))! )3R )~!  |! )1 ! B|! )1"B!   BPPB?B!B B BT! 1B~|BBT@A! ; B|A#! B| )) )3R )~|7 B|)B|! B| 7 B|  )1PB|7 )1BBPPE@A! :  7( )! )!A! 8 B|! 1P!  1Q!  |!  |! BSE@A! 7  |" 1!  BXE@A&! 6 B<A! 4 BT@A! 4  5TB" BPPE@A! 3 )!  70  7H  7P  7@ 1BBPPE@A! 1 B! B|< B" BTE@A! / B| B|!  7X )BQ@A! . )" P@ Ak"$ B7A#! . )B| < 5TBBPPE@A! - )" P@ Ak"$ B7A#! - B.5PP@A! , 7  5TBBPPE@A! * )" P@ Ak"$ B7A#! * B.5P! )! P@A! ) 7 )B|7 ) 1P|7 ) 1Q|7A! & Ak"$ BÀ7#!A?! % )8! )!  7 B| 7 B| 7 Ak"$ Bŀ7A#! % )h! )(! )! )! )H! )@! )0! )X! A?! # Ak"$ Bˀ7#!A9! " )0! )!  7 B| 7 B| 7 Ak"$ B̀7A#! " )h! )(! )! )! )H! )@! )0! )X! A9!  <' )!  7 B| 7 B| 7 Ak"$ BԀ7A#! B|)! )X 7 )XB7 B|! )X 7 )X  )1PB|7 )h! )(! )! )! )H! )@! )0! )X! 1'! )P! A1!   7`  <& )H"P@ Ak"$ B߀7A#!  )! 5 !  7 B| 7 $  Ak"$ Bြ7Av!A #!  B|)! )1BBPPE@A!   )5TBBPPE@A!  B! E@A!  1&B! B8"B|  BBT!  )h! )! )! )H! )@! )0! )`! )P! ! )(!A/!   )(PP! 1&! A!   78 ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  )!  )P7 B| )P7 $  Ak"$ B7Av!A #!  B|1P! )8!A!  B!A!  ! A,!   3R|Bx|"P@ Ak"$ B7A#!   )! PPE@A!   7h 1PB! B|" |!B!A"!  1BBPE@A!  )@"P@ Ak"$ B7A#!  )PP@A!  ) ) Q@A! Aj"$ Aj"$A  7 B| 7 B| 7 Ak"$ B7A#! A!  )! 3R! ) ~!   |B|7 B| Bx|7 Ak"$ B7A#! )(! )! )!A!   B| ))  )| )3R~|7 B|)B|! B| 7 B|  )1PB|7A!  )! )!A!   7 B|B7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )) ! ) B|7 B|! )  ) T! )! )!A!   B|7 ) " RE@A ! P@ Ak"$ B7A#!  )!  3R~!  |1B~|BBT@A!  )QE@A! B.5PP@A! B7 )("PPE@A!  B.5PP@A!  B7  1Bw< Aj"$A B|B Ak"$ B7#!A!  B|B Ak"$ B7#!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@ C  #(M@ Ak"$ BĄ7A#!  Ak"$ )(PPE@A!  )()P@A!  )(1B"BPP@A!  )(1 PE@A&!  )()!A!  B|! B|! BTE@A!  5 B0|5QE@A!    |1BX@A!  ) P@ Ak"$ BĄ7A#!   ) 1Q ~ |B(|! B8| 7 Aj"$ Aj"$A  ) 3R|Bx|"P@ Ak"$ BĄ7A#!  ) P@ Ak"$ BĄ7A#!   )! PPE@A%!   B|!B!A ! B8|B.7 Aj"$ Aj"$A ) P@ Ak"$ BĄ7A#! ) )H"P@ Ak"$ BĄ7A#! )! )(5 !  B0|7 B| 7 $  Ak"$ BĄ7Av!A #! B|)! )(1 ! )()! B?B!B B BTB|" ! ) 3R!  ~! )()"P!  |! PE@A;!  )(1BBPPE@A?!    ~!  |"1B~|BBTE@A=!  !A!  !A;!  B!A6!  B8|B.7 Aj"$ Aj"$A B7 B|B!7 Ak"$ B€Ą7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@ C  #(M@ Ak"$ BȄ7A#!  Ak"$ )(PPE@A!  )()P@A!  )(1B"BPP@A!  )(1 PE@A&!  )()!A!  B|! B|! BTE@A!  5 B0|5QE@A!    |1BX@A!  ) P@ Ak"$ BȄ7A#!   ) 1Q ~ |B(|! B8| 7 B|B< Aj"$ Aj"$A  ) 3R|Bx|"P@ Ak"$ BȄ7A#!  ) P@ Ak"$ BȄ7A#!   )! PPE@A%!   B|!B!A ! B8|B.7 B|B< Aj"$ Aj"$A ) P@ Ak"$ BȄ7A#! ) )H"P@ Ak"$ BȄ7A#! )! )(5 !  B0|7 B| 7 $  Ak"$ BȄ7Av!A #! B|)! )(1 ! )()! B?B!B B BTB|" ! ) 3R!  ~! )()"P!  |! PE@A;!  )(1BBPPE@A?!    ~!  |"1B~|BBTE@A=!  !A!  !A;!  B!A6!  B8|B.7 B|B< Aj"$ Aj"$A B7 B|B!7 Ak"$ B€Ȅ7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ n  !""#$%&'() #(M@ Ak"$ B̄7A#! + A0k"$ )@PPE@A! * )@1"B"BPP@A! ) )8P@ Ak"$ B̄7A#! ) )8)H"P@ Ak"$ B̄7A#! ) )! )@5 !  B|7 B| 7 $  Ak"$ B̄7Av!A #! ( B|)!  7 )@ )@1B< )@)PPE@A! ' )8! )@!A! % B|! BTE@A! $ P@ Ak"$ B̄7A#! $   |1" BXE@A! # PP!   !  ! PE@A ! "  )PP@A.! !  )B|! 1 ! B SE@A*! B?B! B B BTBB ~!  TE@A*!   7 B| 7 Ak"$ B̄7A#!  )8! )@! ) "! )8! )@! 1 "B?B!B ! BT! B B| ! )PP@A!   )! 3R!   ~|!B!B!A!   3 !B T! B BB! B B BTB X@A!  PPE@A?!  P@ Ak"$ B̄7A#!   B|! B8!  B|  BBT<  B|B|"P@ Ak"$ B̄7A#!    B|5>  )B|7  1Q ~! 1"BB!  |B(|! P@A!   B{< B| 7 A0j"$ Aj"$A  7 B| 7 B| 7 Ak"$ B̄7A#!  B|)! ) ! )8! )@!B!A/!  B |B|" P@ Ak"$ Bƀ̄7A#!  5!  B|5R@A !  ! !A:!   |Bx|" P@ Ak"$ B̀̄7A#!   )" P!  PE@A!  ! B! A !   7(  7 B| 7 B| 7 Ak"$ B׀̄7A#!  ) ! )8! )@! )(!A&! )8)@"P@ Ak"$ B܀̄7A#!   )7 B| 7 B|B< Ak"$ B̄7A#! B.5P! B|)! P@A! )@ 7 ) !A !  )@B|  Ak"$ B̄7#!A!  B7 B|B7 Ak"$ B̄7A#!  B7 B|B7 Ak"$ B̄7A#!  B7 B|B 7 Ak"$ B̄7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ BЄ7A#!  Ak"$ )(P@ Ak"$ BЄ7A#!   )(1 ! )(1!  ) 7 B| )(7 B|! B!   BPPB?B! B| )0B B BTB|7 Ak"$ BЄ7A#!   )()PP@A!  Aj"$ Aj"$A )() !  ) 7 B| )(7 B| 7 Ak"$ BЄ7A#!  A!  A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ f  !"# #(A jM@ Ak"$ BԄ7A#! % Ak"$ )P@ Ak"$ BԄ7A#! % )P@ Ak"$ BԄ7A#! %  ))! )3R )~!  |! )1 ! B|! )1"B!   BPPB?B!B B BT! 1B~|BBT@A! $ B|A#! B| )) )3R )~|7 B|)! B| B|7 B| B(|7 )1BBPPE@A! #  7( )!A! ! B|! 1Q! B|!  |! BSE@A=!  |"1!  BXE@A$!  B<A!  BT@A!   70  <'  78  7@ )1BBPPE@A9!  B!  B|< B"BTE@A!  B| B|!  7H )BQ@A5!  )"P@ Ak"$ BԄ7A#!   )B| < )"P@ Ak"$ BԄ7A#!   5> )8! )!  7 B| 7 B| 7 Ak"$ BԄ7A#!  )H )H)B|7 )H )H)B|7 )H )H) )1Q|7 )X! )(! )! )@! )8! )0!A!  )!  )7 B| 7 B| 7 Ak"$ BԄ7A#!  B|)! )H 7 )HB7 )H B|7 )H B(|7 )! )@! )8! )H! 1'! A*!   7P )H"P@ Ak"$ BԄ7A#!  )! )5 !  7 B| 7 $  Ak"$ BԄ7Av!A #!  B|) )(PP! )X! )(! )! )@! )8! )0! )P! 1'! A(!   3R|Bx|"P@ Ak"$ BԄ7A#!   )! PPE@A!   7X B|! B(|!B!A !  )1BBPE@A!  )@"P@ Ak"$ BπԄ7A#!  )PP@A!  ) )) Q@A! Aj"$ Aj"$A  )7 B| 7 B| 7 Ak"$ BրԄ7A#! A!  ))! 3R! ) ~!   |B|7 B| Bx|7 Ak"$ BڀԄ7A#! )(! )!A!   B| ))  )| )3R~|7 B|)! B| B|7 B| B(|7A!  )!A!   7 B|B7 Ak"$ BԄ7A#!  B7 B|B 7 Ak"$ BԄ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@ C  #(M@ Ak"$ B؄7A#!  Ak"$ )(PPE@A!  )()P@A!  )(1B"BPP@A!  )(1 PE@A&!  )()!A!  B|! B|! BTE@A!  ) B0|)QE@A!    |1BX@A!  ) P@ Ak"$ B؄7A#!   ) 1Q ~ |B|! B8| 7 Aj"$ Aj"$A  ) 3R|Bx|"P@ Ak"$ B؄7A#!  ) P@ Ak"$ B؄7A#!   )! PPE@A%!   B|!B!A ! B8|B.7 Aj"$ Aj"$A ) P@ Ak"$ B؄7A#! ) )H"P@ Ak"$ B؄7A#! )! )(5 !  B0|7 B| 7 $  Ak"$ B؄7Av!A #! B|)! )(1 ! )()! B?B!B B BTB|" ! ) 3R!  ~! )()"P!  |! PE@A;!  )(1BBPPE@A?!    ~!  |"1B~|BBTE@A=!  !A!  !A;!  B!A6!  B8|B.7 Aj"$ Aj"$A B7 B|B!7 Ak"$ B€؄7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ r  !!!!!!""""#$%%&'()*+, #(M@ Ak"$ B܄7A#! . A0k"$ )@PPE@A! - )@1"B"BPP@A! , )8P@ Ak"$ B܄7A#! , )8)H"P@ Ak"$ B܄7A#! , )! )@5 !  B|7 B| 7 $  Ak"$ B܄7Av!A #! + B|)!  7 )@ )@1B< )@)PPE@A! * )8! )@!A! ( B|! BTE@A! ' P@ Ak"$ B܄7A#! '   |1" BXE@A! & PP!   !  ! PE@A ! %  )PP@A.! $  )B|! 1 ! B SE@A*! # B?B! B B BTBB ~!  TE@A*! "  7 B| 7 Ak"$ B܄7A#! ! )8! )@! ) "! )8! )@! 1 "B?B!B ! BT! B B| ! )PP@A!   )! 3R!   ~|!B!B!A!   3 !B T! B BB! B B BTB X@A!  PPE@A!  P@ Ak"$ B܄7A#!   B|! B8!  B|  BBT<  B|B|"P@ Ak"$ B܄7A#!  B.5P! B|)! P@A!   7  )B|7  1Q ~! 1"BB!  |B|! P@A!   B{< B| 7 A0j"$ Aj"$A   Ak"$ B€܄7#!A;!   7 B| 7 B| 7 Ak"$ BĀ܄7A#!  B|)! ) ! )8! )@!B!A/!  B |B|" P@ Ak"$ Bʀ܄7A#!  )!  B|)R@A !  ! !A B ! B BB  !  > )P  |> )P1"BBP@A!  )P B{< Aj"$ Aj"$A B|! ! ! !A!  )0 )B||1PP@A!  A7!  )!  7 B| 7 Ak"$ B߀7A#!  A+!  B Ak"$ B7#!A!!   |Bx|"P@ Ak"$ B7A#!   )! PPE@A!   B|!B!A!  7  )H7 B| )P7 B| 7 Ak"$ B7A#! ) !A! Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )(P@ Ak"$ B7A#!   )(1 ! )(1!  ) 7 B| )(7 B|! B!   BPPB?B! B| )0B B BTB|7 Ak"$ B7A#!   )()PP@A!  Aj"$ Aj"$A )() !  ) 7 B| )(7 B| 7 Ak"$ B7A#!  A!  A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ q  !!!"####$$%%&'() #(A jM@ Ak"$ B7A#! + Ak"$ )P@ Ak"$ B7A#! + )P@ Ak"$ B7A#! +  ))! )3R )~!  |! )1 ! B|! )1"B!   BPPB?B!B B BT! 1B~|BBT@A! * B|A#! B| )) )3R )~|7 B|)! B| B|7 B| B|7 )1BBPPE@A! )  7( )!A! ' B|! 1Q! B|!  |! BSE@A! &  |"1!  BXE@A$! % B<A! # BT@A! #  70  <'  7@  78 )1BBPPE@A! " B!  B|< B"BTE@A! B| B|!  7H )BQ@A!  )" P@ Ak"$ B7A#!  )B| < )0" P@ Ak"$ B7A#!  )PPE@A=!  B.1E@A=!  )" P@ Ak"$ B7A#!  B.5P! )! P@A;!  7 )8! )!  7 B| 7 B| 7 Ak"$ B7A#!  )H )H)B|7 )H )H)B|7 )H )H) )1Q|7 )X! )(! )! )8! )@! )0!A!  Ak"$ B7#!A4!  )" P@ Ak"$ B7A#!  )7A4!  )!  )7 B| 7 B| 7 Ak"$ B7A#!  B|)! )H 7 )HB7 )H B|7 )H B|7 )X! )(! )! )8! )@! )0! )H! 1'! A*!   7P )H"P@ Ak"$ Bŀ7A#!  )! )5 !  7 B| 7 $  Ak"$ Bǀ7Av!A #!  B|) )(PP! )X! )(! )! )8! )@! )0! )P! 1'! A(!   3R|Bx|"P@ Ak"$ Bˀ7A#!   )! PPE@A!   7X B|! B|!B!A !  )1BBPE@A!  )@"P@ Ak"$ Bڀ7A#!  )PP@A!  ) )) Q@A! Aj"$ Aj"$A  )7 B| 7 B| 7 Ak"$ B7A#! A!  ))! 3R! ) ~!   |B|7 B| Bx|7 Ak"$ B7A#! )(! )!A!   B| ))  )| )3R~|7 B|)! B| B|7 B| B|7A!  )!A!   7 B|B7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@  A(k"$ )8 )@Q@A! B.1E@A! )0P@ Ak"$ B7A#! )0)"PP@A! )0P@ Ak"$ B7A#! )0)!  )87 B| )@7 B| 7 Ak"$ B7A#! B.1@A !  A(j"$ Aj"$A )0)!  )07 B| )87 B| )@7 B|B7 B | 7 Ak"$ B7A#!  A !  )8! )@!  7 B| 7 B| 7 Ak"$ B7A#!  A!  A(j"$ Aj"$A A ~#!@@@@@@@@@@@   Ak"$B.1E@A! ) PPE@A!  ) )PPE@A!  B )8X@A!   )(7 B| )07 B| )87 Ak"$ B7A#!  Aj"$ Aj"$A )(! )0!  7 B| 7 B| )87 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@  A0k"$ )X )H )X )HS"P@A!  7(B.1@A ! )@ )PQ@A ! )8P@ Ak"$ B7A#! )8)!  ~!B.1@A !  )@7 B| )P7 B| 7 Ak"$ B7A#! B| )(7 A0j"$ Aj"$A  7 )8)! )@! )P!  7 B| 7 B|  } |7 Ak"$ B7A#!  ) !A!  B| 7 A0j"$ Aj"$A  )87 B| )@7 B| )P7 B| 7 Ak"$ B7A#!  )(!A!  B|B7 A0j"$ Aj"$A A ~#!@@@@@@@@@   Ak"$B.1E@A!  ) P@ Ak"$ B7A#!  ) )"PP@A !  ) P@ Ak"$ B7A#!  ) )!  )(7 B| 7 Ak"$ B7A#!  Aj"$ Aj"$A  )(7 B|B7 B| 7 Ak"$ B7A#!  A!  A ~#!@@@@@  Ak"$ ) !  7 B|B7 B| )(7 Ak"$ B7A#!   ) 7 B| )(7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))H!  )|"1B8 1B0 1B( 1B 1B 1B 1 1BB! ) 7@ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ )0P@ Ak"$ B7A#!  )0)0! )0)8!  Q@A!   T@A!   7 )0)@! B }"B~!B' B:|1! B|"B9!  B|! )0!A!   7  7 B| B7 Ak"$ B7A#!  )0)@! B }!B' B~B:|1 B|B9B|! )! )0! )! ! )0! )! BQE@A!  B|B@!  XE@A !  70 B8| 7 A(j"$ Aj"$A  |!  X@A! )@! B|!   B BT7@ B|"B?PE@A!  R@A!  70 B8| 7 A(j"$ Aj"$A  7  7  7 B| B7 Ak"$ B7A#!  ) ! )! )0!A!   70 B8| 7 A(j"$ Aj"$A B8| 7 A(j"$ Aj"$A BŰ7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ /  !"""#$ #(M@ Ak"$ B7A#! & A8k"$ Ak"$ B7A#! % Ak"$ B7A#! $ B7 B|B7 Ak"$ B7A#! #  )H7 Ak"$ B7A#! " Ak"$ B7A#! ! )@P@ Ak"$ B7A#! !   )@B|7 Ak"$ B7A #! 1!  <' BRE@A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!   )@)p!  70 )@)!  7( Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )(7 Ak"$ B7A#!  B̎7 B|B 7 Ak"$ B7A#!   )07 Ak"$ B7A#!  B؎7 B|B 7 Ak"$ B7A#!   1'7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )PPP@A"!  A+!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A! Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )P7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )X7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 B| )P7 B| )X7 Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  B< B7 B|B>7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@ '  A(k"$ )0B!BBT! B !B XE@A !  B! PPE@A!   7   B|7 Ak"$ B7A #!  B|1"BRE@A!  BQ@A!  B.4BPP@A!  B|B7 B| ) 7 B|B7 A(j"$ Aj"$A  ) 7 B| )07 B| )87 B| )@7 Ak"$ B7A#!  A!  B|B7 B| ) 7 B|B7 A(j"$ Aj"$A  ) )! )0 T@A! ) )p )0X@A! ) 3^"PPE@A!  )0 }" |! ) 1e!  B BT! B| 7 B| ) 7 B| 7 A(j"$ Aj"$A )0 }! ) )h" XE@A!  ) 1e!  B BT ) 3\~! ) 1f!  B BT" ~ |!A!  B!A!  B|B7 B| 7 B|B7 A(j"$ Aj"$A BȊ))"P@ Ak"$ B7A#!   B|)"PPE@A&!  B| )0B BB|)!A!  B!A!  A ~#!@@@@@@@@@@@   Ak"$BȊ))"PPE@A! 5$B|"B"BTE@A!   B|)"PPE@A!  B0| 7 B8|B> B<| > B| B|7 Aj"$ Aj"$A B0|B7 B0|B> B0|B> B0|B7 Aj"$ Aj"$A B0|B7 B0|B> B0|B> B0|B7 Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@   Ak"$ 5 B )0|! )! BBBBT" |! B! )(!  X@A!  }B|! 5$ B|B|!BȊ))"PPE@A! B"BTE@A !  B|)"PPE@A!   B|! B|! B8| 7 B8| > B8| > B8| 7 Aj"$ Aj"$A B!B!A!  B8| ) |7 B8| > B8| 5$> B8| )(7 Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  A8k"$ )P! )@!  )@7 B| 5H> B | 5L> B| )P7  }B|BBBBT" )X  )XT!  7X B| 7 Ak"$ B7A#!  B |)! B(|5! B,|5! B0|)! B| 7 B| > B| > B| 7 B| )X7 A8j"$ Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"""""""""""""#$$%%%&'())))))))*********+++,-..../0000122223344567777777788888888888999:;<<<<=>>>>?@@@@ABCCCCDDEFGGHIIJK Ak"$ ) ) )BPP@A! L B.1E@A! K  )B!B XE@A! J B! PP@A.! H  B'7 Ak"$ B7A#! G B|)"PPE@A-! F )! )! B SE@A! D )!B!A! B B|! )"P@ Ak"$ B7A#! B )" XE@A! A  )T@A+! @ B|" S@A! ?  B'7 Ak"$ B7A#! > B|)"PPE@A*! = )! )! B SE@A'! ; )!B!A"! 9 B|! )"P@ Ak"$ B7A#! 9 )" XE@A&! 8  )T@A(! 7 B|" S@A!! 6 Aj"$ Aj"$A )!  7 B| )7 B| )7 B|  }7 B | 7 Ak"$ B7A#! 4 Aj"$ Aj"$A B!B!A! 1 )!  7 B| )7 B| )7 B|  }7 B | 7 Ak"$ B7A#! 1 Aj"$ Aj"$A B!B!A! .  7X  7@  B|7 Ak"$ B7A #! . B|1BR@A! -  ) )@)T@A! , )@)p )X@A! + #)0"P@ Ak"$ B7A#! + )"P@ Ak"$ B7A#! + BȊ))"P@ Ak"$ B7A#! +  )XBTE@A! *  )XB|)"PPE@A! )  )BB|! )BB! B|! )X!  7 )PE@A! ' )! )!B! A! % B|! TE@A! $  7P P@ Ak"$ BЀ7A#! $  1! BB! B B BTBBPPE@A! # |" P@ Ak"$ B׀7A#! # )-" P@ Ak"$ Bڀ7A#! #  )7 B7 )-B|!  7- )- RE@A! "  BBTE@A! ! B|!A!   RE@A!   B|!B!A!   7 B| > B | > B| 7 Ak"$ B逜7A#!  B$|5! B |5! B(|)! B|)! )! )! )! )P! A!   ><  >4  7x  7` B7 B|B7 Ak"$ B7A#!  )! )`! )x! 54! 5 B | > B| 7 Ak"$ B7A#!  B$|5! B |5! B(|)! B|)! )! )! )! )! )H! A!  >8  >0  7p  7h B7 B|B7 Ak"$ B7A#! )! )h! )p! 50! 58! )! )! )! )H! A! B!B!B!B!A! Aj"$ Aj"$A BȊ))"P@ Ak"$ B7A#!  B|)"PPE@A! B| )B BB|)!A !  B!A !  Aj"$ Aj"$A  )X7 B|B7 Ak"$ B7A#!  B7 B|B(7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@ I  Ak"$ )p )h )`BPP@A!  B.1E@A!  #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  BȊ))"P@ Ak"$ B7A#!   )`B"BTE@A!   B|)"PPE@A!   )`BB|! )`BB! B|!  7P )p! )h!B! A!  B|! TE@A!   78 P@ Ak"$ B7A#!   1! BB! B B BTBBPPE@A+!  |" P@ Ak"$ B7A#!  )-" P@ Ak"$ B7A#!   )! B7 7 )-B|!  7- )-RE@A9!   BBTE@A/!  B|!A!   RE@A4!   B|!B!A!  7 B| > B | > B| 7 Ak"$ B7A#! B$|5! B |5! B(|)! B|)! )P! )p! )h! )8! A!  7@  >4  >0  7H B7 B|B7 Ak"$ B7A#! )P! )@! 54! )H! 50! )p! )h! )8! A+!  Aj"$ Aj"$A B!B!B!B!A!  Aj"$ Aj"$A  7 B|B7 Ak"$ Bŀ7A#!  B7 B|B(7 Ak"$ BȀ7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@ 7  A0k"$#)0"P@ Ak"$ B7A#!  )!  7( P@ Ak"$ B7A#!  )PB!BBT! B ! )PBBBBT! B!B B BT! )X |! )H! )8! )@!B!A !  B|!  TE@A6!  BPE@A!   1! B|! PE@A!  B8|!A ! B! P@ Ak"$ B7A#! 1 BPPE@A"!  7  7  <  |! PE@A'! P@ Ak"$ B7A#! )-" P@ Ak"$ B7A#!  )7 B7 )-B|!  7- )- RE@A#! B!A !  B7 B|B7 Ak"$ B7A#!  )(! 1! ) ! )H! )8! )@! )!A"!  P@ Ak"$ B7A#!   |" P@ Ak"$ B7A#!  )-" P@ Ak"$ B7A#!   )! )! 7 7 )-B|!  7- )- R@A"!  B7 B|B7 Ak"$ B7A#!  )(! 1! ) ! )H! )8! )@! )!A"!  A0j"$ Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ?  !"#$%& Ak"$ )XPPE@A=! ' )p )X)R@A1! & )X1BBPP@A)! % B.1E@A(! $ #)0"P@ Ak"$ B7A#! $ )!  7H P@ Ak"$ B7A#! $ )X) ! )X! )h! )`!B!B!A! " B|!  )TE@A'! ! B?PE@A&! P@ Ak"$ B7A#! 1! B|! BBPPE@A !   |" P@ Ak"$ B7A#!   |" P@ Ak"$ B7A#!  )-" P@ Ak"$ B7A#!   )! )! 7 7 )-B|!  7- )-R@A !   7  >  78 B7 B|B7 Ak"$ B7A#!  )H! )8! )X! )h! )`! ) ! 5!A !  BB!A!  Aj"$ Aj"$A Aj"$ Aj"$A  )X7 Ak"$ B7A#!  B|)!  70 B|)!  7@ Ak"$ B7A#!  B7 B|B(7 Ak"$ B7A#!   )@7 B| )07 Ak"$ B7A#!  Bʗ7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B$7 Ak"$ B7A#!   )X7 Ak"$ B7A#!  B|)!  70 B|)!  7@ )X)!  7( Ak"$ B7A#!  B7 B|B(7 Ak"$ B7A#!  )@7 B| )07 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  )(7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )p7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B$7 Ak"$ B7A#!  B7 B|B)7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  )) B B"BPP@A!  5BPP@A!  ))hBQ!  <G )x! 5! 5! )!A!  P@ Ak"$ B7A#!  B< B|!  B|! T@A!  )P }! ! ! ! ! ! ! PPE@A!  7P  7x  7 B| > B | > B| 7 B| 7 Ak"$ B7A#! B8|)! B,|5! B(|5! B0|)! B |)! B! 1G"E@A! )x! B! A !   7X  >L  7h  >H  7`  )x7 B| 7 Ak"$ B7A#!  )X! 5L! 5H! )h! )`! 1G!A !  Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A 0~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!""#$%%%&''''''())*********+,,,,,-.////012222222234444556678899:;;;<<====>????@@AAAABBBCCCCDEEEEEFFGGGHHIIJKKKKKLLMMNNNOOPQRRRRRRSTUVWXYYYYYYZ[\]^____`aaaaaabbbcccdeeefggghiiiiiiijjjjkkllllmnnopppppqqqrrrrssttuvvvwwwwwxyz{|}~ #(A jM@ Ak"$ B7A#!  Ak"$ )BQ@A!  BȊ))"P@ Ak"$ B7A#!   )B"BTE@A!  B!  |)"PPE@A!   )BB|! )BB! B|! ! )P@ Ak"$ B7A#!  )) ! )BQ@A!  )BQ@A!   ) )|B|! B BR" E@A!  )!B! )1" B" BPP@A!  ))" )TE@A!  ))B"B9XE@A!  ! B! B! A!  P@ Ak"$ B7A#!  B|! 1 B BT !  B|! ! T@A! ~ B" |B9XE@A&! } !A#! { B BT !  |! BX@A"! z B"PPE@A! y B9 B~! B B BTB|! B!B! ! PPE@A! v 1! B|!B! ) QE@A! t ))B! P@A! r B"PE@A! q BB! BXE@A! p B!  TE@A6! n B  }}!B B BTB|"B  ! )B!  XE@A! l P@ Ak"$ B7A#! l  < B|! B|!A! j B< B|! B|!  X@A=! i B! B| QE@A! g P@ Ak"$ Bŀ7A#! g   1BL< E@A! e BȊ))"P@ Ak"$ Bʀ7A#! e   |)"PPE@A! d  )BB|! )BB! B|! )B! )! B"BQE@A! b P@ Ak"$ BҀ7A#! b P@ Ak"$ BԀ7A#! b  1 1B3< B|"BBTE@A! a B|! B~|! B|!A! ^  7  7`  7 B| > B | > B| 7 B| BB7 Ak"$ Bူ7A#! ^ B8|)!  7P B,|5!  >L B(|5!  >H B0|)!  7 B |)!  7  )7 B| )x7 B!  7X B| 7 Ak"$ B‰7A#! ] )` )P}! )x )X|! )! 5H! )! 5L!  7xB X@A! [ BQE@A! Z P@ Ak"$ B逰7A#! Z P@ Ak"$ B뀰7A#! Z  1 1BL< B"BTE@A! Y B|!  BBTE@A! W B|! )! !  7 B|  )}7 Ak"$ B7A#! U Aj"$ Aj"$A  R@A! R  7 B| > B | > B| 7 Ak"$ B7A#! Q )x!A! O  RE@A! O  B|!B!A! M  7 B| > B | > B| 7 Ak"$ B7A#! M B$|5! B |5! B(|)! B|)! )x!A! K  RE@A! K  B|!B!A! I  7`  7  7 B| > B | > B| 7 Ak"$ B7A#! I B$|5! B |5! B(|)! B|)! )`! )!A! G B!B!B!B!A! F P@ Ak"$ B7A#! G  < B! B||! B|!B! B||!A! D  < B! B|! BB! B|!  X@A! C  < B! RE@A! B BTE@A! A P@ Ak"$ B7A#! A  1 ! B|! BB! B|!  XE@A! ? B|! ! !A4! < Bx|!A! ; PPE@A! ;  1 B BT!  |"BTE@A! : P@ Ak"$ B7A#! :  1 ! B|!A! 8 Bx|! !A! 7 BTE@A! 7  !  |! Bx|!A! 4 !A! 3 BQE@A! 3 P@ Ak"$ Bǁ7A#! 3 B"B!  B BB T 1B3< B|! BXE@A! 2 B!B!A4! 0 B! B~|!B!A! / PPE@A! / ) B| ~ ))|B!A0! - !A.! , B|B"B|! B B}!  |B|! ! B!B! A(! + ! B!B! B! B! A(! *  7p  <F  7h ))! ))!  7 B| > B | > B| 7 B| 7 B | 7 B(| )7 B0| )7 B8| B|7 Ak"$ B܁7A#! * )h! )p! 1F! A! ( P@ Ak"$ B7A#! ) B 1 ))BQB" B"BB"B!  B"B! B"BXE@A! ( PE@A! ' P@ Ak"$ B䁰7A#! '  1B B< Aj"$ Aj"$A P@ Ak"$ B聰7A#! %  1B B<A! #  <G BQE@A! # P@ Ak"$ B쁰7A#! # 1"B3! B3!  B < B|"BBTE@A! " P@ Ak"$ B􁰅7A#! "  BBB 1Bn<A!  RE@A!  B|!A!   7 B| > B | > B| 7 Ak"$ B7A#!  B|)! 1G!A!  BQE@A!  P@ Ak"$ B7A#!   1B BB<  RE@A!   B|! P@ Ak"$ B7A#!   BBB3 1BL<A!   7 B| > B | > B| 7 Ak"$ B7A#!  B|)! 1G!A!  ))BQE@A!  P@ Ak"$ B7A#!  1! B! B3 B BT < Aj"$ Aj"$A P@ Ak"$ B7A#!  P@ Ak"$ B7A#!  1B! ))B!B ! BT!  B B|B! 1! B!B3 ! BT!   B  B B < Aj"$ Aj"$A !B!B!B!B!A!  Aj"$ Aj"$A B7 B|B!7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B|)!  7h B|)!  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  )7 B| )h7 Ak"$ B7A#! Ak"$ B7A#! Ak"$ B7A#! B7 B|B-7 Ak"$ B7A#! Ak"$ B7A#!  Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ A  !"#$%&'()*+,-./0123456789:;< #(M@ Ak"$ B7A#! > Ak"$ )BPP@A?! = ) )Q@A"! < )PPE@A4! ; B8|A#! ) )! )B" )B}"PPE@A!! : B8|B<B TE@A ! 9 B:|B< B|!B!A! 7 B8| | B< B! B|! B XE@A ! 6 B(T@A! 5 A2! 4 B(TE@A0! 4 B8| | < B|! B(TE@A.! 2 B8| |B< B|!A! 0 B8| | B< B! B|! B XE@A! / B(T@A! . A,! - B(TE@A*! - B8| | < B|! B|"!A! + B8| | B< B! B|! B XE@A! * B(T@A! ) A(! ( B(TE@A&! ( B8| | < B|"B(TE@A$! '  70 B8| |B<  )7 B| B8|7 B| )h7 B|B7 Ak"$ B7A#! & ) )0~ )|B!  B|B! )h )B|! )h |"!  7 B|  }7 Ak"$ B7A#! $ Aj"$ Aj"$A B!A ! ! B!A !  )7 B|B7 B| )h7 B|B7 Ak"$ B7A#! B |)"B )R@A6!  A!   7 B|B(7 Ak"$ B7A#!   7 B|B(7 Ak"$ B7A#!   7 B|B(7 Ak"$ B7A#!   7 B|B(7 Ak"$ B7A#!   7 B|B(7 Ak"$ B7A#!   7 B|B(7 Ak"$ B7A#!   7 B|B(7 Ak"$ B7A#!   7 B|B(7 Ak"$ B7A#!  Ak"$ B7A#!   7( Ak"$ B7A#! B7 B|B+7 Ak"$ B7A#!  )(7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B+7 Ak"$ B7A#!  B7 B|B'7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )PB!BBT! B B|BB B|!  7(  7 B|B7 B|B/7 Ak"$ B7A#! )"P@ Ak"$ B7A#! )(BXE@A ! )(B|" )(TE@A !  78  |!  70 B<  )H7 B|B7 B| 7 B|B7 Ak"$ B7A#! B |)! )01BR@A ! B| > B| )87 Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!   7 B| )(7 Ak"$ B7A#!   )(7 B|B7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!""####$%&&&&'''(()*+,-.....//012333334456789:;<=>??@@AAAABBBCCDEEEEFFFFFGGHHHHHHHHHIIJKKKLMMMMNNNNNOOPPPPPPPPPQQRSSSTUV #(M@ Ak"$ B7A#! X B! )! )! )!B!A! V Bx|! B XE@A! U BQE@A ! T P@ Ak"$ B7A#! T  < B! B|!A! R P@ Ak"$ B7A#! S  BB<  BBB< B! B|!A! Q P@ Ak"$ B7A#! R 1! B! B! B|! PE@A! Q PE@A-! P PPE@A! O B! ! ! ! ) !A! K BQE@A%! K  )}B |!B }B |!A#! I P@ Ak"$ B7A#! J  < Bx|! B! B|! PP@A! H B(| 7 Aj"$A  )}B |!B }B |!A,! E P@ Ak"$ B7A#! F  BB< B||! B! B|! PP@A&! D A$! C B! B! A1! B B|! B|! TE@A! A  1 ! BQE@A:! @ P@ Ak"$ B7A#! @  < B! B|!A.! > P@ Ak"$ B7A#! ?  BB<  BBB< B! B|!A.! = B"PPE@A! =  1 !  |! B|! ! ! !A! : PE@A! : B!A! 8 B|! 1!  B B BT! B! B|! PE@A! 7 B!B! A! 5 B|! 1!  B ! BT!  B ! B! B|! PE@A! 4 ~! B9XE@A! 3 BQ" E@A! 2  B|! ! ! A! 0 B 1! B|! B|!  T@A! / TE@A! .  }!  B BT! BQE@A! , BQE@A! + B9!B!A! ) P@ Ak"$ B怼7A#! *  < B! Bx|! B|! B X@A! (  }! ! !  XE@A! &  B BT !  |! @A! % A! $ P@ Ak"$ B7A#! %  BB< B! B||! B|! B X@A! # A! " PPE@A! " B B BTB| B BT ! |! ! ! ! ! !A!  !A!  |B9XE@A!  !A!   B BT ! |! BX@A!  PPE@A!  B9  ~! B B BTB|! !A!  ! A!   B|! ! ! A!  B 1B! B|! B|!  T@A!  A!  }! BQE@A!   B|" B! B" P!  }" ! PE@A!  P@ Ak"$ B7A#!  1! B }! B BT  ! |!  }! B|! B! A!  P@ Ak"$ B7A#!  P@ Ak"$ B7A#!  1 B BT !   < B|!  B! B|! B|! PP@A!  B"PPE@A! P@ Ak"$ B7A#! B B BTB| 1 B BT !  |! ! ! !A!  B|" B! B" P!  }" ! PE@A! P@ Ak"$ B7A#! 1B! B }! B BT  ! |!  }! B|! B! A!  P@ Ak"$ B7A#!  P@ Ak"$ B7A#!  1B B BT !   BB< B|!  B! B|! B|! PP@A!  B"PPE@A!  P@ Ak"$ B7A#!  B B BTB| 1 B BT !  |!A!  Ak"$ Bā7A#!   A ~#!@@@@@   #(M@ Ak"$ B7A#!  A0k"$ B)7 )8B?|"B!BBT! B B?|! B| B BB BT7 B|B< Ak"$ B7A#!  )!  7( P@ Ak"$ B7A#!   )!  )@B|7 B|B7 B| 7 B|B7 Ak"$ B7A#!  B| )(7 A0j"$ Aj"$A A ~#!@@@@@@@@@@@@  #(M@ Ak"$ Bą7A#! A(k"$ B|B7 B|B7 B |B7 B|B7 B | B|7  B|7 Ak"$ Bą7A#! B!A!  B|)"P@ Ak"$ Bą7A#! B(| B|B.7 B|! BS@A!  B')"BQE@A!  B! B|)"P@ Ak"$ Bą7A#!   7 B|)! B0| 7 A(j"$ Aj"$A  7 Ak"$ Bą7A#!  B|4!A !  A #!@@@@  #(M@ Ak"$ Bȅ7A#!  Ak"$ B|B7 B|B7 B|B7 B| ) 7  B|7 Ak"$ Bȅ7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 4  !"#$% #(M@ Ak"$ B̅7A#! ' Ak"$ )XP@ Ak"$ B̅7A#! ' 1`B"BTE@A2! & )XB(|" B|")"P@ Ak"$ B̅7A#! & 3` )8R@A0! %  7@  7H B.RE@A ! $ B؉)5B|B 5XR@A.! # B) B~|7 B| 7 Ak"$ B̅7A#! " B) )@B~|7 Ak"$ B̅7A#! B|)"PPE@A,!  3` )8Q@A*!   7( B؉)5B|>X B/7 Ak"$ B̅7A#!  B|)"P@ Ak"$ B̅7A#!  1`B"BB8B8! BTE@A(!   78 B| B|! )()8! )(3`!  7 B|  }7 Ak"$ B̅7A#!  B/7 Ak"$ B̅7A#!  )(3`! )()h! )() ! B/7 B| B  ~}7 Ak"$ B̅7A#!  )8BQ@A&!  )X)! B/7 B| 7 Ak"$ B̅7A#!  )XB7B*1@A!!  B.5PP@A!  )H )(7 Aj"$ Aj"$A B.7 Ak"$ B̅7A#!  A!   B0|B7 B0|B/)7 B!< B|B7 B| B0|7 B|B7 B |B7 Ak"$ B̅7A#!  A!  )X) ! B/7 B| 7 Ak"$ B̅7A#!  )XB7 A!  7 B|B7 Ak"$ B̅7A#! B7 B|B7 Ak"$ B̅7A#! B7 B|B 7 Ak"$ B̅7A#! B7 B|B7 Ak"$ B̅7A#!  B7 B|B(7 Ak"$ B̅7A#!   7 B|B7 Ak"$ B̅7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 8  #(M@ Ak"$ BЅ7A#! ! Ak"$ )hB| )hT@A6! )hB "B|  )hB?PP!  78 B !  7@  7 B| 7 Ak"$ BЅ7A#!   B.| 1q< B.|1!  </ B)7 B| )87 B| < B| 1p< Ak"$ BЅ7A#!  B|)"PPE@A4!   70 B/7 Ak"$ BЅ7A#!  B|)!  7P P@ Ak"$ BЅ7A#!   B0|7 B| )@7 Ak"$ BЅ7A#!   )PB8|7 B|B7 Ak"$ BЅ7A#!  B/7 Ak"$ BЅ7A#!  B/7 B| )@7 Ak"$ BЅ7A#!  B*1@A)!  B.5PP@A'!  1/BTE@A1!  B) 1/B~|!  B|B؉)5BBBB(~|7 B| )07 Ak"$ BЅ7A#!   )0 )h )0)|7pBȊ))"P@ Ak"$ BЅ7A#!   )0)! B"BTE@A.!   B|)"PPE@A&!   BB|! BB! B|!  7 B| > B | > B| 7 B| )07 Ak"$ BЅ7A#!  B| )07 Aj"$ Aj"$A B!B!B!B!A$! B.7 Ak"$ BЅ7A#! A!  B|B7 B|B/)7 B!< B|B7 B| B|7 B|B7 B |B7 Ak"$ BЅ7A#! A!  7 B|B7 Ak"$ BЅ7A#!  1/7 B|B7 Ak"$ BЅ7A#!  B7 B|B 7 Ak"$ BЅ7A#!  B7 B|B 7 Ak"$ BЅ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ Bԅ7A#!  Ak"$ )HP@ Ak"$ Bԅ7A#!  )H)! B/7 B| 7 Ak"$ Bԅ7A#!  )HB7B؉)5!  > )H!B!A!  B|! BSE@A!  B(|" B|")"B.RE@A!   7(  78  7 P@ Ak"$ Bԅ7A#!  )8!  70 3`!  = B/7 Ak"$ Bԅ7A#!  B|)"P@ Ak"$ Bԅ7A#!  )(BBB8B8! BTE@A!   B| B|7 B| 3 )0}7 Ak"$ Bԅ7A#!  B/7 Ak"$ Bԅ7A#!  5B|B ) 5XR@A! B) )(B~|7 B| ) 7 Ak"$ Bԅ7A#! )8B.7 5! )H! )(!A! ) )h! B/7 B| 3 )0} ~7 Ak"$ Bԅ7A#! A!  B7 B7 ) ! B/7 B| 7 Ak"$ Bԅ7A#!  )HB7 B.5PP@A!  Aj"$ Aj"$A B.7 Ak"$ Bԅ7A#!  A!   7 B|B7 Ak"$ Bԅ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B؅7A#!  Ak"$ ) P@ Ak"$ B؅7A#!  B؉)5! ) 5 " Q@A !   B~|BR@A !   ) 7 Ak"$ B؅7A#!   ) 7 Ak"$ B؅7A#!  ) B |!B؉)5!  7 B| > Ak"$ B؅7A"#! Aj"$ Aj"$A Aj"$ Aj"$A  >  > Ak"$ B؅7A#! B7 B|B 7 Ak"$ B؅7A#!  57 Ak"$ B؅7A#!  B7 B|B7 Ak"$ B؅7A#!   57 Ak"$ B؅7A#!  Ak"$ B؅7A#!  Ak"$ B؅7A#!  B7 B|B 7 Ak"$ B؅7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ X  !"#$%&'(()*+,-./0123456667899:;;<==>? #(M@ Ak"$ B܅7A#! A Ak"$ )`P@ Ak"$ B܅7A#! A )`1"BB8B8! BTE@A! @ B' |1B 7 B|B7 Ak"$ B܅7A#! ? B؉)5!  > B*1@A! >  )`B|!  7P BB!  >$ B"B(~!  78   |7 Ak"$ B܅7A#! = B|)"PP@A! < 5$! )P!B!A! : )(B|! 5$! )P!  7(B WE@A'! 9 B }B"BTE@A! 8   B(~|7 Ak"$ B܅7A#! 7 B|)"PPE@A'! 6  70 B|!  7H  7 Ak"$ B܅7A#! 5 B|5! 5 B~|!  BQ@A%! 4 B! E@A ! 2  )07 B|B< Ak"$ B܅7A#! 1 B! )0!  70B*1E@A! / E@A#! . P@ Ak"$ B܅7A#! . )8! 3` Q@A! - )0" Q@A! ,  7 B| B@B7 Ak"$ B܅7A#! + )0)@! )0)0B?! )0  B BT7@ B| )07 Aj"$ Aj"$A Ak"$ B܅7A#! ) )0!A! '  )H7 B| > B | 5 B|> Ak"$ B܅7A$#! ' B|1!A! % )`! 5$! )(!A+! $ )(B|! )`! 5$! B WE@A! # B }B"BTE@A! "  7( B|!  7@   B(~|7 Ak"$ B܅7A#! ! B|)"PPE@A!  70 B|!  7H  7 Ak"$ B܅7A#!  B|5! 5 B~|!  BQ@A?!  B! E@A)!   )07 B|B< Ak"$ B܅7A#!   )07 Ak"$ B܅7A#!  B|)" )0)8R@A>!    )@ )8|7 B| )07 Ak"$ B܅7A#!  A)!  )0 70B! )0!A!   )H7 B| > B | 5 B|> Ak"$ B܅7A$#!  B|1!A7!  B*1!  < @A!   )`7 Ak"$ BÀ܅7A#!  B|)"PPE@A!  ! 1!A!  B|B7 Aj"$ Aj"$A Ak"$ Bǀ܅7A#!  A! !B!A! Ak"$ Bʀ܅7A#! 5 !A! B7 B|B7 Ak"$ B΀܅7A#!  7 B|B7 Ak"$ Bр܅7A#!   7 B|B7 Ak"$ BԀ܅7A#!   7 B|B7 Ak"$ B׀܅7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ B7A#!  A k"$ )0P@ Ak"$ B7A#!  )03`"P@A!  B؉)5!  > )0B|! )05X" B|BQ!  < E@A!   7 B| B|> Ak"$ B7A"#!  1"@A!  B )0)8 )03`}SE@A! )(P@ Ak"$ B7A#!  )(B|!   5BBBB(~|7 B| )07 Ak"$ B7A#! A j"$ Aj"$A )(P@ Ak"$ B7A#!  )(B|!   5BBBB(~|7 B| )07 Ak"$ B7A#! A!   )07 B|B< Ak"$ B7A#!  A!   7 B| > Ak"$ B7A"#!  A!  B7 B|B$7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A0k"$ )8P@ Ak"$ B7A#!  )81"BB8B8! BTE@A!  B' |1!  <%B' B|3!  =& B)7 B| 7 B| < B|B< Ak"$ B7A#!  B|)"PPE@A! 1%B ! 1e!  ! BT! B  3\~! 1f!  B BT!  )  3&~|7pBȊ))"P@ Ak"$ B7A#!  )! B"BTE@A!  B|)"PPE@A!  BB|! BB! B|!  7(  7 B| > B | > B| 7 B| 7 Ak"$ B7A#! B| )(7 A0j"$ Aj"$A B!B!B!B!A!  B|B7 A0j"$ Aj"$A  7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$B))!B))!B SE@A!   70B!A!   |B< B|! BS@A!  B|" SE@A!  B|! BȊ))"P@ Ak"$ B7A#! )"BTE@A!  B|)"P@ Ak"$ B7A#! )"PPE@A! B!A! B.B< Aj"$ Aj"$A  7(  78  7 B7 B|B7 B|B/7 Ak"$ B7A#!  B|)"PPE@A!  ) 7 )0! )8! )(!A!  B7 B|B*7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A #!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 Ak"$ B7A#!  1@A!  B.B< Aj"$ Aj"$A Bƣ7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .  #(M@ Ak"$ B7A#!  A0k"$ )PP@ Ak"$ B7A#!   1X )P1"BPPE@A!  BȊ))"P@ Ak"$ B7A#!   )8B"BTE@A!   7  B|)"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   )8BB|!  7(  7 Ak"$ B7A #!  ) B!B B BT! B|1 BPPE@A!  B|B< A0j"$ Aj"$A  )(7 B| < Ak"$ B7A#!  B|B< A0j"$ Aj"$A  7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B97 Ak"$ B7A#!   )87 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#! Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )@7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )H7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 B| )@7 B| )H7 Ak"$ B7A#!  B7 B|B7 B| )87 B|B7 Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  B< B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@  A k"$ B7 B| )(7 Ak"$ B7A#!  )!  7  7 B| )(7 B| )07 Ak"$ B7A#!  B8| )7 A j"$ Aj"$A A x#!@@@@  Ak"$  )(7 B|B ) }7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )PP@A!  B')"P@ Ak"$ B7A#!  B.)! )!B.)!  |B|"B }!  TE@A !  B. 7 B.)"!  7B.)! ) |B|B } |!B. 7 Ak"$ B7A#! 4!B.)"P!B.)! PE@A!  !  B B S@A! B(| )7 Aj"$ Aj"$A   }> Ak"$ B7A#! B|4BBQ@A!  Ak"$ B7A#!  A !  B(|B7 Aj"$ Aj"$A B(|B7 Aj"$ Aj"$A Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$  )(7 B| ) 7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 0  #(M@ Ak"$ B7A#!  A0k"$B.5PP@A.!   B.7 Ak"$ B7A#!  B.)"PPE@A !  5BQE@A!  B.)PP@A!  B 7 B|B7 B|B/7 Ak"$ B7A#!  B. B|)7B.)"P@ Ak"$ B7A#!  B.)7B.B.)7B.1PE@A!  B!A!  B. |B' |1< B|! BSE@A!   B#!B~}"BT@A!  A,!  B.)"P@ Ak"$ B7A#!  B. )7 B.)7B. 7 B.)"P@ Ak"$ B7A#!  5"BTE@A*!   7(  >$  B|7 B|B> Ak"$ B7A#!  )(B| 5$B(~|!B.5PP@A(!   )@7  )H7B.5PP@A#!  )P7  )X7  )87 B.B< B.7 Ak"$ B7A#! A0j"$ Aj"$A B| )P Ak"$ B7#! B | )X Ak"$ B7#! B| )8 Ak"$ B7#!A!   )@ Ak"$ B7#!A!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ B.7 Ak"$ B7A#!  B.1E@A!  B.1E@A!  B.B<B.B<B')!  7 B.7 Ak"$ B7A#!  B| )7 Aj"$ Aj"$A B!A !  A ~#!@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A0k"$ )8P@ Ak"$ B7A#!  )8)"P@A!  )8)"PPE@A !  )8 )7 )8 )8) )8)0|70 )81@@A!  B| 7 A0j"$ Aj"$A  7 )8)!  7 B| 7 Ak"$ B7A#!  ) !A! )85( T@A! )8) ! )8)"PP@A ! )8 )8) )8) |7 )8 )85( )8)}>( )8 )8) )8)0|70 B| 7 A0j"$ Aj"$A  7( )!  )8)7 B| 7 $  Ak"$ B7Av!A #! )(!A !  )8)8! B7 B|B7 B| 7 Ak"$ B7A#!  )8 B|)7 )8B>(A !  Ak"$ B7A#!  B7 B|B47 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A #!@@@@@  #(M@ Ak"$ B7A#!  Ak"$B܉)B>B/D?9B/Bؠ')D?#!7 Ak"$ B7A#!  Ak"$ B7A#!  B.B>B.B> Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$ B7 B|B7 Ak"$ B7A#! )! )"BQE@A! 1BQE@A! 1BQE@A! 1BQ@A!    7 B| 7 Ak"$ B7A#!  B|)! B|1! B B QE@A!  @A!  B(|B> A j"$ Aj"$A B(| > A j"$ Aj"$A B(|B> A j"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ B7 B|B7 Ak"$ B7A#!  )!  7 B> B|B7 Ak"$ B7A#!  B> B|B7 B| )7 Ak"$ B7A#!   )7 B|B7 Ak"$ B7A#!   )7 B|B7 Ak"$ B7A#!  B/B< A j"$ Aj"$A A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ,  !" #(M@ Ak"$ B7A#! $ Ak"$ )PP@ Ak"$ B7A#! $ )PB7 )PB7 )PB7 )PB7 )PB7 )PB7(B/)!B/)B|!  TE@A! # B/ 7 B.4D?"D?#!! )P 78 " D?"D333333ӿcE@A*! !  cE@A! )P B|78 )PB|  )P)8B.49 BB.4SE@A !  )PB.478 )PB|D9 B')!B')!B SE@A!  B!A!  B|! )"P@ Ak"$ B7A#!  B7, B7- B|" S@A !   )P7 Ak"$ B7A#!  BB.4S@A!  Aj"$ Aj"$A  )PB|7 Ak"$ B7A#!  B|)! B| 7B/)!  7@B.)!  78B/)!  70 )P)8!  7( )PB|+!  9 B|+!  9 Ak"$ B7A#!  Bͩ7 B|B7 Ak"$ B7A#!   +9 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )@B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )8B7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )0B7 Ak"$ B7A#! Bّ7 B|B 7 Ak"$ B7A#!  )(7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!   + 9 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  A!  D333333? c@A!  )PB|D9A !  A ~|#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$B.4!  > B/7 Ak"$ B7A#! B|)!  7 B/7 Ak"$ B7A#! )XP@ Ak"$ B7A#! B|)!  7H  )X7 Ak"$ B7A#! B|)!  7 B/7 Ak"$ B7A#!  4BS! B|)! )HDY@"B 4 B|B B !  ) SE@A!  D?#!! )H!  )}"BS!  ) }"BW!B  !  9@B  !  98  ! B(| 9 )XB|! B(|)!  7 B| 7 Ak"$ B7A##!  +8 +@! B0| 9 )XB|! B0|)!  7 B| 7 Ak"$ B7A##!  Aj"$ Aj"$A #!" )S@A!  A !  A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -  !"#$%&' #(M@ Ak"$ B7A#! ) Ak"$B.1@A,! ( Ak"$ B7A#! ' +!  9 B/)!  7xB/)!  7p Ak"$ B7A#! & )P@ Ak"$ B7A#! & )x )p! ) ))0}!B SE@A+! % )) B.4~D?! D?!B/+! + ! D333333?!  ! D? ! BB.4S@A ! # B| 9 Aj"$ Aj"$A  9(  9h  9`  9  9X  9P  9B/)!  78B/)!  7HB/)!  7@ ))!  70 Ak"$ B7A#! ! B7 B|B7 Ak"$ B7A#!  )87 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   +h9 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )H7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   +(9 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )@7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   + 9 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   + D? )8#!7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   +9 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  D333333?9 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )07 Ak"$ B7A#! Bւ7 B|B7 Ak"$ B7A#!  +`9 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  +P9 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!   +X9 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  +! A !  D?!A !  B/+! B| 9 Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 7  #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  ))8BW@A4!  B.4BW@A3!  #PPE@A2!  #)0"PPE@A2!  )"P@A2!  "P@ Ak"$ B7A#!   4!B!A!  B|! BSE@A1!  #)0"P@ Ak"$ B7A#!  B.4! 5! 5!  > B ! B BB  !  >  |B B|B~"B !  B B WE@A!  B|! B B !B')!B')!  TE@A5!   B|)"P@ Ak"$ B7A#!  5BR@A!   )8! "PPE@A%!  #)0QE@A)!  B! E@A! Aj"$ Aj"$A )"PPE@A,!  )QE@A.! B!A'!  B< Bu7B!A'!  Aj"$ Aj"$A Aj"$ Aj"$A Aj"$ Aj"$A Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ )  #(M@ Ak"$ B7A#!  A k"$B.5P@A'!   )07 Ak"$ B7A#!  B|1E@A&!  B.7 Ak"$ B7A#!  B|)"PPE@A%!   7 )(P@ Ak"$ B7A#!  B)!  )(B8|7B$  Ak"$ B7Av!A #!  B|1E@A!  )0P@ Ak"$ B7A#!  )0B7- ))! !  7  7 B|B> B |B> Ak"$ B7A#!  B*1@A!  B8| )7 A j"$ Aj"$A  )7 B|B7 Ak"$ B7A#!  A!  )(B|"+Da@A#!   7 Ak"$ B7A#! )B.)}!B SE@A! )0P@ Ak"$ B7A#! )+ )0)- c@A!! )0P@ Ak"$ B7A#! )0B7-A ! B.7 B| )7 Ak"$ B7A#! B8|B7 A j"$ Aj"$A B.7 B| )7 Ak"$ B7A#!  B8|B7 A j"$ Aj"$A B8|B7 A j"$ Aj"$A B8|B7 A j"$ Aj"$A BӁ7 B|B67 Ak"$ B7A#!   A ~|#!@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ Ak"$ B7A#!  B.)! )" }"BWE@A !  B|B< Aj"$ Aj"$A #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   )-! )-!B.+! B| D333333?   }| c< Aj"$ Aj"$A A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 8  !"#$%&'()*+,-./012345 #(M@ Ak"$ B7A#! 7 Ak"$B.4!B W"E@A&! 6 B/)" ~B |! E@A#! 4 DY@"Dffffff?" +hcE@A"! 3 D333333?!  cE@A!! 2 B/ 9BB.4WE@A ! 1  9h D?B/)!Bؠ')!B܉)5PPE@A! 0 #!!    T"BS@A'! /    T!  7B/ 7 B/7 B| 7 Ak"$ B7A##! - B*1@A! , B.5PP@A! + B܉)5PPE@A! * B)D9 Ak"$ B7A#! ( Aj"$ Aj"$A B/7 Ak"$ B7A#! & B|)!  7( B)7 Ak"$ B7A#! % B|)!  7 B)7 Ak"$ B7A#! $ ) )(}B@|"BS! B|) )}!B  ! BWE@A! # B)D9A! ! B)  9B) )(7 B)7 B| )7 Ak"$ B7A##! ! A!  B.7 Ak"$ B7A#!  A!  Ak"$ B7A#!  A !   9X  7  70 B/7 Ak"$ B7A#!  B|)B|" ) ) T! )0! +h! +X!A !  B!A !  !A!  +h!A!  +hDcE@A%!  D!A!  +h!A!  B!A!   7 B/)!  7PB/)!  7HB/)!  7@B.)!  78 Ak"$ B7A#!  Bן7 B|B7 Ak"$ B7A#!   )P7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )H7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  )@7 Ak"$ B7A#! B؝7 B|B7 Ak"$ B7A#!  )87 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  +h9 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   ) 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~|#!@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B/7 Ak"$ B7A#!  )!B/)!  } "DcE@A!  D! B| 9 Aj"$ Aj"$A A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$A !  #B.)7B.#7 B7 B|B.7 B|B< B|B< B|B7 Ak"$ B7A#!   B.7 Ak"$ B7A#!  B.7 Ak"$ B7A#!  B|5!B.5BR! B|! 5(B   BTE@A!   B.7 Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$B/1E@A!  B.5PPE@A!  B0|B< Aj"$ Aj"$A B.5PP@A!  )P@A!  )BQE@A! B.4BS@A ! B/7 Ak"$ B7A#! B|)"PPE@A ! BР') ) }S! B0| < Aj"$ Aj"$A B!A !  B0|B< Aj"$ Aj"$A )BQE@A!  B.5! 5( }! B0|B B B S< Aj"$ Aj"$A B0|B< Aj"$ Aj"$A B/)!B/)! B0|  X< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!"#$%&&&&'()))))))))***++++++,,,-..///0012334556777889:;;;;;<=>>>??@AAABBCDEEEFFFGHHIJKLMNOPQRST #(AjM@ Ak"$ BĆ7A#! V Ak"$#)0"P@ Ak"$ BĆ7A#! V   4B|>#)0"P@ Ak"$ BĆ7A#! V #P@ Ak"$ BĆ7A#! V #7p# )Q@A! U 4!B S@A! T )PP@A! S   B|> BBQE@A! R #P@ Ak"$ BĆ7A#! R #1E@A! Q #Bu7 A! N B'B'5B|>  )7 B| )7 B| 5> Ak"$ BĆ7A#! M B|1@A! L B! @A! J  B.7 B|B< B|B7 B|B7 Ak"$ BĆ7A#! I  )7 B| )7 B| 5> Ak"$ BĆ7A#! H B|1E@A! G B. )BQ<B.4B"BQE@A! F B!  7@ B'7 B|B< B|B7 B|B7 Ak"$ BĆ7A#! D  B'7 B|B< B|B7 B|B7 Ak"$ BĆ7A#! C B*1@A! B B')!B')!B S@A! A Ak"$ BĆ7A#! @ B7 Ak"$ BĆ7A#! ? B.4!B. >B. >B.4" SE@A4! > B. > B/7 Ak"$ BĆ7A#! < B. B|)7B.B7B. )@7 Ak"$ BĆ7A#! ; )! B8| 7B. 7B. 7B*1@A! : B7 Ak"$ BĆ7A#! 9 B7 Ak"$ BĆ7A#! 8 Ak"$ BĆ7A#! 7 B.B.5B|> B.7 Ak"$ BĆ7A#! 6 B.B/)7 )@PP!  <+ @A! 5  B.7 B|B> Ak"$ B€Ć7A"#! 4 B.5"BQE@A! 3 B! B. < E@A! 1 B! B. <B.B>B.B> Ak"$ BʀĆ7A#! / Ak"$ BˀĆ7A#! . B.7 B|B> Ak"$ B̀Ć7A"#! - B. B8|)7#)0"P@ Ak"$ B΀Ć7A#! -   4B|>#P@ Ak"$ BҀĆ7A#! - #)0!  7h B|B7 B|B7 B|B7 B| B8|7  B|7 Ak"$ BՀĆ7A#! ,  B'7 B|B< B|B7 Ak"$ B؀Ć7A#! + )hP@ Ak"$ BڀĆ7A#! +  )h4! )h B|> BBQE@A! * #P@ Ak"$ B߀Ć7A#! * #1E@A! ) #Bu7 1+"@A! '  B.7 B|B< B|B7 Ak"$ BĆ7A#! & Aj"$ Aj"$A Ak"$ BĆ7A#! $ A! " B.1!A! ! BQ!A! B< Ak"$ BĆ7A#! A?!   B|B7 B|B7 B < B|B7 B| B|7 B|B7 B |B7 Ak"$ BĆ7A#!  A:!   7PB!A!  )xB|! !  7H  7x )!  7` P@ Ak"$ BĆ7A#!  )@"P@ Ak"$ BĆ7A#!   B |7 Ak"$ BĆ7A#!  B|5!B؉)5!  R@A!  )HB|" )PS@A!  A0!   B|B7 B|B*)7 B< B|B7 B| B|7 B|B7 B |B7 Ak"$ BĆ7A#!  B*B*)B|7A/!  BB BQ!A'!   B.7 B|B< B|B7 Ak"$ BĆ7A#!  Aj"$ Aj"$A Ak"$ BĆ7A#!  )BR!A!   4!  B|> BBQE@A!  )pP@ Ak"$ BĆ7A#!  )p1E@A!  )pBu7 Aj"$ Aj"$A  >,  >4 )`4!  >0 Ak"$ BĆ7A#! B7 B|B 7 Ak"$ BĆ7A#!  407 Ak"$ BĆ7A#! B7 B|B 7 Ak"$ BĆ7A#!  5,7 Ak"$ BĆ7A#!  B̑7 B|B 7 Ak"$ BĆ7A#!   547 Ak"$ BĆ7A#!  Ak"$ BĆ7A#!  Ak"$ BĆ7A#!  B7 B|B7 Ak"$ BĆ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ C  !!"""##$ #(M@ Ak"$ BȆ7A#! & Ak"$ B.7 B|B< B|B7 B|B7 Ak"$ BȆ7A#! % A ! # BȊ7 Ak"$ BȆ7A#! #  B'7 B|B< B|B7 Ak"$ BȆ7A#! " B.5BQE@A>! B.5B.5QE@A>!  B7 Ak"$ BȆ7A#!  B|1@A>!   B'7 B|B< B|B7 B|B7 Ak"$ BȆ7A#!  B.B> B7 Ak"$ BȆ7A#!  B.5PPE@A!   B'7 B|B< B|B7 Ak"$ BȆ7A#!  A !   Ak"$ BȆ7A#!  )!B. 7B. 7#)0"P@ Ak"$ BȆ7A#!  B7B.5"PP@A Ak"$ BȆ7A"#! Ak"$ BȆ7A#!  B.7 B|B< B|B7 Ak"$ BȆ7A#! B< Ak"$ BȆ7A#! B.7 Ak"$ BȆ7A#!  B|+9 Ak"$ BȆ7A#!  Aj"$ Aj"$A  B0|B7 B0|B7 B < B|B7 B| B0|7 B|B7 B |B7 Ak"$ BȆ7A#!  A$!  B|B Ak"$ BȆ7#!A#!   B.7 B|B< B|B7 Ak"$ BȆ7A#!  Aj"$ Aj"$A A 7~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"###$%&'()*++++,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^______````aaabbbcccdeeffffgghijklmnopqrstuvvwwwxxyyzz{||}~ #(AjM@ Ak"$ B̆7A#!  Ak"$ B.7 B|B> Ak"$ B̆7A"#!  B.5"BQE@A!  B! B. < E@A!  B! B. <B.B/)7 Ak"$ B̆7A#! ~ #)0"P@ Ak"$ B̆7A#! ~  )!  7X  4B|>#)0!  7 P@ Ak"$ B̆7A#! ~ #P@ Ak"$ B̆7A#! ~ B7 B|!  7B.5"PP@A! } B7 #7#)0"P@ Ak"$ B̆7A#! | B<#P@ Ak"$ B̆7A#! | #)0"P@ Ak"$ B̆7A#! | )!  7  7 B|B> B |B> Ak"$ B̆7A#! { )P@ Ak"$ B̆7A#! { )B< B|B7 B|B7 B|B7 B| )X7  B|7 Ak"$ B̆7A#! z BЊ7 Ak"$ B̆7A#! y ))0"P@ Ak"$ B̆7A#! y B<  )7 B|B> B |B> Ak"$ B̆7A#! x B*1@A! w )B7B.5PP@A! v )B7 B.5PP@A! t B/B/)7B/B/)7  +9 Ak"$ B̆7A#! s  Ak"$ B̆7A#! r )!  7 Ak"$ B̆7A#! q B|4!  >< )!  7`B.)!B. )B.)} |7B. )7B.)! B07 B| ) }7 Ak"$ B̆7A#! p B/7 )`B~ 4<|!  7H B| 7 Ak"$ B̆7A##! o B/7 B| )7 Ak"$ B̆7A##! n B/5"B"B!B/ B|B.)7B/B/5BBB| )H7B/B.)B/)|7B.4!B.)"B.)} ~!  7PB.)!B.) |!B.) |!B.)"B.)} ~!  7   ||!B.) |!B. 7B.4 )B')}~B')|!B/  9B'B>B'B>B.1E@A8! m B/B/5B|>  B.7 Ak"$ B̆7A#! k B/B/5B|> B.7 Ak"$ B̆7A#! j  B.7 Ak"$ B€̆7A#! i Ak"$ BĀ̆7A#! h B؊7 Ak"$ Bŀ̆7A#! g Ak"$ Bƀ̆7A#! f Ak"$ Bǀ̆7A#! e B7 Ak"$ BȀ̆7A#! d B7 Ak"$ Bɀ̆7A#! c BB.4SE@A! b B/+!  9 B|B7 B|B7 B|B7 Ak"$ Bˀ̆7A#! a B.)B.)}B=!B!A! _ ! ! B XE@A! ^ B !  B ~}! BTE@A! ] B| | B0|< B|! BQE@A! \ B| |B.< B~|!A! Z B W@A! Z BTE@A! Y B| | B0|<B/5!  >@  B|7B }! B| B| B }B?|7 B| 7 Ak"$ Bـ̆7A#! X B |)!  7 B|)!  7 Ak"$ Bڀ̆7A#! W B7 B|B7 Ak"$ Bۀ̆7A#! V  5@7 Ak"$ B܀̆7A#! U B7 B|B7 Ak"$ B݀̆7A#! T  )7 B| )7 Ak"$ Bހ̆7A#! S B7 B|B7 Ak"$ B߀̆7A#! R  +DY@#!7 Ak"$ B̆7A#! Q B7 B|B7 Ak"$ B̆7A#! P Ak"$ B̆7A#! O B.)! B|B7 B|B7 B|B7 B|B.)7 B|B.)7 B|B.)7B! B|!A! M )B|! ! )x!  7  7 )!  7x PP@A! L  B|7 B|B7 B|B7 B|  }7 Ak"$ B̆7A#! K B(|)! B |)!  B|7 B| 7 B| 7 Ak"$ B̆7A#! J B |)!  7 B|)!  7 Ak"$ B̆7A#! I  )7 B| )7 Ak"$ B̆7A#! H Ak"$ B̆7A#! G )B|"BS@A! F Ak"$ B̆7A#! E B7 B|B 7 Ak"$ B̆7A#! D Ak"$ B̆7A#! C B|A#! B| )P7 B|B.)7 B|B.)B.)|7 B|B.)7 B| )7B! B|!A! A )B|!  7  7 )!  7p BQE@A! @ Ak"$ B̆7A#! ? B7 B|B7 Ak"$ B̆7A#! > Ak"$ B̆7A#! =  B|7 B|B7 B|B7 B| )p7 Ak"$ B̆7A#! ; B(|)! B |)!  B|7 B| 7 B| 7 Ak"$ B̆7A#! : B |)!  7 B|)!  7 Ak"$ B̆7A#! 9  )7 B| )7 Ak"$ B̆7A#! 8 Ak"$ B̆7A#! 7 )B|"BS@A! 6 B.)!  7B.)!  7B.)!  7B.)!  7B.4!  >D Ak"$ B̆7A#! 5 B7 B|B 7 Ak"$ B̆7A#! 4  )B7 Ak"$ B̆7A#! 3 B7 B|B7 Ak"$ B̆7A#! 2  )B7 Ak"$ B̆7A#! 1 B7 B|B7 Ak"$ B̆7A#! 0  )B7 Ak"$ B̆7A#! / B7 B|B7 Ak"$ B̆7A#! .  )B7 Ak"$ B̆7A#! - B7 B|B 7 Ak"$ B̆7A#! ,  4D7 Ak"$ B̆7A#! + B7 B|B7 Ak"$ B̆7A#! * Ak"$ B̆7A#! ) B.1@A! ( Ak"$ B̆7A#! ' Ak"$ B̆7A#! & Ak"$ B̆7A#! % #)0"P@ Ak"$ B̆7A#! %  0!  B|< BBQ@A! $  B'7 B|B< B|B7 Ak"$ B̆7A#! #  B'7 B|B< B|B7 Ak"$ B̆7A#! "  )4! ) B|> BBQE@A! ! #P@ Ak"$ B̆7A#! ! #1E@A! #Bu7 Aj"$ Aj"$A  B.7 Ak"$ B̆7A#!  A!  Ak"$ B̆7A#!  B7 B|B 7 Ak"$ B̆7A#!  Ak"$ B̆7A#!  A!  BQ@A!  PPE@A!  Ak"$ B̆7A#!  B7 B|B7 Ak"$ B̆7A#!  Ak"$ B̆7A#!  )! )! )p!A!   7h Ak"$ B̆7A#!  B7 B|B7 Ak"$ B̆7A#!  Ak"$ B̆7A#!  )h! )x!A! )B Ak"$ B̆7#!A,!  B< B|B7 B|B7 B|B7 B |B7 Ak"$ B̆7A#! A*! B Ak"$ B̆7#!A! B.1!A!  BQ!A!   7 B|B7 Ak"$ Bā̆7A#!   7 B|B7 Ak"$ BƁ̆7A#!  B7 B|B7 Ak"$ BɁ̆7A#!   A #!@@@@@@@@@   #(M@ Ak"$ BІ7A#!  Ak"$A!  B> B|B7 Ak"$ BІ7A#!  B.7 B|B7 Ak"$ BІ7A#!  B.B7B.B.4B|> B.4B.4S@A!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$%&&''()*+,-./0123456789:;<=>>>?@ABCDE #(M@ Ak"$ BԆ7A#! G Ak"$ #7P#)0"P@ Ak"$ BԆ7A#! G B7B.5"PP@A! F B7 B7 Ak"$ BԆ7A#! D )PP@ Ak"$ BԆ7A#! D )P)0"P@ Ak"$ BԆ7A#! D B|)! B7B.5PP@A! C B7  7H  )P7#)0"P@ Ak"$ BԆ7A#! B   4B|>#P@ Ak"$ BԆ7A#! B  #)07 B.7 Ak"$ BԆ7A#! A B7 B| )H7 B|B< B|B< B|B7 Ak"$ BԆ7A#! ? #)0"P@ Ak"$ BԆ7A#! ?   4B|>#P@ Ak"$ BԆ7A#! ?  )H#)07 )P)0"P@ Ak"$ BԆ7A#! ? )!B.5! ! P@A! > P@ Ak"$ BԆ7A#! > )-P@A! =  7X Ak"$ BԆ7A#! < )!  70 )X 7- B.7 B|B> Ak"$ BԆ7A#! ; B|5!B.5!  Q@A! : B|B7 B|B7 B|B7 B|B7 B| )P7 B| )X7  B|7 Ak"$ BԆ7A#! 9  Ak"$ BԆ7A#! 8 ) )0}! )X)-"BQ@A! 7 BQ@A! 6 BQ@A! 5 B.7 B|B> Ak"$ BԆ7A#! 4 B|5!B.5" T@A! 3 )XB7- B.5Q@A! 2 B! E@A! 0 )H)"P@ Ak"$ BŀԆ7A#! 0  4!  B|> BBQE@A! / #P@ Ak"$ BˀԆ7A#! / #1E@A! . #Bu7  )HB7 Ak"$ BҀԆ7A#! , A! * B7 Ak"$ BԀԆ7A#! * B|1P!A! ( B.7 B| 7 Ak"$ BրԆ7A#! ( A=! &  78 B.7 B| 7 Ak"$ B؀Ԇ7A#! &  )XB-|7 B| )87 Ak"$ BـԆ7A#! % A=! # B.7 B| 7 Ak"$ BۀԆ7A#! # B.7 B|B7 Ak"$ B܀Ԇ7A#! " A=! B|B Ak"$ BހԆ7#!A !  B|B Ak"$ BԆ7#!A!   >$  >, )X)-!  7@ Ak"$ BԆ7A#!  B7 B|B7 Ak"$ BԆ7A#!   )@7 Ak"$ BԆ7A#!  B7 B|B 7 Ak"$ BԆ7A#!   5$7 Ak"$ BԆ7A#!  Bے7 B|B 7 Ak"$ BԆ7A#!   5,7 Ak"$ BԆ7A#!  Ak"$ BԆ7A#!  Ak"$ BԆ7A#!  B7 B|B7 Ak"$ BԆ7A#!   >(  >, Ak"$ BԆ7A#!  B7 B|B7 Ak"$ BԆ7A#!   5(7 Ak"$ BԆ7A#!  Bے7 B|B 7 Ak"$ BԆ7A#!   5,7 Ak"$ BԆ7A#!  Ak"$ BԆ7A#!  Ak"$ BԆ7A#! B7 B|B7 Ak"$ BԆ7A#! B7 B|B7 Ak"$ BԆ7A#! P@ Ak"$ BԆ7A#! )-!  7@ Ak"$ BԆ7A#!  B7 B|B 7 Ak"$ BԆ7A#!   )@7 Ak"$ BԆ7A#!  Ak"$ BԆ7A#!  Ak"$ BԆ7A#!  B7 B|B&7 Ak"$ BԆ7A#!   A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B؆7A#! Ak"$ )PPE@A !  ))-"PPE@A ! )PE@A! ))-"P@ Ak"$ B؆7A#! )PE@A!  B.7 Ak"$ B؆7A#!  B|)PE@A!  B.5B.5TE@A!  B |B< Aj"$ Aj"$A B |B< Aj"$ Aj"$A B |B< Aj"$ Aj"$A B |B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ h  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWX #(M@ Ak"$ B܆7A#! Z Ak"$B.4!B S@A/! Y B.5BR@A! X B. )7B.)!  7X PP@A! W B.5B.5T@A! V BB.4S@A-! U B.)PP@A! T B')!B')!B S@A! S B/B.)7B')!B')!B SE@A! R B!A ! P B|! )"P@ Ak"$ B܆7A#! P )@"PPE@A! O B/B/) )|7 B7 B|" S@A ! M B/B.)7B/B.)7B*1@A! L Aj"$ Aj"$A  B|B7 B|B/)7 B!< B|B7 B| B|7 B|B7 B |B7 Ak"$ B܆7A#! J A! H  7PB!A! G )pB|! ! )P!  7H  7p )!  7hBB.4SE@A)! F  7 Ak"$ B܆7A#! E )hP@ Ak"$ B܆7A#! D  )h)-"PPE@A'! C )PE@A1! B )h)-"P@ Ak"$ B܆7A#! B )PE@A1! A  )hB-|7 Ak"$ B܆7A#! @ )HB|" )PS@A! ? A! > P@ Ak"$ B܆7A#! ?  B-|7 Ak"$ B܆7A#! > A! < Ak"$ B܆7A#! < A! : Ak"$ B܆7A#! : A! 8 Ak"$ B܆7A#! 8 )h4!  >4 )h1-!  <+ Ak"$ B܆7A#! 7 B7 B|B 7 Ak"$ B܆7A#! 6  447 Ak"$ B܆7A#! 5 B7 B|B 7 Ak"$ B܆7A#! 4  1+< Ak"$ B܆7A#! 3 Ak"$ B܆7A#! 2 )h)-"PPE@A! 1 )!  7P Ak"$ B܆7A#! 0 B7 B|B 7 Ak"$ B܆7A#! /  )P7 Ak"$ B܆7A#! . Ak"$ B܆7A#! - )h)-"PPE@A! + )!  7P Ak"$ B܆7A#! * B7 B|B 7 Ak"$ B܆7A#! )  )P7 Ak"$ B€܆7A#! ( Ak"$ BÀ܆7A#! ' A! % Ak"$ Bŀ܆7A#! % B7 B|B 7 Ak"$ Bƀ܆7A#! $ Ak"$ Bǀ܆7A#! # A>! ! Ak"$ Bɀ܆7A#! ! B7 B|B 7 Ak"$ Bʀ܆7A#! Ak"$ Bˀ܆7A#!  Ak"$ B̀܆7A#!  Ak"$ B΀܆7A#!  Ak"$ Bπ܆7A#!  B7 B|B/7 Ak"$ BЀ܆7A#!  B7 B|B7 Ak"$ BҀ܆7A#!  B.5!  >0B.5!  >,B.)!  7PB.)!  7HB.)!  7@B.)!  78 Ak"$ BԀ܆7A#!  Bږ7 B|B7 Ak"$ BՀ܆7A#!   )X7 Ak"$ Bր܆7A#!  B7 B|B7 Ak"$ B׀܆7A#!   507 Ak"$ B؀܆7A#!  B7 B|B7 Ak"$ Bـ܆7A#!   5,7 Ak"$ Bڀ܆7A#!  B7 B|B 7 Ak"$ Bۀ܆7A#!   )P7 Ak"$ B܀܆7A#!  B7 B|B 7 Ak"$ B݀܆7A#!  )H7 Ak"$ Bހ܆7A#! B7 B|B 7 Ak"$ B߀܆7A#!  )@7 Ak"$ B܆7A#! B7 B|B 7 Ak"$ B܆7A#!  )87 Ak"$ B܆7A#!  Ak"$ B܆7A#!  Ak"$ B܆7A#!  B7 B|BЬ 7 Ak"$ B܆7A#!  B7 B|B87 Ak"$ B܆7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ :  #(M@ Ak"$ B7A#!  Ak"$B.5PP@A8!   B)7 Ak"$ B7A#!  B؉)B؉)5B|>B܉)B>B)B7B))!B))!B)B))7B) 7B.5PP@A6!  B) 7 B)B7B)B7 B)7 Ak"$ B7A#!   B'7 B|B> Ak"$ B7A"#!  ) BQE@A'!   B)7 Ak"$ B7A#!  B)D9 B)7 Ak"$ B7A#!  A!  B'B'5B|> Ak"$ B7A#!  )BR@A!  Ak"$ B7A#!  B< Ak"$ B7A#!  B|1@A"!  Ak"$ B7A#! Ak"$ B7A#! Aj"$ Aj"$A  B'7 Ak"$ B7A#! B'1@A3!  B'7 Ak"$ B7A#!  Aj"$ Aj"$A B'B< B')7 B|B7 B|B< Ak"$ B7A#!  A-!  B)  Ak"$ B7#!A !  B7 B|B)7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@ (  #(M@ Ak"$ B7A#!  A0k"$ B.7 Ak"$ B7A#!  B')!B')!B SE@A !  B!A !  B|! )"P@ Ak"$ B7A#!  B< B7 B|" S@A!   B.7 Ak"$ B7A#!   B)7 Ak"$ B7A#! B))!  7B))!  7  B)7 Ak"$ B7A#! B )SE@A$! ) !B!A! )(B|! ! ! BȊ))"P@ Ak"$ B7A#! )"BTE@A&!   7  7(  B|)"P@ Ak"$ B7A#!   B|7 B|B7 Ak"$ B7A#!  )B|" )S@A!  B.B7 B/7 Ak"$ B7A#!  B. B|)7 A0j"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3  #(M@ Ak"$ B7A#!  Ak"$B')"PP@A1!   B'7 Ak"$ B7A#!  B')!A !  ! PPE@A!  )!B.5PP@A !  B7A!  B|B Ak"$ B7#!A!  B.5PP@A.!  B'B7  B'7 Ak"$ B7A#!   B'7 Ak"$ B7A#!  B!A$!  ! PPE@A!!  )(!B.5PP@A!  B7(A! B(|B Ak"$ B7#!A! B.5PP@A&! B7 B|! BSE@A(! B' B|")!A!  B Ak"$ B7#!A#!   B'7 Ak"$ B7A#!  Aj"$ Aj"$A B'B Ak"$ B7#!A!  $ ) Ak"$ B7Av!A #!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 6  !""#$%&&'()*+, #(M@ Ak"$ B7A#! . Ak"$B )0XE@A! -  )0B=! ) B|! )! ) ! !A! + ! ! B XE@A ! * B !  B ~}!  TE@A4! )  | B0|< B|" QE@A! (  TE@A2! '  |B.< B~|!A! %  W@A! %  TE@A/! $  | B0|< )( }! B8|  B }B?|7 B8|  }7 B8| 7 Aj"$ Aj"$A )0B"P@A ! " B!A! B ! B|! B X@A!   ) B|! ) }B|! )! ) !A!  ! ! B XE@A!  B !  B ~}!  TE@A(!   | B0|< B|" QE@A!   TE@A&!   |B.< B~|!A!   W@A!   TE@A#!   | B0|< )( }! B8|  B }B?|7 B8|  }7 B8| 7 Aj"$ Aj"$A ) PPE@A-!  )B0< )(PPE@A*!  B8| )7 B8|B7 B8| )(7 Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#! B7 B| )(7 Ak"$ B7A#! B7 B| ) 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -  #(M@ Ak"$ B7A#!  Ak"$B.B7B.B7B.B7 B'7 Ak"$ B7A#!  )"PPE@A,!  )! )! B SE@A!  B!A !  B|! )"P@ Ak"$ B7A#!   ) )}!B.)! B|B!  SE@A!  B. 7 B|" S@A !   B'7 Ak"$ B7A#!  B|)"PPE@A+!  )! )! B SE@A%!  B!A! B|! )"P@ Ak"$ B7A#!  ) )}!B.)! B|B!  SE@A$! B. 7 B|" S@A! B))!B))!B) 7Bȋ) 7B.5PP@A)!  B) 7 B. 7 B.7 Ak"$ B7A'#!  B. B|)7B.B>B.B.)B.)B.)|B.)|B.)||B|> Aj"$ Aj"$A B)  Ak"$ B7#!A'!  B!B!A!  B!B!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2  ! #(M@ Ak"$ B7A#! # A0k"$B.5!B.5!  T@A*! "  B.7 Ak"$ B7A#! ! B!A !  B|! B.)SE@A!  B')!B')!  TE@A(!   B|)"P@ Ak"$ B7A#!  1@A!  A!   B.7 Ak"$ B7A#!  A0j"$ Aj"$A  7(  B|7 Ak"$ B7A#!  )()!  7 )(1!  < B|5!  > Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )(7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   ) 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   57 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   1< Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!  >  > Ak"$ B7A#!  57 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   57 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ I  !!"#$$$%&'''()**+,---./0123456 #(AjM@ Ak"$ B7A#! 8 Ak"$B.)"B|! B.)|"B.)|"B.)|!B.) |! 5B!B XE@A! 7  BT@A! 6 B|! B XE@A! 5  BT@A5! 4 B|! B XE@A! 3  BT@A'! 2 PE@A! 1 B.)!A ! /  7@  B|7 Ak"$ B7A#! / B|5!  )@B|7 B| B(~7 B|B.7 B| )7 B |B7 Ak"$ B7A#! . )@)! PP@A ! , Aj"$ Aj"$A BQ@A%! * B|! B XE@A! )  BT@A#! ( B XE@A! '  B|BTE@A! & 5 }B~|B!B')!B')!  TE@A! %  B|)!  7X P@ Ak"$ B7A#! %   B|7 Ak"$ B7A#! $ B|5"BQE@A"! # )X)PE@A ! " )XB.)7 B|B7 B|B7 B|B7 B|BĖ7 B| )X7 B| )7  B|7 Ak"$ B7A#! A !  BQ@A!  A !   )7 B| 5 }B~|B7 Ak"$ B7A#!  A !  BЋ7 Ak"$ B7A#!  A !   >, B'7 Ak"$ B7A#!  B|)"PPE@A4!  )! )! B SE@A !   78B!A0!  )hB|! !  7P  7h )"P@ Ak"$ B7A#!  )! )! )!  7 B|  }7 B| 7 B| )7 B | 5 5,}B~|B7 Ak"$ B7A#!  )PB|" )8S@A/!  A !  B!B!A-!   7P B'7 Ak"$ B7A#!  B|)"PPE@A!  )! )! B SE@A !  70B!A>! )`B|! !  7H  7` )"P@ Ak"$ B7A#! )! )! )!  7 B|  }7 B| 7 B| )7 B | 5 )P}B~|B7 Ak"$ B7A#! )HB|" )0S@A=! A !  B!B!A;!   5B~|B7 Ak"$ BĀ7A#!  A !   7 B| 7 Ak"$ Bƀ7A#!  B7 B|B7 Ak"$ BȀ7A#!   A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  A(k"$ )PBBBBT! )8 XE@A!  A(j"$ Aj"$A )0 |!  7 B| )8 }B )8 B|T7 B| )@ )PB |7 B| )H7 B |B7 Ak"$ B7A#!  A(j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@ 0 #(M@ Ak"$ B7A#!  Ak"$ B'7 Ak"$ B7A#!  B')! B| 7B'B7 B'7 Ak"$ B7A#!  B|)P@A.! B(|B7 B(|B7 B(| B|)7 B|)! B0| 7 B|)! !A!  78 )!  )7 B| 7 Ak"$ B7A#! )8B7 )8B7 B0| )87 )8)! ! PP@A!   B'7 Ak"$ B7A#!  B(|)! B(|)! B| 7 B | 7 PE@A(!   B'7 Ak"$ B7A#!  Aj"$ Aj"$A "P@ Ak"$ B7A#!   B')7B' B|)7A!!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ N  !"#$%&'((()*+,-./012345 #(M@ Ak"$ B7A#! 7 Ak"$B؉)5!B))!B))! ) TE@A! 6 BȊ))"P@ Ak"$ B7A#! 6  )B|)"BTE@A! 5  >0  B|)!  7P P@ Ak"$ B7A#! 5 B|!  7`B!A ! 3 )HB|! 50! )P! )`! BSE@A3! 2  7H   |7 Ak"$ B7A #! 1 B|1"P@A! 0  <.B!A! . B|! BTE@A! - B BP@A ! , )HB |"BTE@A! +  7@ )PB| B|)!  78 P@ Ak"$ B7A#! +   B|7 Ak"$ B7A #! * B|1"BR@A?! ) B.1E@A0! (  )8B|!  7h  7 Ak"$ B7A#! ' )8)!A ! % )! PPE@A)! $ 1 BR@A! #  3! )8)h"P! )8)! PE@A4! "  7X     ~|7 B| )x7 Ak"$ B7A#! !  )XB|7 B|B7 B|B'7 B| )x7 B |B7 Ak"$ B7A#! )X!A!    )h7 Ak"$ B7A#!  1.! )@!A !  )85X" 50Q@A!  50B|B Q@A!  A6!  Aj"$ Aj"$A Ak"$ B7A#!   >4 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   547 Ak"$ B7A#!  Ak"$ B7A#!   507 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   </ Ak"$ B€7A#!  B7 B|B 7 Ak"$ BÀ7A#!  1/7 Ak"$ BĀ7A#! Ak"$ Bŀ7A#! Ak"$ Bƀ7A#! B7 B|B+7 Ak"$ Bǀ7A#!  7 B|B7 Ak"$ Bɀ7A#!   7 B|B7 Ak"$ Bˀ7A#!   )7 B| 7 Ak"$ B̀7A#!   A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ C  !"#$%&'''(()*+ #(M@ Ak"$ B7A#! - Ak"$ )P@ Ak"$ B7A#! - ))0"P@ Ak"$ B7A#! - # )Q@A! , #)0"P@ Ak"$ B7A#! , B 4SE@A ! + Aj"$ Aj"$A )PP@A ! ) B!A ! ' 1/!  </ B.7 Ak"$ B7A#! & B|)! B| 7 B|+!  9P B.7 Ak"$ B7A#! % B|)! B| 7 ))!  7` B|+!  9X B.7 Ak"$ B7A#! $ B )`}" +P#!"BS! +XD@#!! B|)!   !B  !B S@A5! # B*1E@A4! " 1/"E@A/! !  </ B|B7 B|B7 B|B7 B|BȖ7 B| )7 B| 7  B|7 Ak"$ B7A#! ))!B.5PP@A-!  )B7 PP@A+!  ))BSE@A#!  )1E@A!!  Ak"$ B7A#!  1/!A !  Ak"$ B7A#!  1E@A !  1/"@A&!  Aj"$ Aj"$A  B,< B|B7 B|B7 B|B7 B |B7 Ak"$ B7A#!  A$!  Ak"$ B7A#!  A!  )B|B Ak"$ B7#!A!   78 B+< B|B7 B|B7 B|B7 B |B7 Ak"$ B7A#!  )8!B!A! 1/!A!  70  SE@A! ) ))  +X#!|B|7  7` B.7 B|B }7 Ak"$ B7A#! )0 )`}"P@A:! !A!  1/"@A Ak"$ B7A#! ' B|5!B.5!  Q@A! &  )X7 B|B> B |B> Ak"$ B7A#! % )XB<#)0"P@ Ak"$ B7A#! % )"P@ Ak"$ B7A#! %   B-|7 B| )`7 Ak"$ B7A#! $ B|)!  7(  )X7 B|B> B |B> Ak"$ B7A#! # B.7 Ak"$ B7A#! " B|)! B8| 7 )X B8|+ )(#! )X)|B|7 B.7 B|B> Ak"$ B7A#! ! B|5!B.5" T@A7!  Q@A/!   Ak"$ B7A#!  )X)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  ) )0}!  ),|!  7,B' S@A+!  Aj"$ Aj"$A  7@ B.7 B| 7 Ak"$ B7A#!  )@B7,A)!  B7 Ak"$ B7A#!  B|1@A!  B.5PP@A3!  )X )X7A!  )H )X Ak"$ B7#!A!  B Ak"$ B7#!A!   >  >$ Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   57 Ak"$ B7A#!  Bے7 B|B 7 Ak"$ B7A#!   5$7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  >  >$ Ak"$ B7A#! B7 B|B7 Ak"$ B€7A#!  5 7 Ak"$ BÀ7A#!  Bے7 B|B 7 Ak"$ BĀ7A#!   5$7 Ak"$ Bŀ7A#!  Ak"$ Bƀ7A#!  Ak"$ Bǀ7A#!  B7 B|B7 Ak"$ BȀ7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#!  B.)!B.B7B.B7 B| 7  B|7 Ak"$ B7A#!   B.7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@ 9  #(M@ Ak"$ B7A#!  A8k"$ B.7 Ak"$ B7A#!  B.7 Ak"$ B7A#!  5P@A3!  #70B.)!  7(B.)!  7 #B7B.)"PPE@A.!  "P@ Ak"$ B7A#!   #7 B. )07 B.7 Ak"$ B7A#! B B|)SE@A)! B. ) 7B. )(7 )(PPE@A#! )("P@ Ak"$ B7A#!  B7  B.7 Ak"$ B7A#!  B|B< A8j"$ Aj"$A  B7 B|B.7 B|B < B|B*< B|B7 Ak"$ B7A#!  B|B< A8j"$ Aj"$A B.#7A!   B.7 Ak"$ B7A#!  B|B< A8j"$ Aj"$A A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@ D  #(M@ Ak"$ B7A#!  Ak"$B.)P@A!  B.7 Ak"$ B7A#!  B|)! B | 7 B |+!  90 B.7 Ak"$ B7A#!  )H +0#!!A!   78 B7  7 B|B7 B|B< Ak"$ B7A#!  )8! B.)"P@A.!  B SE@A.!   "PPE@A!  )!B. 7 PE@A!  B.B7 P@ Ak"$ B7A#!   )|!B W@A !   7 B7B.)"PPE@A?@AABCDDEFGHIJKLLLMMMMNOPQRSTUVWXXXYYYYZ[\]^_`abcdddeeeefghijklmnop #(AjM@ Ak"$ B7A#! r Ak"$ )P@ Ak"$ B7A#! r  )B|!  7  7 Ak"$ B7A#! q 5B "BP@A! p   )7 Ak"$ B7A#! o B|5B_B"BXE@A! n BQE@A! m )#Q@A! l  ))pPE@A! k )1E@A! j B! @A! h )B< B|A*#! ))! B| ))7 B| 7 ))PPP@A! f B|B7 B|B7 B|B7 B|B̖7 B| B|7 B| )7 B7 B|B7 B|B7 B| )7 B |B7 B(|B7 B0|B7 B8| B|7 B|B7 B|B7 Ak"$ B7A#! e  )7 B| B|7 B|B7 Ak"$ B7A#! d ))(! B| 7A! b B|)"P@ Ak"$ B7A#! c )(! B| 7 B|)"PPE@A-! a )PP@A+! ` B|)"P@ Ak"$ B7A#! ` )(PP@A)! _ B|)"P@ Ak"$ B7A#! _ 1E@A! ^  B|7 B|B7 B|B'7 B| )7 B | B|7 Ak"$ B7A#! ] A! [  B(|7 B|B7 B|B'7 B| )7 B | B|7 Ak"$ B7A#! [ A$! Y  B|7 B|B7 B|B'7 B| )7 B | B|7 Ak"$ B7A#! Y A!! W )) "PP@A! W  B|)! B|)!  7 B|B7 B| 7 Ak"$ B7A#! V B| B|)7A B!A9! = B| )7 B7  7 Ak"$ B׀7A#! = B|)"PP@A! ; B|)PP@A! : B|)PP@A! 9 B|)PP@A! 8 Aj"$ Aj"$A !  B|7 B| 7 B|B< Ak"$ Bހ7A#! 6 A.! 4  )B|7 B|B7 B|B'7 B| )7 B | B|7 Ak"$ B7A#! 4 A! 2  )7 Ak"$ B“7A#! 2 A! 0  )B|7 Ak"$ B倜7A #! 0 B|1P!A! . B!A! - BX@A! - BQE@A! , Aj"$ Aj"$A BQ@A! * A! ) B7 B|B7 Ak"$ B퀜7A#! ) Bط7 B|B7 Ak"$ B7A#! '   )7 Ak"$ B󀜇7A#! % ))!  7x B|5!  >\ Ak"$ B7A#! $ B7 B|B 7 Ak"$ B7A#! #  )7 Ak"$ B7A#! " Bց7 B|B7 Ak"$ B7A#! !  )x7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!   5\7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!    )7 Ak"$ B7A#!  ))!  7x B|5!  >` Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Bց7 B|B7 Ak"$ B7A#!   )x7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   5`7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!    )7 Ak"$ B7A#! ))!  7x B|5!  >d Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )7 Ak"$ B7A#! Bց7 B|B7 Ak"$ B7A#!  )x7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   5d7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B۰7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1  !"#$%&' #(M@ Ak"$ B7A#! ) Ak"$ )P@ Ak"$ B7A#! ) ))! PP"E@A0! ( 1(BQ! E@A/! & 1(BQ! )P@ Ak"$ B7A#! % )1@A#! $ @A#! # @A#! "  )7 B| )7 B|B< Ak"$ B7A#! ! B|4! B |)! B(|4! B0|)! B8|)!  7x B|)!  7`B S@A!! B S@A!  ))8PPE@A!  B SE@A!  )!B!A!  B|! )! )! )8!B WE@A!  )@!  |" )(TE@A!  B|" S@A!  Aj"$ Aj"$A  7X  7p  )7 B| 7 B| 7 Ak"$ B7A#!  )! )X! )p! )`!A!   ))@7 B| B7 B| 7 B| )7 B | )7 Ak"$ B7A#!  )x! )`!A!   7h  >T B!  ))8 }7 B| 7 B| 7 B| )7 B | )7 Ak"$ B7A#!  4T! )h! )x! )`!A!   <S  <R ))8"PPE@A%!  ))(!  }"PP@A-!  ))H"PP@A+! E@A)! )B< Aj"$ Aj"$A @A'! )B<A(!   ))@7 B| 7 B|B7 B| )7 B | )7 Ak"$ B7A#!  1R! 1S!A&!   7 B| 7 B|B7 B| )7 B | )7 Ak"$ B7A#!  1R! 1S!A%!  B!A !  B!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ o  !"""#$%%&'()*+,-./012334456789:;;<<=>??@@ABCCCDEFFFGHIJKLMMNOOPQ #(M@ Ak"$ B7A#! S Ak"$B.1E@A! R #)0"P@ Ak"$ B7A#! R )XP@ Ak"$ B7A#! R )! )X)! )`B PPE@A! Q )`BPPE@A! P BЌ! B|!  7@  7  7H  7( )`BPP!  < )`BPP!  <B.5"B.5T@A ! M A$! L )@! 1! ) ! )H! )(! 1! P@ Ak"$ B7A#! L 1E@A! K E@A! J B! @A$! H B.7 B|B> Ak"$ B7A#! G B|5B|!B.5 BX@A$! F  )X7 B| > Ak"$ B7A#! E )HPP@A"! D B! E@A ! B ) ! )X)!B S@A! @ Aj"$ Aj"$A  7 B.7 B| 7 Ak"$ B7A#! > 1"@A ! = )XB7A! ;  )X) ) }7 Ak"$ B7A#! ; A! 9 )H$ )H) Ak"$ B7Av!A #! 9 1!A! 7 )X! )@! 1! ) ! )(!A*! 6 )X! )@! 1! ! !  7  7( P@ Ak"$ B7A#! 6 1E@A! 5 E@A! 4 B! @A! 2 B.)P@A! 1  )"PPE@A! 0 )"PE@A! / B! P@A! - P@A! ,  7 B| 7 Ak"$ B7A#! + )X)!B W@A Ak"$ B7A#!  B|5B|"BB.5TE@A!   )07 B| > Ak"$ B7A#!  )!A!  )0)! B| ) |7 A(j"$ Aj"$A  7 B| 7 Ak"$ B7A#!  )0)!B W@A!  )!A!  B.7 B| 7 Ak"$ B7A#!  )0)! )0B7 ) |!A!   7 Ak"$ B7A#!  B|)"P@A$!  )! )0! )8! ! ) !A! B7 B|B7 Ak"$ B7A#!  )07 Ak"$ B7A#! B|)!A"! B|!  7 BTE@A1! B| B|)!A!  B!A!   7 Ak"$ B7A#!  ) ! )! )0! )8!A !   7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )h! )`! )X!B!  TE@A!   B|1! BPE@A!  B|!A!  B!A!  B|! BB! B|! BSE@A!   TE@A!  BBPPE@A!   |"P@ Ak"$ B7A#!  )"PPE@A!  7@  78  7H  >4  7 B| 7 B| 7 Ak"$ B7A#! B|)! B(|)! B |)! PP@A! )xPPE@A! )x) )8XE@A! )8 )x)T@A!  )h! )`! )X! )H! 54! )@!A!   )x7 B| )87 B|B< Ak"$ B7A#!  A!   7 B| )X7 B| )H7 B| 7 B | )p7 B(| 7 Ak"$ B7A#!  A!  Aj"$ Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m  !!"###$$%%%%&''((()*+,,-../0012 #(M@ Ak"$ B7A#! 4 Ak"$BȊ))"P@ Ak"$ B7A#! 4  )B!BBT! B "BTE@A! 3  B|)"PPE@A! 2  )BB|! )BB! B|! B| )B BB|)"P@ Ak"$ B7A#! 1 P@ Ak"$ B7A#! 1  )h"P@A! 0 B TE@A! /  ) )QE@A! . 1b! BBPP@A! -  7@  7`  >4  >0  7h )B|! )!A! + B|!   )h )|TE@A3! * P@ Ak"$ B7A#! *  )" PPE@A2! ) )" BQE@A.! ( B! @A! &  7H  7 B| 7 Ak"$ B7A#! % )H! 54! )h! 50! )`! )@! )!A! # BTE@A! # B| B| 7 )B|7B! A)! ! B! A)!  )h )| )}!B B T!  7P )!B! A9!  B|! TE@A!   7X PPE@A?!   BBTE@A!  B|! P@ Ak"$ B7A#!   1! BB! B B BT" BBP@A!  BBP@A7!  |" P@ Ak"$ BȀ7A#!  )" PPE@A7!   }XE@A7!   7p  ><  >8  7x  7 B| 7 B| 7 Ak"$ B̀7A#!  B|)! B(|)! B |)! PP@A!  )P! 5 B | > B| 7 Ak"$ B܀7A#! B$|5! B |5! B(|)! B|)! )P! )! )! )X! A?! )P@ Ak"$ B߀7A#! ) )) |7 Aj"$ Aj"$A )!A3! )!A5!  B!B!B!B!A !   7 B|B7 Ak"$ B怰7A#!  B7 B|B7 Ak"$ B逰7A#!   7 B|B7 Ak"$ B쀰7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2  #(M@ Ak"$ B7A#!  Ak"$ )P! )X!B!A!  B|!  TE@A/!  PPE@A !   B|1! PE@A !  B?PP@A0!  B8|!A!  BB!  B BTBBP@A!   78  )H|"P@ Ak"$ B7A#!  )! )hPPE@A!  )h) XE@A!   )h)T@A-!   70  7 Ak"$ B7A#!  B|)"PPE@A,!   )! )0 }! PE@A%!  B!   )0T@A#!  )H!  B|1! B!B B BT B! PE@A#!  )P! )X! )8!A!   )h~ |7 B| )H7 B| )87 B| 7 B | )`7 B(| 7 Ak"$ B7A#! )P! )X! )8!A! 3^PPE@A)! 1e!  B BT!A!  1e!  ! BT! B  3\~! 1f!  B BT!A!  )P! )X! )8!A!   )h7 B| 7 B|B< Ak"$ B7A#!  )P! )X! )8!A!  Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A0k"$  )87 B|B7 B|B7 Ak"$ B7A#!  )! ) ! )(! PP@A!  A0j"$ Aj"$A #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!    7 B|B7 B|B7 B| 7 B | B-|7 B(| 7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ e  !"#$%&'()***+,-. #(M@ Ak"$ B7A#! 0 A8k"$ )@BPP@A! / )XP@ Ak"$ B7A#! /  )X)P! )hB! )hB!B ! BT! B !  |!B.1@A! . BB.4SE@A! -  )h )X)0TE@A! ,  1 "BPP@A! +   7 B| < Ak"$ B7A#! * BȊ))"P@ Ak"$ B7A#! *  )X)! B"BTE@A! )  B|)"P@ Ak"$ B7A#! ) B| BB?|"1! B B!B B BT!  BP@A! ( )X1b! BBPP@A>! ' )`P@ Ak"$ B7A#! '  )`)"PPE@A=! & )"BQE@A9! % B! E@A6! # A8j"$ Aj"$A  )`7 B| )@7 Ak"$ B7A#! ! A4!  BTE@A!  B| B| )@7  )B|7B!A3!  B!A3!  )`P@ Ak"$ B7A#!  )` )`) )X)h|7 A8j"$ Aj"$A  7 B| < Ak"$ B€7A#!  A(!  A8j"$ Aj"$A )X)H!  |1 B! P@A!  A!   )@7 B| )H7 B| )P7 B| 7 B | < B(| )h7 Ak"$ Bˀ7A#!  B0|1E@A*!  A8j"$ Aj"$A  7 B|B7 Ak"$ Bπ7A#!   7 B|B7 Ak"$ BҀ7A#!  Ak"$ BՀ7A#!  B7 B|B7 Ak"$ Bր7A#!   )@7 Ak"$ B׀7A#!  Bԍ7 B|B 7 Ak"$ B؀7A#!  )H7 Ak"$ Bـ7A#! B7 B|B7 Ak"$ Bڀ7A#!  )P7 Ak"$ Bۀ7A#! B7 B|B7 Ak"$ B܀7A#! Ak"$ B݀7A#!  B7 B|B7 B| )H7 B| )P7 Ak"$ Bހ7A#!  B7 B|B7 B| )@7 B|B7 Ak"$ B߀7A#!  #)0"P@ Ak"$ B7A#!  B< Bޤ7 B|B7 Ak"$ B‼7A#!  B7 B|B#7 Ak"$ B䀼7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ l  !"#$%&&&'()*+,-./0123456789:;<<==>?@ABBBCDEFGHIJKLLLLMMNO #(M@ Ak"$ B7A#! Q Ak"$ )xBBBBT!B XE@A! P B!  7( Ak"$ B7A#! N  )h7 B| )p7 Ak"$ B7A#! M B7 B|B7 Ak"$ B7A#! L  )x7 Ak"$ B7A#! K Ak"$ B7A#! J )(PPE@A! I  )()p!  7H )(1b!  < )()h!  7@ )()!  7 Ak"$ B7A#! H B͇7 B|B 7 Ak"$ B7A#! G  )7 Ak"$ B7A#! F B7 B|B 7 Ak"$ B7A#! E  )H7 Ak"$ B7A#! D B7 B|B 7 Ak"$ B7A#! C  17 Ak"$ B7A#! B B7 B|B 7 Ak"$ B7A#! A  )@7 Ak"$ B7A#! @ B7 B|B 7 Ak"$ B7A#! ? Ak"$ B7A#! >  )(B|!  7X  7 Ak"$ B7A #! = B')!B')! B|1" SE@A! <  B|")!  7P )!  78 Ak"$ B7A#! ;  )P7 B| )87 Ak"$ B7A#! : Ak"$ B7A#! 9 Ak"$ B7A#! 8 )()h!  7   )X7 Ak"$ B7A #! 6 B|1BQE@A! 5 ) PE@A! 4 )B|!  7 )!B!B!A3! 1 B|!  TE@A! 0 BT@A8! / B| TE@A7! .  B|T@A8! - B!A1! +  70 @A! +  )x|"P@ Ak"$ B7A#! + )!  7H Ak"$ B7A#! * B7 B|B7 Ak"$ B7A#! )  )h7 B| )p7 Ak"$ B7A#! ( B7 B|B7 Ak"$ B7A#! '  )07 Ak"$ B7A#! & B7 B|B7 Ak"$ B7A#! %  )H7 Ak"$ B€7A#! $ Ak"$ BÀ7A#! # )0 )Q@A! " Ak"$ Bŀ7A#! ! Ak"$ Bƀ7A#! Ak"$ Bǀ7A#!  ) ! )! )0!B!A1!  Ak"$ Bɀ7A#!  B7 B|B7 Ak"$ Bʀ7A#!  Ak"$ Bˀ7A#!  A!  Ak"$ B̀7A#!  B7 B|B7 Ak"$ B΀7A#!  Ak"$ Bπ7A#!  )0!A9!  @A!  Aj"$ Aj"$A Ak"$ BՀ7A#!  B7 B|B7 Ak"$ Bր7A#!  Ak"$ B׀7A#!  A!  ) !A/!   < Ak"$ B܀7A#!  B7 B|B7 Ak"$ B݀7A#!  17 Ak"$ Bހ7A#! B7 B|B7 Ak"$ B߀7A#! Ak"$ B7A#! A(!  Ak"$ B7A#!  Bȁ7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  Aj"$ Aj"$A BȊ))"P@ Ak"$ B7A#!   B|)"PPE@A!  B| )xB BB|)!A!  B!A!  A ~#!@@@@@@@@@@@@@@@@@@@ <  Ak"$B.1@A9!  )P@ Ak"$ Bć7A#!   ) ))}! PE@A/!  B!  ))P! B!   |7 B!B ! BT! B| B < Ak"$ Bć7A#!  BȊ))"P@ Ak"$ Bć7A#!   ))! B"BTE@A6!  B|)"P@ Ak"$ Bć7A#! B| BB?|"1! B B!B B BT!  BP@A-! #)0"P@ Ak"$ Bć7A#! )"P@ Ak"$ Bć7A#!   )- )(|7-  )- )0|7- Aj"$ Aj"$A  7 B| < Ak"$ Bć7A#! A%!  )3^PPE@A3!  )1e!  B BT!A !  )1e!  ! BT! B  )3\~! )1f!  B BT!A !   7 B|B7 Ak"$ Bć7A#!  B7 B|B,7 Ak"$ Bć7A#!   A ~#!@@@@@@@@@@@@@   #(M@ Ak"$ Bȇ7A#! Ak"$B')!B')!B SE@A !  7@B!A! B|! )"P@ Ak"$ Bȇ7A#! )@"PPE@A!  )"PE@A !  B|" S@A!  Aj"$ Aj"$A  78  7P  7H  70  7 B|B7 B|B7 Ak"$ Bȇ7A#!  B(|)! B |)!  )0)7 B|B7 B|B7 B| 7 B | )HB-|7 B(| 7 Ak"$ Bȇ7A#!  )@! )P! )8!A!  A ~#!@@@@@  #(M@ Ak"$ Ḃ7A#!  Ak"$ B/7 Ak"$ Ḃ7A#!  )!  7 B/7 Ak"$ Ḃ7A#!  )! B | ) }7 Aj"$ Aj"$A A ~#!@@@@@@@@@@@   #(M@ Ak"$ BЇ7A#! A0k"$B/)P@A! B/7 Ak"$ BЇ7A#!  B|)!  7(B/)!  7 B/)!  7B.)!  7 Ak"$ BЇ7A#!  ) )( ) #!"B | )|B|B )}! )" XE@A!  B)B7 A0j"$ Aj"$A  }B.)T@A!  B) 7 A0j"$ Aj"$A B)B7 A0j"$ Aj"$A A ~#!@@@@@@@@@@@  #(M@ Ak"$ Bԇ7A#! Ak"$ B'7 Ak"$ Bԇ7A#! B'1@A !   B'7 Ak"$ Bԇ7A#!  Aj"$ Aj"$A B'7 B|B> Ak"$ Bԇ7A"#!   B')7 Ak"$ Bԇ7A#!  B'B< B|B7B')"P@ Ak"$ Bԇ7A#!   B7 B| 7  B|7 Ak"$ Bԇ7A#!  A!  A ~#!@@@@@@@@  #(M@ Ak"$ B؇7A#!  Ak"$ B'7 Ak"$ B؇7A#!   Ak"$ B؇7A#!  B')"P@ Ak"$ B؇7A#!   )!  7@ )! )! ) ! )(! )0!  7 B|  )P|7 B| 7 B| 7 B | 7 B(| 7 B0| 7 Ak"$ B؇7A#!  B'B< B7 B|B'7 B|B< B|B< B|B7 Ak"$ B؇7A#!   Ak"$ B؇7A#!  )! B|  )@}7 Aj"$ Aj"$A A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 0  #(M@ Ak"$ B܇7A#! Ak"$B.5P"P@A*!  B'#7  B'7 Ak"$ B܇7A#!  B'B< B7 Ak"$ B܇7A#!  B.5P! B|)! P@A'!  B' 7 B7  )X7 B|B7 Ak"$ B܇7A#!   B7 B|B'7 B|B < B|B< B|B7 Ak"$ B܇7A#!  D{Gz?!  9 B(|B7 B0|D9 B8|B7 B|B7 B|B7 B8|BԖ7 B| B(|7 B| B0|7  B8|7 Ak"$ B܇7A#!  B(|)"P@A!  B.)!  T@A.!  B0|+DeE@A!  PPE@A,!  B0|  D@9 B0|+! B0| 9DcA cE@A!  B0|DcA9  + D{Gz? B0|+D{Gz?#!7 Ak"$ B܇7A#!  B|)! B0|+"  "DcE@A! D! D? + D?!A!  B'7 Ak"$ B܇7A#! B'B< B7 B|B'7 B|B < B|B< B|B7 Ak"$ B܇7A#! + !A!  B'  Ak"$ B܇7#! B|B Ak"$ B܇7#!A !  B'# Ak"$ B܇7#!A!  Ak"$ B܇7A#!  B7 B|B.7 Ak"$ B܇7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )XP@ Ak"$ B7A#!  B!B!B!B!A!   >D  )X7 B| 7 B| 7 B| )` }7 B | 1h< Ak"$ B7A#!  )H B(|)|! B8|)! B0|)! 5D!  7H  )`TE@A!    TE@A!    }! PE@A!  )X7 Ak"$ B7A#! B|5! B|)! B|)!  TE@A!   }! P@A!  ! )H!A!   )X7 B| 7 B| 7 B| > Ak"$ B7A#!  B| )H7 Aj"$ Aj"$A B!A!  B!A!  !A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ *  #(M@ Ak"$ B7A#! A(k"$ Ak"$ B7A#!  B/7 Ak"$ B7A#!  )!  7 B/7 Ak"$ B7A#!  )!  7 Ak"$ B7A#!  )"PPE@A(!   7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   50B7 Ak"$ B7A#!  Ak"$ B7A#!   )8B 7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   ) B 7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )B~ )7 Ak"$ B7A#!  B΀7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  1@"@A#!  Ak"$ B7A#!  Ak"$ B7A#! Ak"$ B7A#! #)0"P@ Ak"$ B7A#!  0!  B|< BBQ@A! A(j"$ Aj"$A  B.7 Ak"$ B7A#! A!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  A!  Ak"$ B7A#!   A ~#!@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!  BB.4S@A !   )(B|7 )(B|!  7 B| 7 Ak"$ B7A#!  )()! )()!  )7 B|    T7 Ak"$ B7A#!   )( )()B|B~B7 )( )(5B|> )(B7 )(B.)7 )(B')7 A j"$ Aj"$A )(5! )()!  > B| 7 B|B< Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! A8k"$ )@P@ Ak"$ B7A#! )@B|!  70 )@)!  7 B| 7 Ak"$ B7A#!  )! )!  TE@A!    }! PE@A !  )@5! B| 7 B| 7 B| > A8j"$ Aj"$A  7(  )07 B~!  7 B| 7 Ak"$ B7A#!  )@5! B| ) 7 B| )(7 B| > A8j"$ Aj"$A B!A !  A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ ) P@ Ak"$ B7A#!  )( )0TE@A!  )0 )(}! PE@A ! Aj"$ Aj"$A 58B ) 5R@A !  )(BP"P@A!   ) B|7 B| )(7 B| )07 Ak"$ B7A#!  Aj"$ Aj"$A B!A !  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m  !"""#$%%&''())*+,- #(AjM@ Ak"$ B7A#! / Ak"$ )P@ Ak"$ B7A#! /  ) )TE@A! .  ) )}! P@A! , )BPP@A! + )B ! )B?PP! B|!B.)B "P! )B|! ))P! ))H! B  !   ! B" TE@A! * B|)! BPPE@A! ) B!  7h  7P  7H  XE@A!! ' ))`"P@ Ak"$ B7A#! '  BTE@A! &    B|7 B| BB 7 B| 7 B| 7 Ak"$ B7A#! % B(|)! B |)! PP@A! $  B|B7 B|B7 B|B7 B|Bؖ7 B| )7 B| )H7 )hB! )! 1!A)! "  )XB! )! 1!   TE@A! !   }! PPE@A!   @A!  B|)!  7 B| 7 B|$  Ak"$ B7Av!A #!  B|)!  7X B|1! 1"@A!  E@A!  ))`"P@ Ak"$ B7A#!   BTE@A!    B|7 B|B7 B| )H7 B| )P7 Ak"$ BÀ7A#!  B(|)! B |)! PPE@A%!   7@  )7 B| )X7 B| 7 B| 7 Ak"$ Bŀ7A#!  B |)! B| )@B 7 B| )7 B| 7 Aj"$ Aj"$A )! B|B7 B| )7 B| 7 Aj"$ Aj"$A  <7 ))!  7 Ak"$ B̀7A#!  )X! 17! 1!A9!   7` ))!  7 Ak"$ Bր7A#!  )`! )!A4!  B!A0!   78  )7 B| )h7 B| 7 B| 7 Ak"$ B܀7A#!  B |)! B| )8B 7 B| )7 B| 7 Aj"$ Aj"$A BB!A! B|B7 B| )7 B| )7 Aj"$ Aj"$A B!A !  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!  B7 B|B-7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A0k"$ )8P@ Ak"$ B7A#!  )8)`"P@ Ak"$ B7A#!   )@BTE@A!    )@B|B|7 B| )H7 B| )P7 Ak"$ B7A#!   )@B )HB |!  )8)TE@A! )8 7 )81E@A! B| 7 A0j"$ Aj"$A  7 B/7 )PB !  7 B| 7 Ak"$ B7A#! B/7 Ak"$ B7A#!  B|)!  7( P@ Ak"$ B7A#!   7 B|B )}7 Ak"$ B7A#!   )(B|7 B| )7 Ak"$ B7A#!  B/7 Ak"$ B7A#!  B| ) 7 A0j"$ Aj"$A  )@7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@ &  #(M@ Ak"$ B7A#!  Ak"$ ) BXE@A!  ) BQ@A!  ) BQE@A !   )BժժժժBժժժժ| )BժժժժB! ) B|! B(|   B BT} B7 Aj"$ Aj"$A ) BQE@A$!   )BݻݻBݻݻ| )BݻݻB!A! B(| )7 Aj"$ Aj"$A ) BXE@A! ) BQE@A!  )B߿B߿| )B߿B!A! ) BQE@A$!  )BB| )BB!A!  ) B QE@A!   )BB| )BB!A!  ) BQE@A$!   )BB| )BB!A!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )0 )0B|PP@A!  )0P@A!  B )0T@A !  B!A!  )B|! B WE@A !   7 )(P@ Ak"$ B7A#!  )(B|! B!   |) )( |)7 B| )07 Ak"$ B7A#!  B|)BRE@A!  B8|B< A j"$ Aj"$A B8|B< A j"$ Aj"$A Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )07 Ak"$ B7A#! Ak"$ B7A#! Ak"$ B7A#! BГ7 B|B 7 Ak"$ B7A#! Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )07 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B!7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ M  !!!!"#$%&'()*+,-./01234 #(M@ Ak"$ B7A#! 6 Ak"$ )`B| )`PP@A! 5 )`P@A! 4 B )`T@A?! 3 )hPE@A7! 2 )`!  78 )XB! )P! )`!A! / )(B|! )P! )`! )8!  7(B WE@A! . P@ Ak"$ B7A#! . BTE@A=! - B|! B!   |)  |)7 B| 7 Ak"$ B7A#! , B|)BRE@A! + )(BS@A6! * )PP@ Ak"$ B7A#! * )(BTE@A;! ) )PB|!  7@ )(B!  )P |)  |)7 B| )`7 Ak"$ B7A#! ( B|)! B! )(B!B y}!  |!B }!  ! BT! B "PPE@A,! ' BB y}}! )8  )8 T!  }"B@|B|!B.)!B TE@A+! % B.) TE@A+! $ B@|B|! B !  |B@|B|B|! B }!  XE@A*! #   }B@|B|!  !  XE@A)! "   }|B@|B|! B| 7 B| 7 Aj"$ Aj"$A !A(!  !A(!  !A(!   70B }! )(B|!A.!  ) B|! )0! ! ! B WE@A4!   7  7 B!  )P |) )@ |)7 B| )`7 Ak"$ B7A#!  B|)! )B y}}B|! PPE@A-!  )0! !A!  !A2!  B|B7 B|B7 Aj"$ Aj"$A  )h )`|B|B )`}!A!   )(7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )`7 Ak"$ B€7A#! Ak"$ BÀ7A#! Ak"$ BĀ7A#! BГ7 B|B 7 Ak"$ Bŀ7A#! Ak"$ Bǀ7A#!  B7 B|B7 Ak"$ BȀ7A#!   )`7 Ak"$ Bɀ7A#!  Ak"$ Bʀ7A#!  Ak"$ Bˀ7A#!  B7 B|B!7 Ak"$ B̀7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!  )( ) )T@A!  ) ) )(X@A!  ) B|! 10"E@A!  ) B|!  7 )"PPE@A!  )BQE@A!  ) )"PPE@A!  ) B7 P@ Ak"$ B7A#! B7  )7  7 P@ Ak"$ B7A#! )"BTE@A! B | B| )(7  )B|7 Aj"$ Aj"$A Ak"$ B7A#! )! )!A !  Ak"$ B7A#!  )"P@ Ak"$ B7A#!  B7 B7 ) 7A!   7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ B8|B7 B8|B7 )PP@ Ak"$ B7A#!  B8| )PB|7 )PB|!  70 B| 7 )P! B8|!B!A!  B|! )"P@ Ak"$ B7A#!  )"PPE@A !  )PE@A!  )"PP@A!   7 )!  7 PP@A! B|"BS@A! )"PP@A! B|B7 B|B< Aj"$ Aj"$A  7 Ak"$ B7A#! )PB7A !  P@ Ak"$ B7A#!  )B|!  7  Q! BTE@A!  B | B|)! B| 7 B| < Aj"$ Aj"$A  7  7(  7  7  7 Ak"$ B7A#!  )0! )P! )(! )! ) ! )!A !   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!  ) )"P"PE@A!  P@ Ak"$ B7A#!  )!B SE@A !  )( ) )}! B|"B?TE@A!  B | B|! B 5 5|BT@A!  B?Q@A!  P@ Ak"$ B7A#!  )"B?TE@A!  B|7 B | B|" )( ) )}> )0P@ Ak"$ B7A#!  )0)>  )07 ) ) )B|7 Aj"$ Aj"$A  7 Ak"$ B7A#! )"P@ Ak"$ B7A#! B7 ) 7 ) 7A ! Ak"$ B7A#! )"P@ Ak"$ B7A#! B7 ) 7 ) 7A!   7 B|B?7 Ak"$ B7A#!  B7 B|B)7 Ak"$ B7A#!   7 B|B?7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ )`P@A !  )P7 B| )X7 )` )`B?|B!  7@ B| 7 Ak"$ B7A#! B |)"P@ Ak"$ B7A#! B(|)! B|)! B?TE@A !  70 B | B|!  78 B|! B>QE@A ! )!B!  7 B| 7 B| )` )@}B|7 Ak"$ B7A#!  B(|)! B |)! B|)! )8 )07 )8 7 B| )87 B| 7 B| 7 Aj"$ Aj"$A !A!  B|B7 B| )P7 B| )X7 Aj"$ Aj"$A  7 B|B?7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$  ) 7 Ak"$ B7A#! 5!A !    ) 7 Ak"$ B7A#!  B|5! B 5(BTE@A!   ) 7 B| > B | 5(> Ak"$ B7A$#!  B|1E@A!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@ '  #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!  )(5!  > B'7 Ak"$ B7A#!  5! 5! )(!A !  5B|! 5! )(! B"BTE@A!   BB"BTE@A$!   > B| B~|! BBPE@A!  B BB}B"BTE@A!!   B| B(~|7 Ak"$ B7A#!  B|)! PPE@A!   7 B'7 B| 5> Ak"$ B7A#!  B0| )7 A j"$ Aj"$A B BB}B"BTE@A!  B| B(~|7 Ak"$ B7A#! B|)!A! B'7 B|B> Ak"$ B7A#! B0|B7 A j"$ Aj"$A  7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A0k"$A!  B'B'5B|> Ak"$ B7A#!  )BR@A! B؉)5!  >B!A !  7B) B~|!  7( B|! B(~!  7   |7 Ak"$ B7A#!   )(B| ) |7 Ak"$ B7A#! )B|! 5! BSE@A!  B BB}B"BT@A!  A!  Ak"$ B7A#!  Ak"$ B7A#!  A0j"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@ )  #(M@ Ak"$ B7A#!  A k"$B.5P"P@A'!  B'#7  B'7 Ak"$ B7A#!  B'B<  )(7 B|B7 Ak"$ B7A#!   B7 B|B'7 B|B < B|B< B|B7 Ak"$ B7A#!  A!  B'B'5B|> Ak"$ B7A#!  Ak"$ B7A#!  )BR@A! A! Ak"$ B7A#! B< Ak"$ B7A#! B|1@A!  B'7 Ak"$ B7A#!  B܉)5PPE@A!!  B'B< B7 B|B'7 B|B < B|B< B|B7 Ak"$ B7A#!  A!   B'7 Ak"$ B7A#!  A!  B'# Ak"$ B7#!A!  A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ M  !"#$%&'()*+,-.....///0123456789: #(M@ Ak"$ B7A#! < Ak"$ #7X#)0"P@ Ak"$ B7A#! < B)+!  9(  4B|> B܉)7 Ak"$ B7A#! ; 5PP@A;! : B)7 B|B> Ak"$ B7A#! 9 B؉)5!  > B)7 Ak"$ B7A#! 7 B|)!  70 PP!  < E@A9! 6   B|7 Ak"$ B7A #! 5 B|1"BRE@A! 4 )05X" 5 Q@A! 3 5 B|B Q@A! 2 A! 1 )05X! 5 B~|!  BQ@A7! 1 B! E@A! / 1"@A2! . B!  78 B)7 B|B> Ak"$ B7A#! , B|5P@A0! + B! @A! ) )XP@ Ak"$ B7A#! ) )X)0"P@ Ak"$ B7A#! )  4B|> B| )87 Aj"$ Aj"$A B7 Ak"$ B7A#! '  B'7 B|B> Ak"$ B7A"#! & BB.4SE@A! % B/)!  7PB))!  7HB))!  7@ Ak"$ B7A#! $ B7 B|B7 Ak"$ B7A#! #  )PB7 Ak"$ B7A#! " BΕ7 B|B7 Ak"$ B7A#! !  )P )H}B7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!   )@7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   +(9 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  A!  B܉)7 Ak"$ B7A#!  B|5PP!A!  )0) !  78  )07 B|B< Ak"$ B7A#!  B|1@A5!  B!A!  B)7 B| )87 Ak"$ B7A#!  )8!A!   )0B|7 B| > B | 5 B|> Ak"$ B7A$#!  B|1!A!  B܉)7 B|B> Ak"$ B7A"#!  A! )XP@ Ak"$ B7A#!  )X)0"P@ Ak"$ B7A#!   4B|> B|B7 Aj"$ Aj"$A  <  >$ Ak"$ BÀ7A#! B7 B|B7 Ak"$ BĀ7A#!  17 Ak"$ Bŀ7A#! B7 B|B 7 Ak"$ Bƀ7A#!  5$7 Ak"$ Bǀ7A#!  B7 B|B 7 Ak"$ BȀ7A#!   5 7 Ak"$ Bɀ7A#!  Ak"$ Bʀ7A#!  Ak"$ Bˀ7A#!  B7 B|B7 Ak"$ B̀7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A0k"$#)0"P@ Ak"$ B7A#!  4"B"PE@A!  4BPE@A!  # )R@A!  )8P@ Ak"$ B7A#!  B؉)5!  > )8B|!  7(  7 Ak"$ B7A#!  B|5" 5QE@A !  A0j"$ Aj"$A 5B|B!  Q@A !   7  )(7 B| 5B~|> B | 5B|> Ak"$ B7A$#! B|1@A! A! Ak"$ B7A#!  )(7 Ak"$ B7A#! B|5" 5Q@A!   ) QE@A!  A0j"$ Aj"$A  )87 B|B< Ak"$ B7A#!  A0j"$ Aj"$A B7 B|B"7 Ak"$ B7A#!   A 8 ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#######$$$%&&&&&&&&'''()))*+,,--../000112334444555555677777789::;;;;;;<===>>>>>????@AABBBCCCCCCCCDEFFFGGHIJKLLLLMNOPQRSSTUVVWXYZZZ[[[\]^_`abcdddefghijkklmnnnopqqqqqrstuvwxyz{|}~ #(AjM@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  4"BPE@A!  4BPE@A!  # )R@A!  )P@ Ak"$ B7A#!  B؉)5!  >  )B|!  7  7 Ak"$ B7A #!  B|1!  < BR@A!  )5X! 5 B|B" R@A!   7B*1@A!  )) ! B)7 B| 7 Ak"$ B7A#!  )1b!  < ))h!  7H )B|! ))!  78 )! !A!  ! ! ! PPE@A=!  3! PPE@A!   " ~!  )P!  B! B!B B BT! |" 1!  BPPE@A!  )!A!  |! ! A!!  )! PPE@A B|!  T@A! t A! s  7`  70  7 B| 7 Ak"$ B뀰7A#! s 1! )H! )`! )0! )!A! q B|)"P@ Ak"$ B񀰈7A#! r B|1!  1BPP@A! q A! p )0" )8TE@A! p )P! B!  |1! )H |1B ! B! B!  B BTBP! P@A! o B|!A! m B|!   )8B|BTE@A! l )P |1!  )H |1B! BP! PE@A! k  7X  7 Ak"$ B7A#! j 1! )H! )X! )!A! h  )8"B|"B!B!B!A! g B|! )P |)!   {|! !  T@A! f 3`" }!  = B!  7p  T@A! e  7(  =` B70B*1E@A! d #)0"P@ Ak"$ B7A#! d )"P@ Ak"$ B7A#! d   )h B~ ),|7,  )P7H  )87 Ak"$ B7A#! b ) B|)7P  )7 B|B7 Ak"$ B7A#! a   )7 Ak"$ B7A #! ` B|1!  < BR@A! _ )5X! ) R@A! ^ 5 B|B Q@A! ] 5 B|B Q@A! \  )B|7 B| 5 > Ak"$ B7A"#! [  1B"BPPE@A! Z 3BPP@A! Y 1"E@A! X B|B< Aj"$ Aj"$A )pP@A! V )(B ))8QE@A! U 1BTE@A! T B) 1B~|!  B| 5 BBBB(~|7 B| )7 Ak"$ B7A#! S A! Q 1BTE@A! Q B) 1B~|!  B| 5 BBBB(~|7 B| )7 Ak"$ B7A#! P A! N B)7 B| )7 Ak"$ B7A#! N B|B< Aj"$ Aj"$A  < )B<d B/7 Ak"$ B7A#! L B|)"P@ Ak"$ BÁ7A#! L 1B8B8"BTE@A! K  B| B|7 B| 3B7 Ak"$ BƁ7A#! J B/7 Ak"$ Bǁ7A#! I A! G 1"@A! G 3BPPE@A! F BB.4SE@A! E )B7p B/7 Ak"$ B́7A#! C B|)!  7 P@ Ak"$ B΁7A#! C  B|7 B|B7 Ak"$ BЁ7A#! B  )B|7 B| )H7 Ak"$ Bс7A#! A B/7 Ak"$ Bҁ7A#! @ B|B< Aj"$ Aj"$A B)7 B| )7 Ak"$ Bԁ7A#! > A! < 1BTE@A! < B) 1B~|!  B| 5 BBBB(~|7 B| )7 Ak"$ B؁7A#! ; A! 9  7P  7 Ak"$ Bځ7A#! 9 1! )H! )! )P!A! 7 B.4BPP@A! 7 A! 6  7 Ak"$ B߁7A#! 6 1! )H! )!A?! 4  )) B 7 Ak"$ B偰7A#! 4 A! 2  17 B|B7 Ak"$ B灰7A#! 2  17 B|B7 Ak"$ B遰7A#! 0  17 B|B7 Ak"$ B끰7A#! .  7 B|B7 Ak"$ B큰7A#! , Bߠ7 B|B7 Ak"$ B7A#! * )5X!  >$ Ak"$ B񁰈7A#! ( B7 B|B7 Ak"$ B򁰈7A#! '  17 Ak"$ B󁰈7A#! & B7 B|B 7 Ak"$ B􁰈7A#! %  5$7 Ak"$ B7A#! $ Bؙ7 B|B7 Ak"$ B7A#! #  5 7 Ak"$ B7A#! " Ak"$ B7A#! ! Ak"$ B7A#! B7 B|B'7 Ak"$ B7A#!   7@  = Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )@7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )p7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   37 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   3B7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  )5X!  >$ Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  17 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  5$7 Ak"$ B7A#! Bؙ7 B|B7 Ak"$ B7A#!  5 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ b  !"###$%%%&'(())*+,-./01234567888888889:; #(M@ Ak"$ B7A#! = Ak"$ Ak"$ B7A#! < )P@ Ak"$ B7A#! < ))h!  7@ ))0!  78 Ak"$ B7A#! ; B7 B|B$7 Ak"$ B7A#! :  )7 Ak"$ B7A#! 9 B7 B|B 7 Ak"$ B7A#! 8  )@7 Ak"$ B7A#! 7 B7 B|B 7 Ak"$ B7A#! 6  )87 Ak"$ B7A#! 5 B7 B|B.7 Ak"$ B7A#! 4 Ak"$ B7A#! 3  ))P! B| 7 B|B< B|B7 ))H! B| 7 B|B< B|B7B!A! 1 B| B|)B|7 )(B|!  ))8TE@A! 0  7( ))h!  7@ ))!  7 Ak"$ B7A#! / )( )@~ ) |!  70  7 Ak"$ B7A#! . Ak"$ B7A#! - )( ))0TE@A! , B!  < E@A! * Ak"$ B7A#! ) B7 B|B7 Ak"$ B7A#! ( Ak"$ B7A#! ' B|)"P@ Ak"$ B7A#! & B|1!  1"BPPE@A! % Ak"$ B7A#! $ Bބ7 B|B 7 Ak"$ B7A#! # Ak"$ B7A#! " B|)"P@ Ak"$ B7A#! ! B|1!  1BPPE@A! 1P!  < @A!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  1"@A!   B|1"BQE@A!  B| B|)B|7 B|B< B| B|)B|7 B|1"BQE@A!  B| B|)B|7 B|B<A!  B| B<A!  B| B<A!  ))h!  )07 B|B B T )0|7 B|B7 Ak"$ Bʀ7A#!  A=!  Ak"$ B̀7A#!  Bρ7 B|B7 Ak"$ B̀7A#!  Ak"$ B΀7A#!  A9! B!A8! Ak"$ Bр7A#! B7 B|B 7 Ak"$ BҀ7A#! Ak"$ BӀ7A#! A1!  Ak"$ BՀ7A#!  B7 B|B7 Ak"$ Bր7A#!  Ak"$ B׀7A#!  A%!  B|)"P@ Ak"$ Bڀ7A#!  B|1!  1BPP!A !  B7 B|B7 Ak"$ Bဴ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$B)+Da@A!  B*1@A!  A !  B)7 Ak"$ B7A#!  B|) )} )SE@A!  Ak"$ B7A#!  )BQ@A !  B)7 Ak"$ B7A#!  B|) )RE@A! B)7 Ak"$ B7A#! B|)!  7 B/7 Ak"$ B7A#! B)+ B|)B))} )(|#! )0}!  7A! B)D9 B*1@A!  A j"$ Aj"$A Ak"$ B7A#!  A!  Ak"$ B7A#!  A !  A j"$ Aj"$A A ~#!@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ Ak"$ B7A#!  )P@ Ak"$ B7A#!  ) )7 Ak"$ B7A#!  )"PPE@A!  ) 7 Aj"$ Aj"$A Ak"$ B7A#!  )!A!  A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!  ) )"P"PE@A!  )BQE@A!  ) )! ) 7 ) 7 P@ Ak"$ B7A#!  )BQ"@A!  P@ Ak"$ B7A#!  )"BTE@A!  B| B| )(7  )B|7 E@A !  B.5BQ@A! Aj"$ Aj"$A B.7 Ak"$ B7A#! A !  <  7 Ak"$ B7A#! ) B< Ak"$ B7A#!  )! ) 7 1!A!  !B!A!   ) 7 Ak"$ B7A#!  ) )!B!A!   7 B|B7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ $  #(M@ Ak"$ BĈ7A#!  A0k"$ )HP@A!  )8P@ Ak"$ BĈ7A#!  )8)"PPE@A!  )P! )@! )H!B!A!   7 Ak"$ BĈ7A#!  )8B< Ak"$ BĈ7A#!  )8)! )! )8 7 )8 7 )P! )@! )H!B! P@ Ak"$ BĈ7A#!  )"BQ@A!  BXE@A"!  B }!    S! B|! B }! B! B? |" R@A!   ) |7 XE@A !   }!B }!  }!  B? B|! B SE@A!   7P  7@  7HA !   7  7(  <  7 B| 7 B| B7 Ak"$ BĈ7A#!  ) ! )P! )@! )H! 1! )(! A! E@A! B.5BQ@A! A0j"$ Aj"$A B.7 Ak"$ BĈ7A#! A!   )87 Ak"$ BĈ7A#!  )8)!A!  A0j"$ Aj"$A  7 B| 7 Ak"$ BĈ7A#!   7 B|B7 Ak"$ BĈ7A#!   A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ BȈ7A#!  A k"$ )(P@ Ak"$ BȈ7A#!  )()"PPE@A!  P@ Ak"$ BȈ7A#!  )PE@A !  )()! )()! )( 7 )( 7 P@ Ak"$ BȈ7A#!  )P@A! P@ Ak"$ BȈ7A#! )B|!  7 BTE@A! B| B|)! B0| 7 A j"$ Aj"$A  7 Ak"$ BȈ7A#! )"PPE@A!  7  )7 Ak"$ BȈ7A#!  )( )7 )!A !  B0|B7 A j"$ Aj"$A  )(7 Ak"$ BȈ7A#!  )()!A!   7 B|B7 Ak"$ BȈ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B̈7A#!  Ak"$ ) P@ Ak"$ B̈7A#!  ) )"P"PE@A !  )PE@A!   7 Ak"$ B̈7A#!  ) B7 ) )"P@ Ak"$ B̈7A#!  )PE@A!   7 Ak"$ B̈7A#!  ) B7 ) )"PP@A! ) )"PP@A! Aj"$ Aj"$A B.7 B| 7 Ak"$ B̈7A#! ) B7A!  B.7 B| 7 Ak"$ B̈7A#!  ) B7A!   7 Ak"$ B̈7A#!  ) B< A !   7 Ak"$ B̈7A#!  ) B< A!  A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ BЈ7A#!  Ak"$ )P@ Ak"$ BЈ7A#!  ))"PPE@A!  ))"P@ Ak"$ BЈ7A#!  )PP@A!  B )SE@A!  7 Ak"$ BЈ7A#! ) B|)7 )B< B.5BQ@A ! Aj"$ Aj"$A B.7 Ak"$ BЈ7A#!  A !  Aj"$ Aj"$A  7 Ak"$ BЈ7A#!  )B< Ak"$ BЈ7A#!  ) )7A !  Aj"$ Aj"$A A #!@@@@@@@  #(M@ Ak"$ BԈ7A#!  Ak"$ )P@ Ak"$ BԈ7A#!  ))P@A!  Aj"$ Aj"$A Bȝ7 B|B7 Ak"$ BԈ7A#!   A #!@@@@@@@  #(M@ Ak"$ B؈7A#!  Ak"$ )P@ Ak"$ B؈7A#!  ))PP@A!  Aj"$ Aj"$A B٪7 B|B7 Ak"$ B؈7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ <  ! #(M@ Ak"$ B܈7A#! # Ak"$B.)PP@A6! " B! PP@A!  7 B|B7B.)PP@A&!  B|)PPE@A!  B!A !  )0! B|"BXE@A!   7  70  7( B|)") |!  78 P@ Ak"$ B܈7A#!  P@ Ak"$ B܈7A#!   B7  7 Ak"$ B܈7A#!  )PE@A!  )8!A!   )87 Ak"$ B܈7A#!  )(!A!  B| 7 Aj"$ Aj"$A B|B7 B|B7 B|B7 B| B|7  B|7 Ak"$ B܈7A#!  B|)PPE@A:!   B.7 Ak"$ B܈7A#!  B|)! B.7 B| 7 Ak"$ B܈7A#!   B.7 Ak"$ B܈7A#!  ) !A!   B.7 Ak"$ B܈7A#!  B.)! B| 7 PP@A3!  B.7 Ak"$ B܈7A#! ) !A! B.7 B| 7 Ak"$ B܈7A#! B|)! B.7 B| 7 Ak"$ B܈7A#! A-!  B.7 Ak"$ B܈7A#!  B|)"PPE@A!   78  7 Ak"$ B܈7A#!  )8!A!  B7 B|B 7 Ak"$ B܈7A#!   A #!@@@@@  #(M@ Ak"$ B7A#!  Ak"$  )7 Ak"$ B7A#!  )P@ Ak"$ B7A#!  B.7 B| )7 Ak"$ B7A#!  Aj"$ Aj"$A A #!@@@@@  #(M@ Ak"$ B7A#!  Ak"$  )7 Ak"$ B7A#!  )P@ Ak"$ B7A#!  B.7 B| )7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#!  )"PPE@A!   7  7 Ak"$ B7A#!  B | )7 Aj"$ Aj"$A B | 7 Aj"$ Aj"$A A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! A k"$ Ak"$ B7A#!  )(P@ Ak"$ B7A#!  )! )()" B?|B! )(  }7 P@ Ak"$ B7A#!   7 )()"BTE@A !   7  B|7 )(B|! B|  B|7 B| B7 Ak"$ B7A#!   )(7 Ak"$ B7A#!  B0| )7 A j"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@ $ #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#!  B.)PP@A!! B.B7B.)"PP@A ! A! B.7 )! PP@A ! B.)"PPE@A! B.)"P@ Ak"$ B7A#!  7B.)"P@ Ak"$ B7A#! B.)7B.B.)7 B.B7B.B7  B.7 Ak"$ B7A#!  Aj"$ Aj"$A B.)p!B.B.)7B. 7A!  B7 B|B(7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$ B.7 Ak"$ B7A#! B.5PPE@A !   B.7 Ak"$ B7A#!  B0|B< A j"$ Aj"$A B.)PPE@A!  B|B7 B|B< B|B7 B| 1(<  B|7 Ak"$ B7A#!  B.)!  7 B.7 Ak"$ B7A#!  B0| )PP< A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A8k"$ )@P@ Ak"$ B7A#!  )@)" )@)W@A!  )@)! )@)! B|" XE@A!  )@ 7 )@)!  TE@A!   B| )H7 A8j"$ Aj"$A B |B7 B |B7 B |B7 B~" B?|B"BB S!  7  B7 B|B/7 Ak"$ B7A#!  B|)! B | 7 PPE@A!  )@)! B(| 7 B0| )7 )@)! )@)!B SE@A !   S! B |)!   !  R@A! )@)! )@)! B |)! B |)! )@ B |)7 )@ 7 )@ 7 PPE@A!  B/7 B|B B}7 Ak"$ B7A#! A!  7 B| 7 B| B7 Ak"$ B7A#! A !   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@  Ak"$ ) BBBBT!B XE@A! B! PPE@A !  ) )TE@A ! B(|B< Aj"$ Aj"$A  7  B|7 Ak"$ B7A #!  B|1B|BBXE@A!  ))p! B(| ) T< Aj"$ Aj"$A B(|B< Aj"$ Aj"$A BȊ))"P@ Ak"$ B7A#!   B|)"PPE@A!  B| ) B BB|)!A!  B!A!  A ~#!@@@@@@@@@@@@@@@@@ !  Ak"$ ) BBBBT!B XE@A!  B! PP@A! B! E@A ! B(|B7 Aj"$ Aj"$A P@ Ak"$ B7A#!  ) )T@A ! )p ) X@A !  B(| 7 Aj"$ Aj"$A  7  B|7 Ak"$ B7A #!  B|1BR! )!A !  BȊ))"P@ Ak"$ B7A#!   B|)"PPE@A !  B| ) B BB|)!A!  B!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ H  !!!"""# #(M@ Ak"$ B7A#! % Ak"$ ) P@ Ak"$ B7A#! %  ) B7B.5PP@A! $ ) B7 ) ) 7 ) B7 ) B7 ) B> ) B7B.5PP@A! " ) BȾ/7 ) B< ) B 7B.5PP@A?! ) B7 ) B7 ) B7 ) B7 ) B> ) B7B.5PP@A ) B7B.5PP@A6!  ) B/7 ) B< ) B7B.5PP@A3!  ) B7 ) B7 ) B7 ) B7 ) B> ) B7B.5PP@A0!  ) B/7 ) B< ) B7B.5PP@A-!  ) B7 ) B7 ) B7 ) B7 ) B> ) B7B.5PP@A*!  ) B/7 ) B< ) B< ) !B!A'!  B| B~|!  < B|! BS@A#!   B|7 B| 7 B|B/7 Ak"$ B7A#! Aj"$ Aj"$A ) B|B/ Ak"$ B7#!A!! ) B|B Ak"$ B7#! ) B|B Ak"$ B7#!A! ) B|B/ Ak"$ B7#!A!  ) B|B Ak"$ B7#! ) B|B Ak"$ B7#!A!  ) B|B/ Ak"$ B7#!A!  ) B|B Ak"$ B7#! ) B|B Ak"$ B7#!A!  ) B|Bؾ/ Ak"$ B7#!A!  ) B|B Ak"$ B7#! ) B|B Ak"$ B7#!A !  ) B|BȾ/ Ak"$ BĀ7#!A !  ) B|B Ak"$ Bƀ7#! ) B| ) Ak"$ Bǀ7#!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ F  !"""#$ #(AjM@ Ak"$ B7A#! & Ak"$ )P@ Ak"$ B7A#! & )B|!  7  7 Ak"$ B7A#! % B )X@A! $ #)0"P@ Ak"$ B7A#! $   4B|>#P@ Ak"$ B7A#! $ #)0!  7hB*1@A! # ))!  7H ))!  7p ))!  7P )!B!A! ! )p! )P! ! ! )h! )H!  <? PPE@A*!  7@ )B|!  7x  7 Ak"$ B7A'#!  B|)"PPE@A!   )x7 )@  )@ T!  7` B|  }7 Ak"$ B7A%#!  )@ )`} )@ B|1! 1?!A!   )7 B|B7 Ak"$ B7A#!  B|)B||! )H B X@A'!  1?"E@A !   )7 B| )p7 B| )H7 B| )P7 B | 7 B(|B7 Ak"$ B7A#!  B0|)" )@XE@A!  )@ }! B!A!   )x7 B|  )@}7 Ak"$ B7A#!  B!A!   7X  )7 Ak"$ B7A#!  )X!A!   )7 B|B7 Ak"$ B7A##!  1?"@A;!  B*1@A8! )hP@ Ak"$ B7A#!  )h4! )h B|> BBQE@A6! #P@ Ak"$ B7A#! #1E@A6! #Bu7 Aj"$ Aj"$A Ak"$ B7A#!  A,!    )7 Ak"$ B7A#!  A+!  Ak"$ B€7A#!  )h!A!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ O  !"#$$$$%%&&&&'''(()*+,-./012 #(A jM@ Ak"$ B7A#! 4 Ak"$ )P@ Ak"$ B7A#! 4 )5!  > )! )! )! )! )! !B! A*! 2 )HB|! )! )! )p! )! )! )! )P! )h! )x! )! )8! ! 5!  SE@A)! 1  7H  70 |!  7  7 Ak"$ B7A #! 0 B|1! )H )8TE@A! / )x )H|!  1B"BPE@A ! . )0!A! ,  7 )0! B!A ! + B|! BTE@A(! * B BPPE@A ! )  )HB )h||"BTE@A! (  7@  7`  < )PB| B|)!  7 P@ Ak"$ B7A#! ( B|!  7  7 Ak"$ B7A#! ' B|5! 5B~|!  BQ@A&! & B! @A! $ 1! )`! )@! ! )!A ! ! ) ) !  7(  )7 Ak"$ B7A#! !  ) 7 B|B< Ak"$ B7A#! B|1!  <  )7 Ak"$ B7A#!   )7 Ak"$ B7A #!  )` )(|! B|1 )1B!  )` 1"! ! !A!   )7 B| > B | 5B|> Ak"$ B7A$#!  B|1!A!  !A!  B" |!  }! PPE@A5!  B " TE@A!  )" P@ Ak"$ B7A#!   B|)" BTE@A!  B|)" P@ Ak"$ B7A#!  B" B" BXE@A!  B|! B }! B }B?! |! B| |! B" TE@A4!   7p  78  7P  7h  7  7x  7B!A!  !A3!  B*1@A7!  B| 7 Aj"$ Aj"$A  7X  7 Ak"$ B7A#!   ) )X}B 7 Ak"$ B7A#!    )7 Ak"$ B7A#! )X! A6!  7 B|B7 Ak"$ Bƀ7A#!  )H7 B| )87 Ak"$ BȀ7A#!  7 B|B7 Ak"$ Bʀ7A#!   7 B|B7 Ak"$ B̀7A#!   7 B| 7 Ak"$ B΀7A#!   A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$ B|B7 B|A#! B|B7 B | )H7 B(| )P7 B0| 1X< B8| B|7  B|7 Ak"$ B7A#! B|)"PPE@A!  1Y"E@A!  1dPP@A !  B|)"P@ Ak"$ B7A#!  B<d B|)! B| 7 Aj"$ Aj"$A )! ) !  7 B| B 7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ 18BPPE@A!   )(7 B| )07 B| 18< B|B< Ak"$ B7A#!  B|)! B| 7 A j"$ Aj"$A B7 B|B<7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  ))"P@ Ak"$ B7A#!  ) B !B BT! B ! ) BBBBT"BTE@A!  B|)! )0! )(! ) ! )!B!A ! P@ Ak"$ B7A#! B| B| 7 B|!  TE@A!  |B" PE@A ! )"P@ Ak"$ B7A#!  B |B" BTE@A!   B|)!A !  Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )P! )X! )`!B!A !  )8! 1'! )0!  TE@A !   )@7 B| 7 B| 7 Ak"$ B7A%#!  B|1@A !   )@7 Ak"$ B7A'#!  B|)" )0XE@A!  )8 T@A!  A! )0 )8}" )(|! )` B }! )P! 1'! PPE@A! P@ Ak"$ B7A#! )"P@ Ak"$ B7A#!  B"BTE@A!  7`  <'  7(  B|)"P@ Ak"$ B7A#! Bȡ|!  7@  7 Ak"$ B7A'#! B|)! )(B" T! )`B |!  1'!B TE@A! B!  78  70  <'A!  B| < Aj"$ Aj"$A B7 B|B37 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  )! "PPE@A!  )$PE@A!  7B!A!  7  )(B|7 Ak"$ B7A#! )B$| )B| B|)7 )B|! )! BS@A ! B7$ )$B|"BTE@A!  B$| B|)!  7$ B0| 7 A j"$ Aj"$A  )(B|7 Ak"$ B7A#!  B|)! B0| 7 A j"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A $~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$$$$$$$$$$%%%%%%&&&'()*+,-./012345678999999::;;;;;;<<=>?@@@@AAABBCDEFGHHHHIIJKLLMMNOOOOOOOOPPQQQQRRRSSTTUVVWXYZ[\\]^ #(M@ Ak"$ B7A#! ` Ak"$#)0"P@ Ak"$ B7A#! ` )! "PPE@A! _ )BTE@A! ^  )PP@A! ]  B|7 B| )7 Ak"$ B7A#! \ B|)! B|)! PPE@A! [ #)0"P@ Ak"$ B7A#! [  )! "PPE@A! Z )$"PE@A! Y B! PPE@A! W  70  7H  7(  7 B| 7 B| )7 Ak"$ B7A#! V  )7 B| )H7 B| )7 Ak"$ B7A#! U B|1E@A! T )0P@ Ak"$ B7A#! T )0B<d )B !  7@ 1B!  7X P!  <' P!  <& @A! R )0P@ Ak"$ B7A#! R )0 1<b 1B"B"BPE@A! Q )0 7h )0B78 )0B<e )0B=\ )0B<f )0B=^ )0B70 )0B7@  )0)87 Ak"$ B7A#! O )0 B|)7P  )0)87 Ak"$ B7A#! N )0 B|)7H )P@ Ak"$ B7A#! N )0B|! )5!  7 B| > Ak"$ B7A"#! M   )0B|7 B|B< Ak"$ B7A#! L )(PP@A! J 1'"@A! I 1&"@A! H B/7 Ak"$ B7A#! G B|)!  7` P@ Ak"$ B7A#! G  7 B| )(7 Ak"$ B7A#! F  )`B|7 B|B )(}7 Ak"$ B7A#! E )XBXE@A! D 1'"E@A! C  )`B|7 B| )@7 Ak"$ B7A#! B B/7 Ak"$ B7A#! @ )0P@ Ak"$ BÀ7A#! @  )0)!  )7 B| 7 B| )7 B| )07 Ak"$ BȀ7A#! ? 1&"E@A! > Ak"$ Bʀ7A#! = B| )07 Aj"$ Aj"$A BȊ))"P@ Ak"$ B̀7A#! <  )0)! B"BTE@A! ;  B|)"P@ Ak"$ B׀7A#! ;  B| BB?|7 B B! B|B B BT< Ak"$ Bۀ7A#! : )P@ Ak"$ B܀7A#! :  )B|7 B| )7 Ak"$ Bހ7A#! 9 A! 7  )`B|7 B| )@7 Ak"$ B7A#! 7 A! 5 )XBQ@A! 5 )XBQE@A! 4  )`B |7 B| )@7 Ak"$ B䀤7A#! 3 A! 1  )`B(|7 B| )@7 Ak"$ B怤7A#! 1 A! / B/7 B|B )@}7 Ak"$ B耤7A#! / A7! - B/7 B| )@7 Ak"$ Bꀤ7A#! - 1'!A6! + B/7 B|B )(}7 Ak"$ B쀤7A#! + A5! ) B8B8"BTE@A! ) B' B|3! )0 7h PPE@A! ( )0  78B' B~|! )0 1<e )0 3=\ )0 1<f )0 3=^A*! & )0P@ Ak"$ B񀤉7A#! ' )0B7( )0B78 )0 )0) B )0)|7p  )0B|7 B|B< Ak"$ B7A#! & A4! $  7(  7P  7H )P@ Ak"$ B7A#! %   )7 Ak"$ B7A#! $ )HP@A! # )H! )(!  7(  78 )PPPE@A! ! )P!  7P  )7 Ak"$ B7A#!  )P! )8! )(!A!   )7 Ak"$ B7A#!  B|)!A!  )B|!  7p  7 B| )7 Ak"$ B7A#!  B|)! B|)! PE@A!   )7 B| )7 Ak"$ B7A#!  B|1@A!    )7 Ak"$ B7A#!  B|B7 Aj"$ Aj"$A  )p7 B| )7 Ak"$ B7A#!  B|)! B|)! P@A!  A!  B|"BTE@A!  B$| B|)!  7$ !A!  B!A!   7h )P@ Ak"$ B7A#!    )7 Ak"$ B7A#!   )B|7 Ak"$ B7A#!  B|)! B|)! B|)! )h 7H )h 7P )h 7X  )7 Ak"$ B7A#!  )h!A ! B!B!B!A!  7 B|B7 Ak"$ B7A#! Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!  B7 B|B+7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +  #(M@ Ak"$ B7A#! ! Ak"$ )hP@ Ak"$ B7A#! !  )pB|B|B !B BT! B ! )h)!  |!B.)! )h)!B }  |B|!  TE@A)!  70  )h7 B| 7 Ak"$ B7A#!  B|)! B|)"PPE@A!!   78 ! )h)!  QE@A!  )h  |7B!  7 B/7 B| 7 Ak"$ B7A#!  B/7 Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!   B|7 B| )87 Ak"$ B7A#!  B/7 Ak"$ B7A#!  B.)!B }  )h) )0||B|! ) !  7 )h)! )h 7 )hB|!  7X  7 B| 7  }!  7H B| 7 Ak"$ B7A#!  Ak"$ B7A#!  )H ) |! ) |! )h)" T@A!  B|B< Aj"$ Aj"$A  )X7  }! B|    T7 B|B< Ak"$ B7A#!  A!  )h)!  }"PP@A!  B! )h 7 )h  |7 !A !  7P  7(  )hB|7 B| 7 B| 7 Ak"$ B7A#! )8! )P! )(!A! B/)!  7@ Ak"$ B7A#! B7 B|B(7 Ak"$ B7A#!  )07 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!   )@7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  B|B< Aj"$ Aj"$A  T@A!  B!A!  A #!@@@@  #(M@ Ak"$ B7A#!  A k"$ B|B7 B|B7 B|B7 B|B7 B| )(7 B| )07  B|7 Ak"$ B7A#!  A j"$ Aj"$A A #!@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )(P@ Ak"$ B7A#!  )(B<d ) P@ Ak"$ B7A#!    ) 7 Ak"$ B7A#!   ) 7 B| )(7 B| 10< Ak"$ B7A#!    ) 7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ w  !"###$%&&'(()*****+,-./0123456789:;<= #(M@ Ak"$ B7A#! ? Ak"$ )PP@ Ak"$ B7A#! ? )XP@ Ak"$ B7A#! ?  )XB|!  7@  7 Ak"$ B7A #! > 1"BQE@A! = )X3`!  = PP@A! < )X5X )P5R@A! ; )PB|! )X) !  7 B|B }7 Ak"$ B7A#! : BȊ))"P@ Ak"$ B7A#! :  )X)! B"BTE@A! 9  B|)"P@ Ak"$ B7A#! 9  B| BB?|7 B B! B|B B BTB< Ak"$ B7A#! 8 )X) B !  70 1`B!  78 P!  < @A! 6  P@A! 5 B/7 Ak"$ B7A#! 4 B|)! )8BXE@A! 3 1"E@A! 2 P@ Ak"$ B7A#! 2  B|7 B|B )0}7 Ak"$ B7A#! 1 B/7 Ak"$ B7A#! / )PB|! )X) ! )X)!  7 B| 7 B| 7 Ak"$ B7A#! .   )@7 B|B< Ak"$ B7A#! - #)0"P@ Ak"$ B7A#! -  )! "PPE@A?! , )$"BSE@A?! + BTE@A! * B$| B| )X7  )$B|7$ Aj"$ Aj"$A  )P )P) )P)}7 )X )P)7 )P )X7A=! & P@ Ak"$ Bŀ7A#! '  B|7 B|B )0}7 Ak"$ Bǀ7A#! & A(! $ )8BQ@A! $ )8BQE@A(! # P@ Ak"$ Bˀ7A#! #  B |7 B|B )0}7 Ak"$ B̀7A#! " A(! P@ Ak"$ Bπ7A#! !  B(|7 B|B )0}7 Ak"$ Bр7A#! A(!  B/7 B| 7 Ak"$ BӀ7A#!  A!!  B/7 B|B }7 Ak"$ BՀ7A#!  )0! )8! 1!A!  BQE@A!  )X3`PP@A!  A!   7 B|B7 Ak"$ B܀7A#!   7 B|B7 Ak"$ B߀7A#!   )X5X!  >$ )P5!  > )X)!  7( Ak"$ B倴7A#!  B7 B|B7 Ak"$ B怴7A#!   )X7 Ak"$ B瀴7A#!  B7 B|B7 Ak"$ B耴7A#!   )(7 Ak"$ B逴7A#!  Bȍ7 B|B 7 Ak"$ Bꀴ7A#!   37 Ak"$ B뀴7A#!  B7 B|B 7 Ak"$ B쀴7A#!  5$7 Ak"$ B퀴7A#! B7 B|B7 Ak"$ B7A#!  5 7 Ak"$ B7A#! Ak"$ B7A#! Ak"$ B񀴉7A#!  B7 B|B#7 Ak"$ B򀴉7A#!  B7 B|B)7 Ak"$ B􀴉7A#!  B7 B|B)7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  )B7 )B7 )B7 ) ) 7 ) )(7 )B=` )B<b )B7h )B7x )B7 )B<d )B70 )B7H )B7P  )B|7 B|B< Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@    #(M@ Ak"$ B7A#!  A(k"$ )8P@ Ak"$ B7A#!  )8)" )0R@A!  )0P@ Ak"$ B7A#!  )0) )8QE@A!  )0 )8)7 )0) )8QE@A !  )0 )8)7 )8B7 )8B7 )8B7 A(j"$ Aj"$A )8)"P@ Ak"$ B7A#!   )8)7A !  )8)"P@ Ak"$ B7A#!   )8)7A!   7 )8) !  7 )8)!  7 Ak"$ B7A#!  B7 B|B-7 Ak"$ B7A#!   )7 Ak"$ B7A#!  BȀ7 B|B7 Ak"$ B7A#!  )87 Ak"$ B7A#! B€7 B|B7 Ak"$ B7A#!  )7 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  ) 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )07 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ )8P@ Ak"$ B7A#!  )8)!  7 PP@A !  )8)PP@A !  )8)PP@A !  )0P@ Ak"$ B7A#!  )8 )0)7 )0)"PPE@A !   )87 )0 )87 )8 )07 A(j"$ Aj"$A )0 )87A !  )8)!  7 )8)!  7 Ak"$ B7A#!  B7 B|B!7 Ak"$ B7A#!  )87 Ak"$ B7A#! Ak"$ B7A#!  ) 7 Ak"$ B7A#! Ak"$ B7A#!  )7 Ak"$ B7A#!  Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ Bĉ7A#! Ak"$BȊ))"P@ Ak"$ Bĉ7A#! )P@ Ak"$ Bĉ7A#!  ))"B"BTE@A!  B|)"P@ Ak"$ Bĉ7A#! B "BB"BTE@A!   B| |7 B! B|B B BT< Ak"$ Bĉ7A#!  Aj"$ Aj"$A  7 B|B7 Ak"$ Bĉ7A#!   7 B|B7 Ak"$ Bĉ7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ Bȉ7A#! Ak"$BȊ))"P@ Ak"$ Bȉ7A#! )P@ Ak"$ Bȉ7A#!  ))"B"BTE@A!  B|)"P@ Ak"$ Bȉ7A#! B "BB"BTE@A!   B| |7 B! B|B B BTB< Ak"$ Bȉ7A#!  Aj"$ Aj"$A  7 B|B7 Ak"$ Bȉ7A#!   7 B|B7 Ak"$ Bȉ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @  #(M@ Ak"$ B̉7A#!  Ak"$  )H7 Ak"$ B̉7A#!  )"PPE@A>!   7 #)0"P@ Ak"$ B̉7A#!    4B|>#P@ Ak"$ B̉7A#!  #)0!  70  7 Ak"$ B̉7A#!  )PP@ Ak"$ B̉7A#!  )H!  7( )P1 !  < ) B|!  78 ) )!  7  7 Ak"$ B̉7A#!  )( )}! ) B|! 1!A!  ! )"PPE@A!  3!  Q"E@A!   1 Q@A.!   T@A!  E@A!   1 TE@A!  )P = )P )7  )P7  ) 7 Ak"$ B̉7A#!    )87 Ak"$ B̉7A#! )0P@ Ak"$ B̉7A#!  )04! )0 B|> BBQE@A,! #P@ Ak"$ B̉7A#! #1E@A,! #Bu7 B|B< Aj"$ Aj"$A   )87 Ak"$ B̉7A#!  )0P@ Ak"$ B̉7A#!   )04! )0 B|> BBQE@A?@ABBCDDEFFGHHIJ #(A0jM@ Ak"$ B7A#! L Ak"$ )P@ Ak"$ B7A#! L )B !B BT! B  )|B|!  7H )B!BBT! B !  7 BB " Q@A! K  7 1"@A4! J ))P!  78 ))H!  7 )!A! H ) )B| 7 )B|! )! )! )H! )8! )!  XE@A! G )`"P@ Ak"$ B7A#! G  BTE@A! F  7   B|7 Ak"$ B7A#! E B|)! ) )8T@A! D A! C )!B!B!A-! B )`! )X! )p! )! )P! )! )! ! )H! SE@A,! A B~!  B||" )! )! BT! B ! B|" B " XE@A! @  XE@A! ?  7h  7  7x  </ }!  B }B? B|7 B|  }7 B| 7 B| 7 Ak"$ B7A#! > B |)! ) )x|")! )! )h TE@A! =  )hB|!  )RE@A+! <  7B!A! : 1/!A! 9 B|! ! B WE@A3! 8 E@A3! 7  7` B|"B!B' |)!  7XB' |)!  7PB' B|)"BT!  B !  B B|!  7pB! A$! 5 Aj"$ Aj"$A ))`"P@ Ak"$ B7A#! 5 ))P! ))H! BTE@A! 4  7  7@   B|7 Ak"$ B7A#! 3 B|)! ) )@TE@A! 2 ) )B| 7 ))X! ))H! ) XE@A! 1 )B|" )XE@A! 0 B  )}B|}B? B|! ) )}B|! 1"@A! / B!A! -  B|B7 B|!  S@A! , ))`"P@ Ak"$ Bƀ7A#! ,  )BTE@A! +   )B|7 Ak"$ B̀7A#! * B|)! ) )@TE@A! ) ) )B| 7 )H! )!A! ' PPE@A! '  7 B| B7 Ak"$ BҀ7A#! & A! $ ))P! ))H!  TE@A! $ ))`"P@ Ak"$ Bր7A#! $ B!  |)! BTE@A! #  70  7   B|7 Ak"$ B݀7A#! " B|)" )0Q@A! ! ))P! ))H! ) TE@A!  )| 7 )H! )!A!  Aj"$ Aj"$A  )h7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   )7 B| )87 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   )7 B| )@7 Ak"$ B7A#!   )7 B|B7 Ak"$ B7A#!   7 B| )7 Ak"$ B7A#!   )7 B| 7 Ak"$ B7A#!  )7 B| )@7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!  )7 B| 7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ K  #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  )B !B BT! ) B |B|!BBT! )BB ! BB ! )BB B !  7X BB B !  Q@A5!  ))`"P@ Ak"$ B7A#!   BTE@A!   7P  7H  7@  B|!  7h  B|7 B| 7B }!  78 B| 7 Ak"$ B7A#!  B|)!  70  )h7 B| )X7 B| )87 Ak"$ B7A#!  )PB|! )H! )! )0!A"!   7(  B|!  7p  B|7 B|B7 B|B7 Ak"$ B7A#!  B|)!  78  )p7 Ak"$ B7A#!  )(B|! ) )8|! )H! )!  7  TE@A)!  )`"P@ Ak"$ B7A#!   BT@A!  A!  )`"P@ Ak"$ B7A#!   BTE@A?!   B|!  7`  B|7 B|B7 )@B|!  78 B| 7 Ak"$ B7A#!  B|)!  70  )`7 B|B7 B| )87 Ak"$ B7A#!  ) )0|!  7  )7 B| )7 B| )7 B|B< B|B< Ak"$ B7A#!  B| ) B 7 Aj"$ Aj"$A ))`"P@ Ak"$ B7A#!   BTE@A!  B|!  7x  B|7 B| 7  }B|!  78 B| 7 Ak"$ B7A#! B|)!  70  )x7 B| )X7 B| )87 Ak"$ B7A#! )0!A3!  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ BĀ7A#!   7 B|B7 Ak"$ Bǀ7A#!   7 B|B7 Ak"$ Bʀ7A#!   A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$ )(P@ Ak"$ B7A#!  )0BBBBT! )(1E@A !  )(B|7 B| )07 Ak"$ B7A#! B|)! B|1E@A ! B8| 7 A j"$ Aj"$A B')! B8| 7 A j"$ Aj"$A BȊ))"PPE@A!  BTE@A!   B|)PPE@A!  B8| )07 A j"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A 4~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUUUUUUVWWWWXXYYYZZZ[[[\\\]^^^^^_`abbbbbbccdeeeeefghiiiiiijkkkkllmmmnnoopqrstuvwxyz{|}~ #(AjM@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  B|B7 B|B7 B|B.)7 B|B')7 B|B7 B|B7 B|B7 B| B|7 )!B!B!B!B!A,!  B|! SE@A!  TE@A!  B|)"PE@A !  B!A!   7  7  7  7x B|)! |!  7 )!   B BT7 BT!  </ B|B B B 7 B|$  Ak"$ B7Av!A #!  )BPP!B )B ! )x |! ) X@A:!   E@A7!  B! ) X@A+!  )xPE@A&!   E@A#!  B! )B| )B 1/ }! )! )! )! )! )! )! )! )! )! )! )! ! )!A!  )B$B!A!  B )B 1/!  T@A!   )x|! )! )! )! )! )! )! )! )! )! )! )! )! )!A! ~ )B|! )! )! )"! BSE@A! } B!B' |")! B ! BT! B ! B' |)!  B !  B~|" )! )!  |" XE@A! |  XE@A! { B }}B? B|!  )h! B }! B' |")! B BT! QE@A! z B| !  7  7  7  7  7  7  7  7  7  7 ! B!B!A! w )BB!A! v ) )B 1/ ) )xP! ) X@A! u )P@A! t  )BP! )B! P!B  ! E@A! s B!  7p  7` E@A! q B!  78 Ak"$ Bˀ7A#! o B7 B|B7 Ak"$ B̀7A#! n  )B|7 Ak"$ B̀7A#! m B7 B|B7 Ak"$ B΀7A#! l  )7 Ak"$ Bπ7A#! k B7 B|B7 Ak"$ BЀ7A#! j  )`7 Ak"$ Bр7A#! i B7 B|B7 Ak"$ BҀ7A#! h  )p7 Ak"$ BӀ7A#! g B7 B|B7 Ak"$ BԀ7A#! f  )87 Ak"$ BՀ7A#! e Ak"$ Bր7A#! d Ak"$ B׀7A#! c Ak"$ B؀7A#! b B7 B|B7 Ak"$ Bـ7A#! a  )7 Ak"$ Bڀ7A#! ` B7 B|B 7 Ak"$ Bۀ7A#! _  )7 Ak"$ B܀7A#! ^ B݁7 B|B7 Ak"$ B݀7A#! ]  )7 Ak"$ Bހ7A#! \ Ak"$ B߀7A#! [ Ak"$ B7A#! Z ))h!  7 Ak"$ Bက7A#! Y B7 B|B7 Ak"$ B 7A#! X  )7 Ak"$ B 7A#! W Bڀ7 B|B7 Ak"$ B䀀7A#! V  )7 Ak"$ B倀7A#! U Ak"$ B怀7A#! T Ak"$ B瀀7A#! S ))!  7 ))!  7 Ak"$ B耀7A#! R B7 B|B7 Ak"$ B退7A#! Q  )7 Ak"$ Bꀀ7A#! P B7 B|B7 Ak"$ B뀀7A#! O  )7 Ak"$ B쀀7A#! N Ak"$ B퀀7A#! M Ak"$ B7A#! L B!A! J  7P Ak"$ B7A#! J B7 B|B7 Ak"$ B񀀊7A#! I  )7 Ak"$ B򀀊7A#! H B7 B|B7 Ak"$ B󀀊7A#! G  ) )|7 Ak"$ B􀀊7A#! F B7 B|B7 Ak"$ B7A#! E  )h7 Ak"$ B7A#! D B7 B|B7 Ak"$ B7A#! C  )X7 Ak"$ B7A#! B B7 B|B7 Ak"$ B7A#! A  )P7 Ak"$ B7A#! @ B7 B|B7 Ak"$ B7A#! ? Ak"$ B7A#! > )B|!  )SE@A! < ) B|)! BP! B! P!B  ! E@A! ; B!  7  7X  7h E@A! 9 B!A! 7 B$B!A! 6 BB!A! 5 )B$B!A! 4 )BB!A! 3 B')! B|B7 B| 7 Aj"$ Aj"$A  7 B|)! ))!  7  )7 B| 7 Ak"$ B7A#! 2 B|)! B| )B ) )B )BT|7 B| 7 Aj"$ Aj"$A !A;! / B! A4! . )`"P@ Ak"$ B7A#! /  BTE@A! .  7   B|7 B| )7 B|B7 Ak"$ B7A#! - B|)! B |)! BQ@A! ,  7 B ! )B!  7  |! B|)!  7 B| )B|B }7 B|$  Ak"$ B7Av!A #! + B|)!  )7 B| 7 Ak"$ B7A#! * B|)! B| )B )|7 B| 7 Aj"$ Aj"$A ))P! ))H! ) TE@A! (  )B|)! BP! B! P!B  ! E@A! ' B!  7@  7H E@A! % B!A! # BB!A! " B$B!  70 Ak"$ BŁ7A#! ! B7 B|B7 Ak"$ BƁ7A#! B7 Ak"$ Bǁ7A#!  B7 B|B7 Ak"$ Bȁ7A#!   )7 Ak"$ BɁ7A#!  B7 B|B7 Ak"$ Bʁ7A#!   )H7 Ak"$ Bˁ7A#!  B7 B|B7 Ak"$ B́7A#!   )@7 Ak"$ B́7A#!  B7 B|B7 Ak"$ B΁7A#!   )07 Ak"$ Bρ7A#!  B7 B|B7 Ak"$ BЁ7A#!  Ak"$ Bс7A#!  Ak"$ Bҁ7A#!  BŢ7 B|B7 Ak"$ BӁ7A#!   )7 Ak"$ Bԁ7A#!  Ak"$ BՁ7A#!  Ak"$ Bց7A#!  B7 B|B7 Ak"$ Bׁ7A#!   )7 B| 7 Ak"$ Bف7A#!  7 B|B7 Ak"$ B܁7A#! B7 B|B7 Ak"$ B߁7A#!  7 B| 7 Ak"$ B၀7A#!   7 B| 7 Ak"$ B぀7A#!   7 B| 7 Ak"$ B偀7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ E  !"#$%%&''() #(M@ Ak"$ B7A#! + Ak"$ )HP@ Ak"$ B7A#! +  )H)h! )H)x! B!  X@A-! * B"B !B }! )P XE@A! ) )H)P! )H)H!  TE@A! (  B|)! BPPE@A*! ' B! )P X@A! %  )H7 B| )P7 Ak"$ B7A#! $ B|)! B|)! PE@A! # )PBQE@A! " )HB')7h B|B7 B|B7 Aj"$ Aj"$A  78  70  )H7 B| 7 B| )P7 Ak"$ B7A#!  B|)! )H)h!  )0TE@A!  )H )07h B| )87 B| 7 Aj"$ Aj"$A )H)`"P@ Ak"$ B7A#!   BTE@A?!   78  7(   B|7 B| )P7 B| 7 Ak"$ B7A#!  B|)! B |)! BQ@A.!   B ! )8B!  |! B |!A!  BB!A!  B|B7 B|B7 Aj"$ Aj"$A Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )(7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )P7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!   )H)h!  78 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  )8BB 7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )87 Ak"$ B7A#! Ak"$ B7A#! Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B| 7 Ak"$ BĀ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ n  !""#$ #(M@ Ak"$ B7A#! & Ak"$ )HP@ Ak"$ B7A#! &  )P )H)hTE@A! % )H )P7h )XB )P|B|! )H)!  TE@A ! # )H 7 )XBQ@A! !  )PB! B! )PBB ! BB !  Q@A X@A(!  B!A!  B|! BSE@A!  P@ Ak"$ B7A#!   B|)"zB?!  ! BT! B "B| P@A!  B! ! A!   }! B! PPE@A!   X@A!  B?!   B BT"B| PE@A!  !A!  B?!   B BT"B| P@A!  BzB?!  B BT"z"B?!  B BT!  |! B| P@A!  ! ! !A! !A! !A! !A!  BQE@A%! B! B| 7 Aj"$A BB B BB$!A$!   BQE@A.!  B! B| 7 Aj"$A BB B BB$!A-!   B|B7 Aj"$A A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  A(k"$ )8BQE@A!  )0P@ Ak"$ B7A#!   )@B! )0!A ! B|! BTE@A!  B|)B"P@A! B z|! B| 7 B| 7 A(j"$ Aj"$A B!A !  )8BXE@A!   )07 B| )87 B| )@7 Ak"$ B7A#!  B|)! B |)! B| 7 B| 7 A(j"$ Aj"$A  )07 B| )87 B| )@7 Ak"$ B7A#!  B|)! B |)! B| 7 B| 7 A(j"$ Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ BĊ7A#!  )B!BBT! B ! )! )!B!B!A!  B|! BTE@A!  P@ Ak"$ BĊ7A#!   B|)"B"PE@A!  B!A!  BQE@A !  B z|!  z |X@A!   B|!B! A!  }! B! PPE@A!  X@A! B?!   B BT"PE@A ! B! BT@A!  BB y}}!A!  B |  B|7 B(| 7 Aj"$A B?!   B BT! z!A!  B | B }7 B(| 7 Aj"$A B |B7 B(| 7 Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ BȊ7A#!  )B"BBBT! )! )!B!B!B!A!  B|! BTE@A!  P@ Ak"$ BȊ7A#!   B|)"BQE@A!  B!A! BQE@A ! B Bz|! PE@A!  B! B y}! |B|B@|!B }!A! z!  |X@A! BTE@A!   B! B y}! |B|B@|!B }!A!  B|!A!  B | 7 B(| 7 Aj"$A  TE@A!  B |B7 B(| 7 Aj"$A B | 7 B(| 7 Aj"$A A #!@@@@@   #(M@ Ak"$ B̊7A#!  Ak"$ ) P@ Ak"$ B̊7A#!    ) 7 B| )(7 B| )07 Ak"$ B̊7A#!   ) B|7 B| )(7 B| )07 Ak"$ B̊7A#!  Aj"$ Aj"$A A #!@@@@@   #(M@ Ak"$ BЊ7A#!  Ak"$ )P@ Ak"$ BЊ7A#!    )7 Ak"$ BЊ7A#!   )B|7 Ak"$ BЊ7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@   #(M@ Ak"$ BԊ7A#! A(k"$ )8BBBBT! )0BQE@A! B|!  7  7 B|B7 B|B/7 Ak"$ BԊ7A#!  B|)!B.B.) ) |7 P@ Ak"$ BԊ7A#!   )07  )87( B| 7 A(j"$ Aj"$A )0B~|BXE@A !  B|!A!  B7 B|B7 Ak"$ BԊ7A#!   A ~#!@@@@@@@   #(M@ Ak"$ B؊7A#!  Ak"$ )P@ Ak"$ B؊7A#!  ))BR@A!   ) ))(B|B0|! B | 7 Aj"$ Aj"$A B7 B|B7 Ak"$ B؊7A#!   A ~#!@@@@@@@@   #(M@ Ak"$ B܊7A#!  Ak"$ )P@ Ak"$ B܊7A#!  ))"BRE@A!  BR@A !   ) ))(B|B0|! B | 7 Aj"$ Aj"$A B7 B|B7 Ak"$ B܊7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ B  !"##$$%&&'(()*+, #(M@ Ak"$ B7A#! . Ak"$B')PPE@A1! - B )pSE@A0! , )h! )p! !B!B!A! * B|! )! B|!  |" B |" B!  S@A! ) B')"P@ Ak"$ B7A#! )  )`|" B |" B" B|" B " B B ~}"B TE@A=! (  78 B!  7H  |)! )`! )X!A ! & )! PPE@A! % ) QE@A ! $  )QE@A ! #  ) QE@A ! "  )(" B XE@A:! !  7@  B0|7 B| 7 B| 7 B| )h7 B | 7 B(| )x7 Ak"$ B7A#! B0|1@A!  )@! )p! )h! )8! )H! )`! )X!A !  B| )@7 Aj"$ Aj"$A 1"E@A/!   7 B| 7 Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!   )("B XE@A7!  )p  )p S! B0|" )hR@A-!   )87  )`7 B')"P@ Ak"$ B7A#!    )H|)7B')"P@ Ak"$ B7A#!   )H| 7 )XBQE@A*!  B.)7B. 7 B| 7 Aj"$ Aj"$A )XBQE@A,!  B.)7B. 7A)!  B.)7B. 7A)!   7@  7 B| )h7 B| B7 Ak"$ B7A#!  )@!A#!  B|B7 Aj"$ Aj"$A )p! )h!B!A!  B7 B|B/7 Ak"$ B7A#!  B.5P! B|)! P@A5! B' 7 B')PP@A! A! B'  Ak"$ B7#!A4!  7 B|B 7 Ak"$ B7A#!  7 B|B 7 Ak"$ B7A#!   7 B|B 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@   #(M@ Ak"$ B7A#! ) )(R@A! B )SE@A! ) ! )! )!B!A!  B|! )  B|)R@A!  B|" S@A!  B8|B< Aj"$A B8|B< Aj"$A B8|B< Aj"$A A ~#!@@@@@   #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#!  B.5"B|BBժ ~!B.  B:BB:BTB0~}B|>B.B< B.7 Ak"$ B7A#!  Aj"$ Aj"$A A #!@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#!  B.1E@A !   B.7 Ak"$ B7A#!  Aj"$ Aj"$A Ak"$ B7A#!  B.B<A!  A ~#!@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$B.5!  >B.)!A !   7  7 Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!  B | 5 5Bժ ~B!B~}BB|!  ) )|7  ) )|7  ) )|7  ) )|7 B7 B7 B7 B7 ))! 5! PP@A!  A j"$ Aj"$A A ~#!@@@@@@@@@@@@@  #(AjM@ Ak"$ B7A#! Ak"$ B|A #! B7 B| B|7 B|B 7 B|B 7 Ak"$ B7A#! ) !  7@ B.7 Ak"$ B7A#! )@B XE@A! B7 B| )7 B| B|7 B| )@7 B |B 7 B(|B< Ak"$ B7A#!  B0|)!  7HB.5!  ><  7 Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!  B |! 5  ) 7 Ak"$ B7A#!  )"P@ Ak"$ B7A#!  B |! 5B|!  5 BBժ ~B!B~}B|BB|" )B|7  )( )|7 B.7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ ) BW!B ) !  7  7 Ak"$ B7A#!  B|1@A!  Aj"$ Aj"$A  ) 7 B| )(B|7 B|B7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ B.7 Ak"$ B7A#! )"BWE@A!  B |B< Aj"$ Aj"$A ) SE@A!  #)0"P@ Ak"$ B7A#!   5! 5!  > B ! B BB  !  > )  |B S! @A!  B |B< Aj"$ Aj"$A B!A !  A ~#!@@@@@@@@@@@@@@@@@@ $  #(AjM@ Ak"$ B7A#!  Ak"$ B|A #!#P@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  )"PPE@A!  #QE@A!   )7 B| B|7 B|B 7 B|B 7 Ak"$ B7A#!  B |)!  7X B.7 Ak"$ B7A#! )XB XE@A"!  )7 B|B7 B| B|7 B| )X7 B |B 7 B(|B< Ak"$ B7A#! B0|)!  7`  7 Ak"$ B7A#! B|)"P@ Ak"$ B7A#!  )B|7  )`7 Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!   ) )|7 B.7 Ak"$ B7A#!  Aj"$ Aj"$A  B7 B|B7 B|B7 B| 7 B | )7 B(| B|7 B0|B 7 B8|B7 B|B7 B|B7 Ak"$ B7A#!  B|)!A !   )X7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 9  !" #(M@ Ak"$ B7A#! $ Ak"$ B.7 Ak"$ B7A#! # #70#)0"P@ Ak"$ B7A#! # B< )xPPE@A1! "  )x7 Ak"$ B7A#! ! B|)!  7( B|)!  78 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!   )h7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )p7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )87 B| )(7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  )0P@ Ak"$ B7A#!  )0)0"P@ Ak"$ B7A#!  )"PPE@A!  )0 QE@A(!   )07 Ak"$ B7A#!  B|B7 B|B7 B|B7 B|B7 B|B7 B| )`7 B| B|7 B| )07  B|7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )0)0"P@ Ak"$ B7A#!  B< B.7 Ak"$ B7A#!  Aj"$ Aj"$A  7 Ak"$ B7A#! )0)0"P@ Ak"$ B7A#!  )! B7 B|B7 B|B7 B| 7 B |B7 Ak"$ B7A#! A! Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!   )h7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )p7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A8k"$ B.7 Ak"$ B7A#!  #7#)0"P@ Ak"$ B7A#!  B< Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  )@7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )H7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#! Ak"$ B7A#!   )7 Ak"$ B7A#!  B|B7 B |B7 B(|B7 B0|B7 B|B7 B | )87 B(| B|7 B0| )7  B|7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  B< B.7 Ak"$ B7A#!  A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#! #7#)0"P@ Ak"$ B7A#! B< Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#! Ak"$ B7A#!  )7 Ak"$ B7A#! Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  B< B.7 Ak"$ B7A#!  Aj"$ Aj"$A A #!@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ ) )X ) ) XR@A!  B(| )7 B(| ) 7 Aj"$ Aj"$A Bƅ7 B|B<7 Ak"$ B7A#!   A ~#!@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )8 )(X@A!   )0 )8XE@A!  B| )(7 B| )07 A j"$ Aj"$A  )(7 B| )87 Ak"$ B7A#!  B|)! B|)! B| 7 B| 7 A j"$ Aj"$A B|B7 B|B7 A j"$ Aj"$A A #!@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!  )(B7 )(B7 B7 B|B7 B| )07 Ak"$ B7A#!  )( B|)7B.5PP@A!  )( )07 )(B7 A j"$ Aj"$A )(B | )0 Ak"$ B7#!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  ))! ))! ) ! !B!A!  !  }!B SE@A!  B? |B |" TE@A!   B|")! )! XE@A !    T@A!    T@A!  B|! !A! B(| B|7 Aj"$ Aj"$A B|!  SE@A!  TE@A!    B|)TE@A!  B(| 7 Aj"$ Aj"$A B(| 7 Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$  ) 7 B| )(7 Ak"$ B7A#!  )"PE@A!  ) P@ Ak"$ B7A#!  ) )! ) )! PPE@A!  )! B0| 7 B8|B< Aj"$ Aj"$A ) P@ Ak"$ B7A#!  ) )! ) )! B|" TE@A!   B|")! )!  )(XE@A!   )( T@A!  SE@A!  TE@A!  B|)! B0| 7 B8|B< Aj"$ Aj"$A B0|B7 B8|B< Aj"$ Aj"$A B0| )(7 B8|B< Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!  B7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ x  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ #(M@ Ak"$ B7A#! a Ak"$ )X )`T"E@A! `  )` )X}! P@A! ^  <'  )P7 B| )X7 Ak"$ B7A#! ] B|)!B SE@A! \ )PP@ Ak"$ B7A#! \ )P)! )P)! B|" TE@A! [  B|)!  )XQ! )PP@ Ak"$ B7A#! Z )P)! )P)! )P)!  SE@A! Y  TE@A! X   B|) )`Q! E@A2! V E@A2! U  TE@A! T B|! B!  |)!  TE@A! S  B| 7 )P)! )P)! )P)!  XE@A! R  }!  }!B }!  B? |! B|" XE@A! Q B|"   S! B B|}B? B|!  R@A0! P )P)! )P)B|" XE@A! O )P 7  1'"E@A/! M  )` )X}! )P )P) |7 Aj"$ Aj"$A B!A.! I  7 B| 7 B| B7 Ak"$ B7A#! I A%! G E@A5! G B|" TE@A! F  B| )`7A'! D E@A8! D  TE@A! C  B| )X7A'! A  7( B|!  S@A! A  XE@A! @ )P 7 )P)! )P)! B|" XE@A! ?  }!  }"B|!B }! B!  B? |!  XE@A! > B|"   S! B }B? B|" R@A?! = )P)! )P)!  TE@A! <  B|" )X7  )`7A'! :  7 B| 7 B| B7 Ak"$ B7A#! : )(!A=! 8  78  70  7@ )P 7 )P B7 B! )P) !  7 B|B7 B| 7 Ak"$ B€7A#! 8 )P B|)7 )P)! )P)! )( XE@A! 7 )( )8XE@A! 6  )@RE@A! 5  7 B| )@7 B| )(B7 Ak"$ Bƀ7A#! 4 )P)! )P)! )P)! )(B|" XE@A! 2  )(}B|! )0 )(}"   S!  )(}B|!B }! B!  B? |! )@B )8 )(}}B? )(B|" R@A! 1 )(!A=! /  7 B| 7 B| B7 Ak"$ Bˀ7A#! / A! - B!A! , B!A! + B!A ! *  7 B| 7 Ak"$ BЀ7A#! *  7 B| 7 Ak"$ BҀ7A#! (  7 B| 7 Ak"$ BԀ7A#! &  7 B| 7 Ak"$ Bր7A#! $  7 B| 7 Ak"$ B؀7A#! "  )(7 B| )87 Ak"$ Bڀ7A#!  )(7 B| 7 Ak"$ B܀7A#!   7 B| 7 Ak"$ Bހ7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B
7A#!   7 B| 7 Ak"$ B䀨7A#!   7 B| 7 Ak"$ B怨7A#!   7 B| 7 Ak"$ B耨7A#!   7 B| 7 Ak"$ Bꀨ7A#!   7 B| 7 Ak"$ B쀨7A#!   7 B| 7 Ak"$ B7A#! Ak"$ B7A#! Bע7 B|B7 Ak"$ B񀨋7A#!  )X7 Ak"$ B򀨋7A#!  B7 B|B7 Ak"$ B󀨋7A#!   )`7 Ak"$ B􀨋7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B)7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! )P@ Ak"$ B7A#! ))! ))! P@A! B|!  B|")! )!  TE@A!    }! ) TE@A!    )}!  7 ) )) )}7 B| 7 B | 7 Aj"$A ) 7 ) )) }7 B| 7 B | 7 Aj"$A B!A !  B|B7 B|B7 Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @  !" #(M@ Ak"$ B7A#! $ Ak"$  )`7 B| )h7 Ak"$ B7A#! # )"P@A7! " )`P@ Ak"$ B7A#! " )`)! )`)! )`)!  XE@A>! !  }!B  }}!  B B?|!B SE@A6! B!B!A !  B|! )! )!  TE@A5!    }! B|!  |!  S@A!  B|"B!  |")! )!  )hXE@A!   )h T@A!  )`)!  XE@A:!  )` 7 )` )`) }7 Aj"$ Aj"$A  70  7P  7H  7@  78  7(  7 B| 7 B| )h7 Ak"$ B7A#!  )( )H )@}|! B |)! B|)" T"E@A4!    }! PE@A*!  )P! ! !A!   E@A3!    }!  }! )`)! )`)! )P TE@A$  7@  )H7 B| 7 B| )8B|BB B7 Ak"$ B7A!#!  B|1E@A!   )pB|7 Ak"$ B7A#!  )@B ! B|)!  BB|!  7P  7 Ak"$ B7A#!  B|)!  7` P@ Ak"$ B7A#!  B| 5$BBB|!  7X  7 Ak"$ B7A#!  B|)!A!   )X7 Ak"$ B7A#!  B|)! PPE@A!   7(  )X7 B|B7 Ak"$ B7A(#!   )`B|7 B|B> Ak"$ B7A#!  B|5BQ@A"! B| )(7 Aj"$ Aj"$A  )P7 B|B7 Ak"$ B7A(#! B.7 B| )`7 Ak"$ B7A#! A!!  )pP@ Ak"$ B7A#! )pB |!  7H  7 Ak"$ B7A#!   B|)"B "B! B!  X@A8!   7@  78  70  )pB|7 Ak"$ B7A'#!  B|) )@B)X@A7!  )8! )@"!A !  B|B7 Aj"$ Aj"$A B|B7 Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -  #(M@ Ak"$ B7A#!  A0k"$ )8P@ Ak"$ B7A#!  )8B |!  7   7 Ak"$ B7A#!   )"B! B "B!  T@A"!  B B" )8)TE@A!   )8) B|")"PPE@A!  5"P@A !  BQ@A!   7(  7 B|B7 Ak"$ B7A(#!  B.7 B| )(7 Ak"$ B7A#!    ) 7 B|B7 Ak"$ B7A##!   )8B|7 B|B7 Ak"$ B7A&#!  A0j"$ Aj"$A B7 B|B17 Ak"$ B7A#!  B7 B|B47 Ak"$ B7A#!  7  7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )7 Ak"$ B7A#!  B܅7 B|B 7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B#7 Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ Bċ7A#!  A k"$ )(P@ Ak"$ Bċ7A#!   )(7 Ak"$ Bċ7A#!  )"PPE@A!  B0| 7 A j"$ Aj"$A B')! B 7 B| 7 B|B/7 Ak"$ Bċ7A#!  B|)! B0| 7 A j"$ Aj"$A A ~#!@@@@@  #(M@ Ak"$ Bȋ7A#!  Ak"$ ) P@ Ak"$ Bȋ7A#!  ) B|!  7 B|B> Ak"$ Bȋ7A"#!  )P@ Ak"$ Bȋ7A#!   )7 B| ) 7 Ak"$ Bȋ7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B̋7A#!  A(k"$  )07 B|B7 Ak"$ B̋7A#! )"B"P@A! B8| 7 A(j"$ Aj"$A  7  7 Ak"$ B̋7A#! B7 B|B7 Ak"$ B̋7A#!  ) B 7 Ak"$ B̋7A#!  B܅7 B|B 7 Ak"$ B̋7A#!   )7 Ak"$ B̋7A#!  Ak"$ B̋7A#!  Ak"$ B̋7A#!  B7 B|B7 Ak"$ B̋7A#!   A Q#!@@@  #(M@ Ak"$ BЋ7A#!  Aj"$A A ~#!@@@@@@@@@@@   #(M@ Ak"$ Bԋ7A#! Ak"$B')!B')! ) TE@A !  ) B|)"P@ Ak"$ Bԋ7A#! )@"PPE@A!   7  7 Ak"$ Bԋ7A#!   )7 Ak"$ Bԋ7A#!  Aj"$ Aj"$A Aj"$ Aj"$A  ) 7 B| 7 Ak"$ Bԋ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@   A k"$ )(PPE@A!   )(7 B| )07 Ak"$ B؋7A#!  B|)!  7B )0SE@A!   )0S@A !  )0BSE@A!   )0| )0S@A ! A j"$ Aj"$A A j"$ Aj"$A Ak"$ B؋7A#! B7 B|B 7 Ak"$ B؋7A#!  )7 Ak"$ B؋7A#!  B7 B|B7 Ak"$ B؋7A#!   )07 Ak"$ B؋7A#!  Ak"$ B؋7A#!  Ak"$ B؋7A#!  B7 B|B7 Ak"$ B؋7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@    #(M@ Ak"$ B܋7A#!  A k"$#)0"P@ Ak"$ B܋7A#!  )! "PP@A!  )(P@ Ak"$ B܋7A#!   )(B|!  7 Ak"$ B܋7A#!  )(P@ Ak"$ B܋7A#!   )(B|7 Ak"$ B܋7A#!  B|5" Bժ ~B!B~}B"BTE@A!  B0| )( B ~|7 A j"$ Aj"$A  B|7 B|B> Ak"$ B܋7A#! B|5"BBP@A! A!  7 B|B7 Ak"$ B܋7A#!  > Ak"$ B܋7A#!  B7 B|B 7 Ak"$ B܋7A#!   57 Ak"$ B܋7A#!  Ak"$ B܋7A#!  Ak"$ B܋7A#!  B7 B|B7 Ak"$ B܋7A#!   A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$#)0"P@ Ak"$ B7A#!  )! "P"P@A!  )(P@ Ak"$ B7A#!   )(B|!  7 Ak"$ B7A#! A j"$ Aj"$A  B|7 B|B> Ak"$ B7A#! B|5"BBPPE@A!  > Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   57 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$ )P@ Ak"$ B7A#! )B|! ) 7B.1E@A!  ) )B |7 )) ))}BPP@A!  Aj"$ Aj"$A ) B |7A!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@  Ak"$#)0"P@ Ak"$ B7A#! B 4S@A! B.1E@A! )PP@A!  B7 Ak"$ B7A#!  Aj"$ Aj"$A  )7 B| ) 7 Ak"$ B7A#!  B.1@A!  #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!    B-|7- Aj"$ Aj"$A )"P@ Ak"$ B7A#!    B-|7- Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ K  !"#$%%&''() #(M@ Ak"$ B7A#! + Ak"$ )pP@ Ak"$ B7A#! + )p)- )pB-|}"B"BXE@A! *  7P )pB7- )pB-|!B.1@A:! ) B SE@A9! (  7` !B!B!A! & B|! )"B TE@A! % B|" S@A! $ BXE@A! #  )pB-|7 B| 7 B| 7 B|B7 Ak"$ B7A#! "  )pB-|7 Ak"$ B7A#! ! Aj"$ Aj"$A  7H  7X  78  7 B|B7 B|B7 Ak"$ B7A#!  B |)! B|)! B(|)! PE@A!  )P! )X! )`! )H! )8!A !  P@ Ak"$ B7A#!   )P! B! B!B ! BT! B !  |"1 "BPPE@A!  )P! )X! )`! )H! )8!A !   7@  70  7 B| < Ak"$ B7A#!  BȊ))"P@ Ak"$ B7A#!   )0)! B"BTE@A!   B|)"P@ Ak"$ B7A#!  B| BB?|"1! B B!B B BT!  BP@A7!  )01b! BBPPE@A5!  )p )p)- )0)h|7- )P! )X! )`! )H! )8!A !  )8 )PTE@A!  )` )8B| )@7 )8B|! )P! )X! )`! )H!A !   7 B| < Ak"$ B7A#!  A2!  !B!A !  B SE@A?!  B!A=!  )XB|! !  7H  7X  )7 Ak"$ B7A#! )HB|" )PS@A Ak"$ B7A"#!  A !  A #!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$B ) T@A!   ) 7 B| )(7 B| 40> Ak"$ B7A#!  B8| 40> Aj"$ Aj"$A B7 B|B&7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ #7 #7 Ak"$ B7A#! 1E@A ! )P@ Ak"$ B7A#! )B > B7 B|B17 Ak"$ B7A#!  B')!B')! PPE@A !  )!  7 B| 7 Ak"$ B7A#!  B7 B|B*7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ B> Ak"$ B7A#! )P@ Ak"$ B7A#! B.5P! B|)! P@A !  ) 7P P@ Ak"$ B7A#!  B.5PP@A !   )70 Aj"$ Aj"$A B0| ) Ak"$ B7#!A !  )B|  Ak"$ B7#!A!  A ~#!@@@  #(M@ Ak"$ B7A#!  B.B>#)0"P@ Ak"$ B7A#!  B7HB.B7 Aj"$A A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#PPE@A!  #)0"PPE@A!  4BPP@A!  Aj"$ Aj"$A  ) 7 B| )(7 Ak"$ B7A#!   A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#PPE@A!  #)0"PPE@A!  4BPP@A!  Aj"$ Aj"$A  )7 B| ) 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B׼7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B׼7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B׼7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B׼7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B׼7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B׼7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B׼7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A8k"$  )87 B|B׼7 B|B7 Ak"$ B7A#!  B |B7 B |B7 B |B< B |B< B | )@7 B0|B< B(| )H7 B1|B< B7 B| B |7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$  )7 B|B7 B|B7 Ak"$ B7A#!  B')!B')! PPE@A!  )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 Ak"$ B7A#!  B')!B')! PPE@A!  )!  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ G  !"#$%%&'()*+,- #(M@ Ak"$ B7A#! / Ak"$ B,|B7 B0|B7 B,|B7 B!A! - B,| B|B> B|! BS@A! , B!A! * B|!  BX"E@A0! ) B! B X@A/! '  E@A,! & B!  BTE@A&! $ BXE@A! #  B|B"BTE@A! " B' |1"BTE@A! ! B' B|3! B,| B|"4"BSE@A!   >A!  B BR@A3!  A!   Bx|B"BTE@A!  B' |1"BTE@A>!  B' B|3!A!  B| T@A!   B?|B@!A!  B|!A!  Aj"$ Aj"$A B|B!A !   7  7  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   ) 7 Ak"$ B7A#!  Ak"$ B7A#! Ak"$ B7A#! Bɧ7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B€7A#!   7 B|B7 Ak"$ BĀ7A#!   7 B|B7 Ak"$ Bƀ7A#!   A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 B7 B|B7 )"P@ Ak"$ B7A#!  B.5P! )0! P@A!  B' 7 Aj"$ Aj"$A B'  Ak"$ B7#!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@ )  Ak"$ B|B7 4XB B "BXE@A"!  B! BTE@A!  #)0"P@ Ak"$ BČ7A#!  )"P@ Ak"$ BČ7A#!   B| B~|")PE@A!  B' B|)PP@A !  )! )!B SE@A!  B|"B!  |)! B| 7 )! )!  TE@A'!   |!B.5PP@A!  B7 )!  XE@A%!   7 B|)PPE@A! B|)"P@ Ak"$ BČ7A#!  4X> B|)"P@ Ak"$ BČ7A#! B< B|)! B| 7 Aj"$ Aj"$A B |B7 B(|B> B0|B7 B |B7 B(| 4X> B0| B|7  B |7 Ak"$ BČ7A#! A! B Ak"$ BČ7#!A!   7 B8|B7 B|B7 B|B7 B8|B7 B| 7 B| 7  B8|7 Ak"$ BČ7A#!  )!A!  B|B!A!   7 B| 7 Ak"$ BČ7A#!   7 B| 7 Ak"$ BČ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 0  Ak"$ )hP@ Ak"$ BȌ7A#!  )h) PP@A.!  )h)PP@A,!  )h1E@A+!   )h4"BXE@A(!  B! B X@A'!  #)0"P@ Ak"$ BȌ7A#!  )"P@ Ak"$ BȌ7A#!   B| B~|!  7@ ) )Q@A%!  )hB> )hB< )hB< )hB7 )hB7 )hB7@ )hB78B.5PP@A"!  )hB70 )hB7( )! )! )! B|!  T@A!   B|7  B|!B.5PP@A!   )h7 Aj"$ Aj"$A  )h Ak"$ BȌ7#!A!  B7 B| 7 B| 7 B| 7 B | 7 Ak"$ BȌ7A#!  B(|)! B0|)! )@ B8|)7B.5PP@A !  )@ 7 )@! ! ! !A! )@  Ak"$ BȌ7#!A! )hB0|B Ak"$ BȌ7#! )hB(|B Ak"$ BȌ7#!A! B|B7 B|B7 B|B7 B|B7 B| 7 B| 7  B|7 Ak"$ BȌ7A#! )@!A!  Aj"$ Aj"$A B|B!A !  Aj"$ Aj"$A Ak"$ BȌ7A#!  A!  Ak"$ BȌ7A#!  A!  A #!@@@@  #(M@ Ak"$ B̌7A#!  Ak"$ B7 B|B7 Ak"$ B̌7A#!   A #!@@@@  #(M@ Ak"$ BЌ7A#!  Ak"$ B7 B|B7 Ak"$ BЌ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .  Ak"$#)("PPE@A+!  ) B|R@A*!  #7  70#B(|!  7( 1@A !  4"B"PE@A!  )!B.5PP@A!  B7  7 )P@ Ak"$ BԌ7A#!  B.5P! )(! P@A!  ) 7(  7 Ak"$ BԌ7A#!  ) P@ Ak"$ BԌ7A#!  B8| B|7 B8|)!  ) 7 B| 7 Ak"$ BԌ7A#! Aj"$ Aj"$A   Ak"$ BԌ7#!A !  B|B Ak"$ BԌ7#!A!  BQE@A!  B|P@ Ak"$ BԌ7A#!  )H! B| 7A!    B|7 B| B|7 B| 7 Ak"$ BԌ7A#!  )0! )(!A! #7 B| 7 Ak"$ BԌ7A#! B|1E@A,! )P@ Ak"$ BԌ7A#! B.5P! )0)(! P@A(! ) 7(  )07 Ak"$ BԌ7A#!  Aj"$ Aj"$A )(  Ak"$ BԌ7#!A&!  Aj"$ Aj"$A Aj"$ Aj"$A B7 B|B+7 Ak"$ BԌ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B،7A#!  Ak"$ B7H B7|B< B|B7 B7|B< )X!A!  )! PPE@A!  )"P! )! PE@A!   7X  78  7@ B7 B| 7 B| 7 Ak"$ B،7A#!  B|)! B |)! B(|1E@A!  P@ Ak"$ B،7A#!  )!  7  Ak"$ B،7Av!A #!  B|)!  B|)7 B| 7 Ak"$ B،7A#!  B|)! )XB7B.5PP@A!  )X 7 )X!A!  )XB|  Ak"$ B،7#!A ! B7 B| )87 B| )@7 Ak"$ B،7A#! B|)! B |)! B(|1@A! )X!A! P@ Ak"$ B،7A#! )!  7  Ak"$ B،7Av!A #! B|)!  B|)7 B| 7 Ak"$ B،7A#! B|)! )XB7B.5PP@A!  )X 7 )X!A!  )XB|  Ak"$ B،7#!A!  B7|B< Ak"$ B،7A#!  Aj"$ Aj"$A @ Ak"$ B،7A#!  Ak/"AF Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B܌7A#!  Ak"$ )P@ Ak"$ B܌7A#!  ))"PP@A!  )12@A!  Ak"$ B܌7A#!  B7 B|B7 Ak"$ B܌7A#!  Ak"$ B܌7A#!  ))! ))!  7 B| 7 Ak"$ B܌7A#!  )10@A!  Ak"$ B܌7A#!  Ak"$ B܌7A#!  Ak"$ B܌7A#!  Aj"$ Aj"$A Ak"$ B܌7A#! B7 B|B 7 Ak"$ B܌7A#! Ak"$ B܌7A#! A !  Aj"$ Aj"$A  7 Ak"$ B܌7A#!  ))"P@ Ak"$ B܌7A#!  12@A!  Ak"$ B܌7A#!  B7 B|B7 Ak"$ B܌7A#!  Ak"$ B܌7A#!  A!  A ~#!@@@@@@@@   #(M@ Ak"$ B7A#!  A0k"$ )HPPE@A!  )@!B! )H! B|A#! B|B7 B| 7 B| 7 B | )87 B(| 7  B|7 Ak"$ B7A#!  A0j"$ Aj"$A )8P@ Ak"$ B7A#!  )8)("P@ Ak"$ B7A#!  )@! )!A!  A (~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$$%%%%%&'())*****+,-../0001123334444566678888899999:;<===>>>>>?@ABBBCCCCCDEFGGGGHIJJJKKKKLMNOPQRSTUVVWXXXXXYYZ[[\]^_`aabcdefgghijklmmnopqrsstuvwxyyz{|}~ #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  ))0!B! P@ Ak"$ B7A#!  1! B|! BT@A!  B WE@A!  B|!B S@A!  A!  B WE@A!  B!B! 1! B|! BT@A!  B WE@A!   B~ B BT|! B|!B S@A!  A!  B WE@A!    ! BT!  B |!B!B! 1! B|! BT@A#!  B WE@A!   B~ B BT|! B|!B S@A!  A!  B WE@A!  ))8! B!  78  }"P@ Ak"$ B7A#!  1!   B BT|BB|!A!  1! B|! BT@A/!  B WE@A!   B~ B BT|! B|!B S@A!  A(!  B WE@A!     B BT|!B!B! 1!  B|! BT@A:!  B WE@A!   B~ B BT|! B|!B S@A!  A4!  B WE@A!    B BT|!B!B! 1!  B|! BT@A!  B WE@A!   B~ B BT|! B|! B S@A!  A?!  B WE@A!  B B BT!  " B!  B BT|! PE@A!  B!A!  1!  B|! BT@A!  B WE@A!  B|!B S@A!  A!  B WE@A!  B! 1!  B|! BT@A!  B WE@A! ~ B|!B S@A! } A! | B WE@A! | B! 1!  B|! BT@A! z B WE@A! y B|!B S@A! x A! w B WE@A! w B|! B BTE@A! u B!A! s B|! B WE@A! r B!B!A(! p ))8 B}"P@ Ak"$ B7A#! q )! )B|! B.5PP@A! p ) 7  )4BPE@A! n B!  70  >$  >(  <#  7`  7h  7X  <"B! A! k 1! B|! BT@A! k B WE@A! j B~ B BT|! B|!B S@A! i A! h B WE@A! h   B BT|! B!B! 1! B|! BT@A! f B WE@A! e  B~ B BT|! B|!B S@A! d A! c B WE@A! c    B BT|!B!B! 1! B|! BT@A! a B WE@A! `  B~ B BT|! B|!B S@A! _ A! ^ B WE@A! ^  >,  7@   B BT|B|! ))8 B}!  7 B| 7 B| B7 Ak"$ B7A#! ] 5,B|! 5(! )@! )8! )0! 5$! )`! 1"! ! )h! )X! 1#! )@! B BTE@A! [ B! B!A! Y  7P ))8 }"P@ Ak"$ B7A#! Z  B!  <#  < )) !  7H  7 B| 7 B| 7 B| > Ak"$ B7A#! Y )HPPE@A! X )H11@A! W B.5PP@A! V )B7  )X7 B| 5$B7 Ak"$ B7A#! T )) "PPE@A! S 10@A! R )8! )0! )P! 1#!A! P 1#BP! B| < Aj"$ Aj"$A )hB Ak"$ B7#!A! M B!A! L  )B|! A! K  Ak"$ B7#!A! J B!A! I B7 B|B 7 Ak"$ BÁ7A#! I Ak"$ BŁ7A#! G Ak"$ Bǁ7A#! E B7 B|B 7 Ak"$ Bʁ7A#! C Ak"$ B́7A#! A Ak"$ B΁7A#! ? B7 B|B 7 Ak"$ Bс7A#! = Ak"$ BӁ7A#! ; Ak"$ BՁ7A#! 9 B7 B|B 7 Ak"$ B؁7A#! 7 Ak"$ Bځ7A#! 5 Ak"$ B܁7A#! 3 B7 B|B 7 Ak"$ B߁7A#! 1 Ak"$ B7A#! / Ak"$ B7A#! - B7 B|B 7 Ak"$ B7A#! + Ak"$ B7A#! ) Ak"$ B7A#! ' B7 B|B 7 Ak"$ B7A#! % Ak"$ B7A#! # Ak"$ B7A#! ! B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#! Ak"$ B7A#! Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ )0P"P!  <' @A !  B7 B| )87 B| )@7 B| 5H> B| 5H> Ak"$ B7A#!  1'"E@A! )0B7 B.5PP@A! )0B7( A(j"$ Aj"$A )0B(|B Ak"$ B7#!A!  B7 Ak"$ B7A#!  B.5P! B|)! P@A!  )0 7 )0 )(7 B.5P! B0|! P@A!  )0 7(A!  )0B(|  Ak"$ B7#!A!  )0  Ak"$ B7#!A!  A +~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$%&'()*+,--./0012345678899:::;;<=>?@ABBCCDDEFGHHHIJJKLLLLLMMNOPQRSTTUUVVWXYYYZZ[\]^_`abcdefghijklmnopqrstuvwxxxxxyz{|}~ #(A8jM@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  )#R@A!  4"BPP@A!  #7P )PP@A!  4BPP@A!  B|A#! B| )7 B| )7#P@ Ak"$ B7A#!  B|#) 7#B |!  7xB.5PP@A!  # B|7 B.7 B|B> Ak"$ B7A#!  B|!  )P7 B| )7 B| 7 Ak"$ B7A#!  )P)("PPE@A!  )PB(|! 1E@A!  ) "PPE@A!  B<1 B.5PP@A!  B7 1E@A!  B< B |!B.5PP@A!   B|7  7p  7h  7` 1E@A!   )P7 B| 7 Ak"$ B7A#!  B|1"E@A!  )h) "P@ Ak"$ B7A#!  10E@A! ~ B|B7 )P)( )hR@A! } B.5PP@A! | )hB7  <& )h)!  7( )h)!  78 @A! z B|1E@A! y B.5P! B|)! P@A! x )P 7 PPE@A'! v 12E@A'! u 11@A! t B.7 B|B> Ak"$ B7A#! s )P)(! 1&"E@A! r B!A+! p ! ! PPE@A:! o 1@A:! n 1@A/! m )(!A*! k  7@ PPE@A6! k B.5P! )(! P@A4! j  7( )(!  7H  7 Ak"$ B7A#! h )H! )@!A*! f B(|  Ak"$ B7#!A2! e B.5P! )(! P@A8! e )P 7(A2! c )p  Ak"$ B7#!A2! b B.5P! B|)! P@A! b )P 7 )P! ) "PP"E@A! _ 11E@A! ^ B.5P! )! P@A! ]  7 A=! [ )x  Ak"$ B€7#!A=! Z )x  Ak"$ BĀ7#!A B| > Ak"$ B߀7A#! I B!A! G  )hB|!A! F  B| Ak"$ B7#!A! E B.5PP@A! E B7 B.5P! )(! P@A! C )P 7(  7 Ak"$ B7A#! A A! ?   Ak"$ B7#!A! > B|B Ak"$ B7#!A! = B |B Ak"$ B7#!A! <  )P) 7 Ak"$ B7A#! <  )P) 7 Ak"$ B7A#! ; BP@ Ak"$ B7A#! ; BB7 Aj"$ Aj"$A  B| Ak"$ B7#!A ! 8 @A! 8 B>  )87  )(7 B7 Ak"$ B7A#! 6 B˜7 B|B7 Ak"$ B7A#! 5 )P )(7 )P ) 7 B7 Ak"$ B7A#! 3 B7 B|B7 Ak"$ B7A#! 2 B7 B|B7 Ak"$ B7A#! 0 Ak"$ B7A#! . B7 B|B7 Ak"$ B7A#! - Ak"$ B7A#! ,  )7 B| )7 Ak"$ B7A#! + Ak"$ B7A#! * Ak"$ B7A#! ) Ak"$ B7A#! ( B7 B|B7 Ak"$ B7A#! ' Ak"$ B7A#! % B7 B|B7 Ak"$ B7A#! $ Ak"$ B7A#! #  )7 B| )7 Ak"$ B7A#! " Ak"$ B7A#! ! Ak"$ B7A#! Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  )PP@ Ak"$ B7A#!  )P)0"P@ Ak"$ B7A#!  )!  7X )!  70 Ak"$ B7A#!   )X7 B| )07 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!   )7 B| )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#! B7 B|B7 Ak"$ B7A#! Ak"$ B7A#! B7 B|B7 Ak"$ B7A#! Ak"$ B7A#!   )7 B| )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A 7~#!@@  B|! B| 7 Aj"$A ~#!@@@@@@@@  #) "PPE@A!  12E@A!  B|B7 B|B7 Aj"$A 10@A!  ) )QE@A!  B<0 )! )! B| 7 B| 7 Aj"$A ~#!@@@@@@@@   A k"$ B|B7 B|B7 B|B7 B|B7 B| )(7 B| )07  B|7 Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  4BPE@A!  B> Ak"$ B7A#!  BP@ Ak"$ B7A#!  BB7 A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ )0P@ Ak"$ B7A#!  )0)! )0)! P"PE@A!   7 )0)!  7  T@A!  )0) T@A!  )0 78 )0 7@ )0B7` )0B7X  )0B8|7 Ak"$ B7A#!  A(j"$ Aj"$A )0)!  7 Ak"$ B7A#! Bچ7 B|B 7 Ak"$ B7A#!  )7 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   ) 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  Bď7 B|B 7 Ak"$ B7A#!   A #!@@@@  A(k"$ B|B7 B|B7 B|B7 B |B7 B|B7 B|#7 B| )(7 B | B0|7  B|7 Ak"$ B7A#!  BP@ Ak"$ B7A#!  BB7 A(j"$ Aj"$A A #!@@@@@@@@  Ak"$ B|B< B|A#! B|B7 B| )H7 B |#7 B(| )@7 B0| B|7 B8| B|7  B|7 Ak"$ B7A#!  B|1E@A !  BP@ Ak"$ B7A#!  BB> B7 Ak"$ B7A#!  BP@ Ak"$ B7A#!  BB7 Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ &  #(M@ Ak"$ B7A#!  A k"$ #7B*)P@A"!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!   4B|> ))0"P@ Ak"$ B7A#!  4"BSE@A !  B> ))0"P@ Ak"$ B7A#!  4B"P@A!  BQ@A!  BQ@A!  B> Ak"$ B7A#!  B(|B< A j"$ Aj"$A B> Ak"$ B7A#!  Bȹ7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B> Ak"$ B7A#!  A!  B> Ak"$ B7A#!  BХ7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B(|B< A j"$ Aj"$A B> B.7 B|B> Ak"$ B7A#! B.7 Ak"$ B7A#! BB.4SE@A!! B< Ak"$ B7A#! Ak"$ B7A#!  B(|B< A j"$ Aj"$A BB.4S@A!  A!  Ak"$ B7A#!  B7 B|B.7 Ak"$ B7A#!  Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ <  !"#$%&'()* #(M@ Ak"$ B7A#! , Ak"$ )HP@ Ak"$ B7A#! , )H5"PP@A.! + Ak"$ B7A#! * 4! B|1! B|1!  <"B SE@A! ) )H)0"P@ Ak"$ B7A#! ) ) )HR! )!  !  <#  )HR@A(! ( B WE@A%! ' Ak"$ B7A#! & BȚ7 B|B7 Ak"$ B7A#! % Ak"$ B7A#! $  )P7 B| )X7 B|B7 B| )H7 Ak"$ B7A#! # B.1E@A"! !  B.7 Ak"$ B7A#! B.7 B|B> Ak"$ B7A#!  B|5PP@A!  B| 1"< Aj"$ Aj"$A  B.7 Ak"$ B7A#!   B.7 Ak"$ B7A#!  A!  1#"E@A!  B.B<  )H7 Ak"$ B7A#!  A!  #)0"P@ Ak"$ B7A#!  B 4S@A !  A!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!   )H7 Ak"$ B7A#!   )P7 B| )X7 B|B7 B| )H7 Ak"$ B7A#!  A!   >$ Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   5$7 Ak"$ B7A#! Ak"$ B7A#! )H)!  78 )H)!  70 )H)!  7( Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )87 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!   )07 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )(7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@   Ak"$#)0! )PPE@A!  P@ Ak"$ B7A#!  ) )RE@A!  B |B< Aj"$ Aj"$A 4"BPPE@A! B |B< Aj"$ Aj"$A 4BPP@A! 4BPP@A! )PP@A!  4BPP@A!    )B|7 Ak"$ B7A#!  B|5B_BBRE@A!  B |B< Aj"$ Aj"$A ))pPP@A!  B |B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ V  !"#$%&'()*+,-./0123456667789:;<= #(M@ Ak"$ B7A#! ? Ak"$#)0"P@ Ak"$ B7A#! ? )"PP@A! > B!B!B!B!B!A! < )0B|! )@! 5$! ! 1!  7(  >$  7@  <  70 )PP@ Ak"$ B7A#! <   )PB|7 Ak"$ B7A#! ; B|5"BXE@A! : BQ@A! 9 BQE@A:! 8 )P1E@A9! 7 )P1E@A8! 6 )P)BuQE@A7! 5 )@ )P)0Q@A3! 4 B! E@A+! 2 1! )@! 5$!  >$  7@  < )0P@A&! 0 )(!  7( Ak"$ B7A#! . ) )(SE@A ! - B > Ak"$ B7A#! , )(!A! * Ak"$ B7A#! *  Ak"$ B7A#! ) )B'|!A! '  Ak"$ B7A#! ' )B|!A! %  )P7 B|B> B |B > Ak"$ B7A#! % B|1@A.! $ 1! )@! 5$!A! " )PB< )PB< )PBu7 )P)0!  78 P@ Ak"$ B7A#! #  B|7 Ak"$ B7A#! " B|5!  >  )P7 B|B > B |B> Ak"$ B7A#! ! 1! )8! 5 !A!  )@P@ Ak"$ B7A#!  )@B|7 Ak"$ B7A#!  5$B B|5Q!A!  B!A!  B!A!  B!A!  B X@A=!  B BPPE@A!  1! )@! 5$!A!  1!  <  )P7 B| > B | B > Ak"$ B7A#!  B|1@A!  1! )@! 5$!A!  )PB< )PB< )P )P)B|7 B| )P7 B|B< B| 1< Aj"$ Aj"$A 1!A>!  BQ@A!  BQE@A!  1! )@! 5$!A!  B QE@A;!   )P7 B|B > B |B> Ak"$ BȀ7A#! B|1@A! 1! )@! 5$!A! B!B!A>! B|B7 B|B< B|B< Aj"$ Aj"$A   B|7 Ak"$ Bπ7A#!  B|5BQ@A!  A!   )P7 Ak"$ BҀ7A#!  Bț7 B|B7 Ak"$ BӀ7A#!  B7 B|B'7 Ak"$ BՀ7A#!   A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ 1("@A!  ) P@ Ak"$ B7A#!    ) B|7 Ak"$ B7A#! B|5"B QE@A!  ) 7 B | B_> Ak"$ B7A#! 1)"@A! Aj"$ Aj"$A  ) 7 B|B7 B|B< Ak"$ B7A#!  A !  B_|BBX@A !  A!  Aj"$ Aj"$A  ) 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A #!@@@@@@@@@   Ak"$ #7#B<#P@ Ak"$ B7A#!  #1E@A!  B،7 Ak"$ B7A#!  )B< Aj"$ Aj"$A B7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B |B7 B |B7 B(|)"P@ Ak"$ B7A#!    )7 Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#! 4!  > B0|B7 B0|B7 B8|)"P@ Ak"$ B7A#!   )7 Ak"$ B7A#! )! )!  7 B| 7 Ak"$ B7A#! 4 4|"B B B|!BȠ' 7B T@A! Aj"$ Aj"$A Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  BȠ')7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ Ak"$ B7A#!  B.7 Ak"$ B7A#!  5PE@A !  )@! )0! )8!B!A!  B.)|"B?B7 |!B.  B B }7  |!  SE@A ! B.)"BXE@A! B }!B }!  B?!  XE@A!  }"   S!B. |!  B  }}B?|!  RE@A!  7  7  7 B| 7 B| 7 Ak"$ B7A#! )@! )0! )8! ) ! )!A!  Ak"$ B7A#!  A(j"$ Aj"$A  7 B| 7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!   4B|> 0!  B|< BP@A!   4B|> Aj"$ Aj"$A  7 B.7 Ak"$ B7A#!  )!A!  A ~#!@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  0!  B|< BBQ@A!  Aj"$ Aj"$A  B.7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ )8P@A!   )07 B| )87 B| )@7 Ak"$ B7A#!  #PPE@A!  #)!#)!#)! PPE@A! #)0"P@ Ak"$ B7A#! 4!B SE@A !  )07 B| )87 B| )@7 Ak"$ B7A#! A(j"$ Aj"$A #7  }! )8  )8 S!  B }B?|" )0R@A ! ) )! ) ) |" XE@A!  ) 7 A(j"$ Aj"$A  7  7 B| )07 B| 7 Ak"$ B7A#!  )!A !  A(j"$ Aj"$A  7 B| 7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 Ak"$ B7A#!  Aj"$ Aj"$A A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ 1"E@A!  B7 B|B7 Ak"$ B7A#!  Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!  A!  A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ *  !"# #(M@ Ak"$ B7A#! % A(k"$ +0 +0b@A(! $ +0 +0 +0a"E@A! # D +0c@A&! " E@A! ! +0Dc@A$! B|B7 B|B7 B|B+<D +0aE@A!  D? +0DcE@A !  B|B-< +0!B! B!A!  #!! B| B|| B0|< B|!  D$@! BS@A!  B| B|1< B|B.< B#|B< B$|B+< BSE@A!  B$|B-<B }! B%| B#!B0|< B #!! B&|  BB~BB B ~}B0|< B'|  B ~}B0|<  B|7 B|B7 B|B7 Ak"$ B7A#!  A(j"$ Aj"$A +0DcE@A#!  B|B-< +0! B!A!  B|! D$@! D$@ e@A!  A!  B|! D$@! D?c@A!  B!D@!A ! B|! D$@! BS@A!  !D$@ eE@A ! B|! D$@!A !  +0!A!  B7 B|B7 Ak"$ B7A#!  A(j"$ Aj"$A B7 B|B7 Ak"$ B7A#!  A(j"$ Aj"$A B7 B|B7 Ak"$ B7A#!  A(j"$ Aj"$A A #!@@@@@@@@@  #(M@ Ak"$ Bč7A#!  Ak"$ Ak"$ Bč7A#!  Bޔ 7 B|B7 Ak"$ Bč7A#!   +9 Ak"$ Bč7A#!   + 9 Ak"$ Bč7A#!  B7 B|B7 Ak"$ Bč7A#!  Ak"$ Bč7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@   #(M@ Ak"$ Bȍ7A#! Ak"$ B|B7 B |A #! )!B!A! B|! ! B SE@A!  B| |! B !   B ~}B0|< B TE@A!  BXE@A!  B }!  B| B }B?|7 B| 7 B| 7 Ak"$ Bȍ7A#!  Aj"$ Aj"$A  7 B|B7 Ak"$ Bȍ7A#!   A ~#!@@@@@@@@@  #(M@ Ak"$ B̍7A#!  Ak"$ )BS@A!  )!  7 Ak"$ B̍7A#!  Aj"$ Aj"$A B 7 B|B7 Ak"$ B̍7A#!  B )}!A!  A ~#!@@@@@@@@@@@@@@@   #(M@ Ak"$ BЍ7A#!  Ak"$ B|B7 B |A #! )!B!A! B|! B! B SE@A! B| |B B|1< BTE@A! B|"BTE@A ! B| |B< B~|"BTE@A !  B| |B0<B }!  B| B }B?|7 B| 7 B| 7 Ak"$ BЍ7A#!  Aj"$ Aj"$A  7 B|B7 Ak"$ BЍ7A#!   7 B|B7 Ak"$ BЍ7A#!   A ~#!@@@@  #(M@ Ak"$ Bԍ7A#!  Ak"$ )!  7 Ak"$ Bԍ7A#!  Aj"$ Aj"$A A #!@@@@  #(M@ Ak"$ B؍7A#!  Ak"$  )7 Ak"$ B؍7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B܍7A#!  Ak"$ B| )H7 B | )P7 B(|B7 B(|B7 B(|B7 B(| B|)7 B |)! B0| 7 B |)! B8| 7  B(|)7 B| 7 B| 7 Ak"$ B܍7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! A k"$ B(|)!  7 B(|)!  7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!    B(|)7 Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ,  !"#$%&'( #(M@ Ak"$ B7A#! * Ak"$ Ak"$ B7A#! ) B'|B< B'|B <B!A! ' )0B|!  )`|" )hTE@A'! &  70  7@ BPE@A ! % PP@A#! $ B)!  7B$  Ak"$ B7Av!A #! # Ak"$ B7A#! " B7 B|B7 Ak"$ B7A#! ! Ak"$ B7A#! )pPP@A !   B'|7 B|B7 B|B7 Ak"$ B7A#!  )@"P@ Ak"$ B7A#!  )!  7(B)!  7B$  Ak"$ B7Av!A #!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!   )(7 Ak"$ B7A#!   B|)! B|)! PPE@A!   7P  7 B| 7 Ak"$ B7A#!  B|)!  78 B|)!  7H )P)!  7@ Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )H7 B| )87 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )( )@}7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A! )p)!  )@7 )p$  Ak"$ B7Av!A #! B|1! B'| < B'|1PE@A ! B'|B <A ! Ak"$ B7A#! Ak"$ B7A#! Ak"$ B7A#!  )@!A!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ X  !"###$%&'(()***+,-./0123456778 #(M@ Ak"$ B7A#! : Ak"$ B7@ B7H B'|B< #70#)0"P@ Ak"$ B7A#! : )"P@ Ak"$ B7A#! : B7B'B7B'Bֹ7B.B<#)0"P@ Ak"$ B7A#! :   5B|> )0P@ Ak"$ B7A#! : )0)0B(R@A! 9 B(B< Ak"$ B7A#! 8 )!B. 7 P@A! 7 B.4BPPE@A! 6 B.#)7B.B< B'7 Ak"$ B7A#! 4 B&|B< B|B7 B| B&|7 B'|B< Ak"$ B7A#! 3 B7 B|B7 Ak"$ B7A#! 2 B.5P! B|)! P@A! 1 B' 7 B.1E@A!! / B')PPE@A! . B')PPE@A! - B')PPE@A! , B')"PPE@A! +  7 B|B7 Ak"$ B7A#! * B'7 Ak"$ B7A#! ( B.B< B')7 Ak"$ B7A#! ' B&|B< #78#)0"P@ Ak"$ B7A#! '  5P@A! & )8P@ Ak"$ B7A#! & )8)0"P@ Ak"$ B7A#! &  5B|>B.1@A! % B.1@A! $ Bȋ$Bȋ) Ak"$ B7Av!A #! # B.7 Ak"$ B7A#! " B|5PPE@A9! ! B!A6!  Ak"$ B7A#!  )(B|! BSE@A9!   7( B.7 Ak"$ B7A#!  B|5PE@A4!  B.7 Ak"$ B7A#!  B|5PP@A!  B> Ak"$ B7A#!  BP@ Ak"$ B7A#!  BB>A=!  B7 B|B7 B|B< B|B< B|B7 Ak"$ B7A#!  A;!  B'|B<  B|)7 Ak"$ BÀ7A#!  Aj"$ Aj"$A B7 Ak"$ Bƀ7A#!  A)!  B'  Ak"$ Bɀ7#!A!  B7 B|B%7 Ak"$ Bˀ7A#!  B7 B|B7 Ak"$ B̀7A#! B٣7 B|B7 Ak"$ Bπ7A#! B7 B|B7 Ak"$ Bр7A#!  B7 B|B7 Ak"$ BӀ7A#!  B7 B|B7 Ak"$ BՀ7A#!  @ Ak"$ B؀7A#!  Ak/"AF Aj"$ Aj"$A A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B> B|B7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$B.5P"P@A!  B'#7 A !   Ak"$ B7A#!  )! B7 B| 7 B|B> Ak"$ B7A#!   B'7 Ak"$ B7A#! B'5PP@A! B'7 B|B> Ak"$ B7A"#!  B7 B|B'7 B|B< B|B< B|B7 Ak"$ B7A#! BB.4SE@A! Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!  Ak"$ B7A#!  A!  B'# Ak"$ B7#!A !  Bը7 B|B7 Ak"$ B7A#!   A #!@@@@@  Ak"$ Ak"$ B7A#!  B7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@ *  #(M@ Ak"$ B7A#!  A k"$ 18BBR@A&!  #)0"P@ Ak"$ B7A#!    4B|>#)0!  7 P@ Ak"$ B7A#!  #P@ Ak"$ B7A#!  )!  7 P@ Ak"$ B7A#!    B|7 Ak"$ B7A#!  B|5"BRE@A!  B R@A(!  B.5PP@A#! ) )07 ) )(7 ) 18< ) 19< ) )@7 )4! ) B|> BBQE@A ! #P@ Ak"$ B7A#! #1E@A ! #Bu7 B7 Ak"$ B7A#!  A j"$ Aj"$A )B| )0 Ak"$ B7#! )B| )( Ak"$ B7#!A!  Ak"$ B7A#!  A!  B7 B|B7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  A k"$ B|B7 B|B7 B|B7 B|B7 B| )(7 B| )07  B|7 Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ V  !!"#$%&&'()*+,- Ak"$#)0"P@ Ak"$ B7A#! /   4B|>#)0!  7@ P@ Ak"$ B7A#! / )!  7X P@ Ak"$ B7A#! / #P@ Ak"$ B7A#! /  )"PE@A2! .  B'7 Ak"$ B7A#! - )XB|!  7P )X! ) )BSE@A,! + B')"PPE@A,! * B.5P! )! P@A)! ) B' 7 B7 )"B|! )! )" T@A#! '  B|7  B|!B.5PP@A!! &  7A! $   Ak"$ B7#!A! #  7H B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B7A#! # B(|)! B0|)! )X B8|)7B.5PP@A'! " )X 7 )H! ! ! )P! )X!A!  )P  Ak"$ B7#!A&!  B'  Ak"$ B7#! B|B Ak"$ B7#!A!   B'7 Ak"$ B7A#!  )X)P@A!  )X)! )X)! B|" TE@A!   B|")!B.5PP@A!  B7 )X)!  XE@A!  )X 7 P@ Ak"$ B7A#!  )PP@A!   )@4! )@ B|> BBQE@A!  #P@ Ak"$ B7A#!  #1E@A!  #Bu7 B| 7 Aj"$ Aj"$A B Ak"$ BÀ7#!A5!  B7 Ak"$ Bŀ7A#!  )X)! )X)! )X)! B|! B|)!  T@A!  )X B|7  B|!B.5PP@A!   7A2!   Ak"$ Bɀ7#!A2!  7H B7 B| 7 B| 7 B| 7 B | 7 Ak"$ Bˀ7A#! B(|)! B0|)! )X B8|)7B.5PP@A! )X 7 )H! ! ! !A!  )P  Ak"$ Bπ7#!A!  B7 B|B*7 Ak"$ Bр7A#!   7 B| 7 Ak"$ BӀ7A#!   7 B| 7 Ak"$ BՀ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ f  !!!"#$%&&'''()*+,-./0123456789: Ak"$ )hP@ Ak"$ B7A#! < )h)PP@A! ; )h14@A! : )h)PP@A! 9 )h)PP@A! 8 )h)@PP@A! 7 )h)PPP@A! 6 #)PP@A! 5 #)0"P@ Ak"$ B7A#! 5   4B|>#)0!  7@ P@ Ak"$ B7A#! 5 )!  7X P@ Ak"$ B7A#! 5 #P@ Ak"$ B7A#! 5  ) )QE@A;! 4 B!B!A! 2 ! )! )! )! B SE@A,! 1 B|" TE@A! 0  B|")!B.5PP@A*! / B7 )!  XE@A! -  7 PPE@A)! , P@ Ak"$ B7A#! , B.5PP@A'! +  7A! ) B|  Ak"$ B7#!A! ( !A! ' B Ak"$ B7#!A!! &  7P  7H B'7 Ak"$ B7A#! & )HP@ Ak"$ B7A#! & B.5P!B')! P@A! % )H 7B' )P7  B'7 Ak"$ B7A#! # )X)"B|! )X)! )X)" T@A! ! )X B|7  B|!B.5PP@A!  )h7  )@4! )@ B|> BBQE@A!  #P@ Ak"$ BÀ7A#!  #1E@A!  #Bu7 Aj"$ Aj"$A  )h Ak"$ Bʀ7#!A?!  B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B̀7A#!  B(|)! B0|)! )X B8|)7B.5PP@A!  )X 7 ! !A=!  )XB|  Ak"$ BЀ7#!A!  )HB|  Ak"$ BҀ7#!B' )P Ak"$ BӀ7#!A5!   7 B| 7 Ak"$ BՀ7A#!   7 B| 7 Ak"$ B׀7A#!  B7 B|B+7 Ak"$ Bـ7A#!  B7 B|B7 Ak"$ Bۀ7A#! B7 B|B$7 Ak"$ B݀7A#! B7 B|B 7 Ak"$ B߀7A#! B7 B|B 7 Ak"$ Bင7A#!  B7 B|B&7 Ak"$ B〄7A#!  B7 B|B 7 Ak"$ B倄7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B$7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B 7 Ak"$ B7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@  A k"$B')!B')! B7 B| 7 B| > Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@  A k"$B')!B')! B7 B| 7 B| > Ak"$ B7A#!  A j"$ Aj"$A A ]#!@@@@  Ak"$ B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@ )  #(M@ Ak"$ B7A#!  Ak"$ )HP@ Ak"$ B7A#!    )HB|7 Ak"$ B7A#!  5P@A'!   B.7 Ak"$ B7A#!  B')"B|!B')!B')" T@A!  B' B|7  B|!B.5PP@A!   )H7 B')"P!B')! PE@A%!  B')R@A!  B')! B.7 B| 7 Ak"$ B7A&#!   B.7 Ak"$ B7A#!  Aj"$ Aj"$A B'7 B| 7 Ak"$ B7A#! A!  )H Ak"$ B7#!A! B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B7A#! B(|)! B0|)!B' B8|)7B.5PP@A#! B' 7 ! !A!  B'  Ak"$ B7#!A"!  B7 B| 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A'#!  )!  7 B'7 Ak"$ B7A#!  )! B | 7 B(| )7 Aj"$ Aj"$A A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 Ak"$ B7A #!  B.B.1<B.B.1<B.B.1<B.B.1<B.B.1< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ O  !"#$%&&'''(()*+, #(M@ Ak"$ B7A#! . Ak"$ #7@B'B>B'!A! ,  78  7 Ak"$ B7A#! , )8)! PP@A! * Ak"$ B7A#! ) Ak"$ B7A#! (  B.7 B|B7 B|B7 Ak"$ B7A#! ' )@P@ Ak"$ B7A#! '  )@)07 B|B7 Ak"$ B7A#! & Ak"$ B7A#! % Ak"$ B7A>#! $ Ak"$ B7A#! # Ak"$ B7A#! " Ak"$ B7A#! ! )@)0P@ Ak"$ B7A#! ! Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!   B'7 Ak"$ B7A#!   Ak"$ B7A#!  B' )7B.4!  >$ Bň7 B|B 7 Ak"$ B7A#!   B|)!  B|)7 B| 7 Ak"$ B7A#!  B|)"B B Q! B !  B|1E@A!  B B B SE@A!   > Ak"$ B7A#!  B|)PP@A!   B'7 Ak"$ B7A#!  B.4!B SE@A?!  B.B<B.B<B')!B')!B SE@A?!   70B!A;!  )HB|! !  7(  7H )"P@ Ak"$ B7A#!   B-|7 Ak"$ B7A#!  )(B|" )0S@A:!  B')"PE@A! B'B7B.5"PP@A! B'B7 B')BQE@A! B'B7B.5PP@A! B'B7 Aj"$ Aj"$A B'B Ak"$ Bɀ7#!A!  B'B Ak"$ Bˀ7#!A!  4$!A0!  B7 B|B+7 Ak"$ B΀7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@ '  #(M@ Ak"$ B7A#!  A(k"$ )0P@ Ak"$ B7A#!  #7   )0B|7 Ak"$ B7A#!  )0)!  7 5!  > Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )07 Ak"$ B7A#!  Bց7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   57 Ak"$ B7A#!  Ak"$ B7A#! Ak"$ B7A#! ) P@ Ak"$ B7A#!   ) B|7 Ak"$ B7A#! ) )!  7 5!  > Ak"$ B7A#! B؜7 B|B7 Ak"$ B7A#!  ) 7 Ak"$ B7A#!  Bց7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   57 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  A(j"$ Aj"$A A ~#!@@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$B')B')}B B !B'4" S@A! Aj"$ Aj"$A  > Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   47 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$B')"B|" S@A!   7B' 7 Ak"$ B7A#!  B | )7 Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ >  #(M@ Ak"$ B7A#!  A8k"$#)0"P@ Ak"$ B7A#!  # )R@A:!   B'7 Ak"$ B7A#!  B )HWE@A6!  )@P@ Ak"$ B7A#!  )@ )H7 )@P@ Ak"$ B7A#!  B.)! )@)! B0| 7  B0|7 B| 7 Ak"$ B7A#!  )@ B|)> Ak"$ B7A#!  B.)! )! B(| 7  B(|7 B| B7 Ak"$ B7A#!  B|)! )@ > )@5 BPE@A !  )@B>  )@7 Ak"$ B7A#!  )@)P"PPE@A#!   )B|7 B.5P!B')! P@A4!  )@ 7 B'7 B| )@7 Ak"$ B7A#!   B'7 Ak"$ B7A#! B.1@A.! A8j"$ Aj"$A B7 Ak"$ B7A#! B.5P! B|)! P@A2! )@ 7A,!  )@B|  Ak"$ B7#!A,!  )@B|  Ak"$ B7#!A%!  Ak"$ B7A#!  )@P@ Ak"$ B7A#!  )@ )7A !  )@P@ Ak"$ B7A#!  B7 B| )@B|7 B|B 7 B|B 7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@ .  #(M@ Ak"$ B7A#!  A(k"$B*1@A(!  )0P@ Ak"$ B7A#!    )0B|7 Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!   B|5!  4B|>#P@ Ak"$ B7A#!  B_"B!#)0! BR@A+!  #7  7  )07 B|B> B |B> Ak"$ B7A#! ) )0"P@ Ak"$ B7A#! )!  7 B| )07 B| 1@< Ak"$ B7A#! Ak"$ B7A#! )P@ Ak"$ B7A#!  )4! ) B|> BBQE@A&! #P@ Ak"$ B7A#! #1E@A&! #Bu7 A(j"$ Aj"$A  )07 B| )87 Ak"$ B7A#!  A!   )07 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@   #(M@ Ak"$ BĎ7A#!  Ak"$ B.7 B|B> Ak"$ BĎ7A"#! B!A! B> Ak"$ BĎ7A#! )B|! BSE@A !  7B'B> B'7 B|B> Ak"$ BĎ7A"#!  Ak"$ BĎ7A#!  1@A!  B> Ak"$ BĎ7A#!  Ak"$ BĎ7A#!  B> Ak"$ BĎ7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ BȎ7A#! Ak"$ 5(B_|"BBXE@A!  5(B_B 5,BQE@A !  ) P@ Ak"$ BȎ7A#!   ) B|7  5(>  5,> Ak"$ BȎ7A$#!  1E@A !  Aj"$ Aj"$A 5(BB Q@A!  A!  Ak"$ BȎ7A#!  B7 B|B'7 Ak"$ BȎ7A#!   ) 7 Ak"$ BȎ7A#!  Bʅ7 B|B 7 Ak"$ BȎ7A#!   5(B7 Ak"$ BȎ7A#!  B7 B|B 7 Ak"$ BȎ7A#!   5,B7 Ak"$ BȎ7A#!  Ak"$ BȎ7A#!  Ak"$ BȎ7A#!   ) 7 Ak"$ BȎ7A#!  B7 B|B47 Ak"$ BȎ7A#!  Ak"$ BȎ7A#! B7 B|B+7 Ak"$ BȎ7A#!  ) 7 Ak"$ BȎ7A#! Bʅ7 B|B 7 Ak"$ BȎ7A#!  5(B7 Ak"$ BȎ7A#! B7 B|B 7 Ak"$ BȎ7A#!   5,B7 Ak"$ BȎ7A#!  Ak"$ BȎ7A#!  Ak"$ BȎ7A#!   ) 7 Ak"$ BȎ7A#!  B7 B|B77 Ak"$ BȎ7A#!   A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ B̎7A#!  Ak"$ 5(B|"BBXE@A!  5,B 5(B BQE@A! ) P@ Ak"$ B̎7A#!  ) B|7 B| 5(> B | 5,> Ak"$ B̎7A$#! B|1! B0| < Aj"$ Aj"$A Ak"$ B̎7A#! B7 B!7 Ak"$ B̎7A#!  5(B7 Ak"$ B̎7A#!  B7 B7 Ak"$ B̎7A#!   5,B7 Ak"$ B̎7A#!  Ak"$ B̎7A#!  Ak"$ B̎7A#!  B7 B7 Ak"$ B̎7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ )  Ak"$ 5XB "BPPE@A%!  B8|B7 B|B> B|B> B8|B7 B| 5X> B| 5\>  B8|7 Ak"$ BЎ7A#!  B!B!A !   7 B> Ak"$ BЎ7A#!  )B|! )0! )P! B SE@A !   5R@A!  ) ! )(B|! ! !  7  7( )PP@ Ak"$ BЎ7A#!   )PB|7 B| 5X> B | 5\> Ak"$ BЎ7A$#!  B|1@A$!  5XB"BQE@A!  )P5BQ@A'!   70 )(P@A!  ) !  7  Ak"$ BЎ7A#!  ) ) SE@A! )0! )P!B!A! Ak"$ BЎ7A#!  Ak"$ BЎ7A#! )B|! )P!A !   Ak"$ BЎ7A#!  )B'|!A!  Aj"$ Aj"$A 5\B BPP@A!  5\B 5XBQ@A!  A!  B7 B|B17 Ak"$ BЎ7A#!   A ~#!@@@@@@@@@@   #(M@ Ak"$ BԎ7A#! Ak"$ 5(B"BR@A!  5,BB R@A!  ) P@ Ak"$ BԎ7A#!   ) B|7 B|B> B |B > Ak"$ BԎ7A$#!  B|1E@A!  Aj"$ Aj"$A Bؚ7 B|B7 Ak"$ BԎ7A#!   A ~#!@@@@@@@@@   #(M@ Ak"$ B؎7A#!  Ak"$ 5(BB R@A!  5,BBR@A!  ) P@ Ak"$ B؎7A#!   ) B|7 B|B > B |B> Ak"$ B؎7A$#!  B|1! B0| < Aj"$ Aj"$A Bؚ7 B|B7 Ak"$ B؎7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Q  !"#$$$$%&'()*+,-. #(M@ Ak"$ B܎7A#! 0 Ak"$#)0"P@ Ak"$ B܎7A#! 0 4!B S@A! / #7H B'7 Ak"$ B܎7A#! . B'B.4> B'7 B|B> Ak"$ B܎7A"#! - Ak"$ B܎7A#! , )HP@ Ak"$ B܎7A#! , )H)0"P@ Ak"$ B܎7A#! , )"P@ Ak"$ B܎7A#! ,  B>B'B'4B|>B')!B')!B SE@A ! +  70B!A! ) B|!  7(  7P )!  78 P@ Ak"$ B܎7A#! ) 5"BQ@A! ( B! E@A! & B*1@A! %  5B|>B'B'4B|> B|" S@A! # A ! " B>B'B'4B|> Ak"$ B܎7A#! ! )"PP@A! B'4!  > B'7 Ak"$ B܎7A#!  B 4S@A!  B'4BPP@A?!  B')!B')!B SE@A=!  B!B!B!A,!  B|! )"P@ Ak"$ B܎7A#!  B|! 5BR!B.  !B  !  S@A+!   7@  7 B.7 Ak"$ B܎7A#!  B|5PP@A3!  ) PP@A!  Aj"$ Aj"$A  B.7 Ak"$ B܎7A#!   B.7 Ak"$ B܎7A#!  A1!  B!B!A/!  B)!B!A/!  Ak"$ B܎7A#!  B'7 B|B7 Ak"$ BÀ܎7A#! B|1E@A! B'B7A(!  7 Ak"$ Bɀ܎7A#!  )87 Ak"$ Bʀ܎7A#! )0! )P! )(! )8!A!   B|7 B| > B |B> Ak"$ B̀܎7A$#!  B|1! )0! )P! )(! )8!A!   )@7 B| ) 7 Ak"$ B΀܎7A#!  B7 B|B7 Ak"$ BЀ܎7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ N  ! #(M@ Ak"$ B7A#! # Ak"$#)0"P@ Ak"$ B7A#! #   4B|>#P@ Ak"$ B7A#! # #)0!  7@ B.7 Ak"$ B7A#! " 5PP@A! !  B'7 Ak"$ B7A#! B.4!B.4"BPPE@A!  B.B>  > Ak"$ B7A#!  B|)!  78B'B>B'5PP@A!   B'7 Ak"$ B7A#!  )8!A!  )H! PPE@A0!  )! )8"P! !  7H PE@A.!   ! B78 P@ Ak"$ B7A#!  )PP@A!    7  B|7 Ak"$ B7A#!  A!  B7 B| 7 B|B7 Ak"$ B7A#!  A!   Ak"$ B7A#!  )!  7( 1X"@A!  Ak"$ B7A#!  )@P@ Ak"$ B7A#!   )@4! )@ B|> BBQE@A!  #P@ Ak"$ B7A#!  #1E@A! #Bu7 B| )(7 Aj"$ Aj"$A  B < B|B7 B|B7 B|B7 B |B7 Ak"$ Bŀ7A#! A5!  B'B> B'7 Ak"$ BȀ7A#!  A!  !A!  B0|B7  B0|7 Ak"$ Bˀ7A#!  A!  B7 B|B%7 Ak"$ B̀7A#!   A ~#!@@@@@@@@@@   A k"$ #7#)P!  < E@A!  #)! B| 7 PE@A!  B|B7 )P@ Ak"$ B7A#!  B|! ) 7 )  B|)}B|7 )P@ Ak"$ B7A#!  ))B|! ) 7 ) 7 Ak"$ B7A#!   1< Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  )#R@A!  #7  )7 B| B |7 Ak"$ B7A#!  Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))0B(Q@A!  ))0"P@ Ak"$ B7A#!  )"PP@A! ))0!B( R@A! Ak"$ B7A#! Aj"$ Aj"$A P@ Ak"$ B7A#! )!  7 Ak"$ B7A#! ))0"P@ Ak"$ B7A#! B7A !  $ ) Ak"$ B7Av!A #!  A !  Ak"$ B7A#!  A !  B7 B|B7 Ak"$ B7A#!   A #!@@@@@@@@  #(M@ Ak"$ B7A#!  B.1E@A!  B.1E@A!  Aj"$A B.B< Ak"$ B7A#!  A!  A ~#!@@@@@@@@  Ak"$ #7 )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!   B|7 Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!   B7 Ak"$ B7A#!  1@A!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ G  !!!!""####$$%&' #(M@ Ak"$ B7A#! ) Ak"$#)0!B( Q@A7! (  7 P@ Ak"$ B7A#! ( )P"PP@A0! '  B'7 Ak"$ B7A#! & )!B'!A ! $ B|! )"PPE@A5! #  QE@A ! " B.5P! )! P@A.! !  7 1 "E@A(!   B'7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!   B'7 Ak"$ B7A#!  B'B')B|7 Ak"$ B7A#!   B'7 Ak"$ B7A#!  1 "E@A&!  Aj"$ Aj"$A  )B|7 Ak"$ B7A#!  Aj"$ Aj"$A  B|7 B|B> Ak"$ B7A"#!  B.5P!B')! P@A+!  ) 7B' )7A!  )B|  Ak"$ B7#!B' ) Ak"$ B7#!A!    Ak"$ B7#!A!  )!  )7 B| 7 Ak"$ B7A#!  B.5PP@A3!  )B7PA! )B|B Ak"$ B7#!A! Bˤ7 B|B7 Ak"$ B7A#! Ak"$ B7A#! Ak"$ B7A#!   B'7 Ak"$ B7A#!  B'B')B|7 Ak"$ B7A#!   B'7 Ak"$ BÀ7A#!  Ak"$ Bŀ7A#!  B7 B|B7 Ak"$ Bƀ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !""""##$%%%%&&&&&&'''())***+,-.////012345678999:;;<=>?@AB #(M@ Ak"$ B7A#! D Ak"$ #7H#)0"P@ Ak"$ B7A#! D   4B|>#)0"P@ Ak"$ B7A#! D )HP@ Ak"$ B7A#! D )! )H)0!  7@ !  7X B'7 Ak"$ B7A#! C B'4"BPP@A! B B'B.4B|>B.5PP@A! A B' )h7 B')!B')!B SE@A! ?  7( )X!B!A! = B|! )" R@A! < B|" S@A! ; Ak"$ B7A#! : B')! !A&! 8 )0)! ! PPE@A-! 7  70  B|7 B|B> B |B> Ak"$ B7A$#! 6 B|1E@A"! 5 )hP@ Ak"$ B7A#! 5 )h)!  )07 )h$  Ak"$ B7Av!A #! 4 B'B'4B|>A"! 2 B'4!  > B'7 Ak"$ B7A#! 2 )hP@ Ak"$ B7A#! 2 )h)!  )X7 )h$  Ak"$ B7Av!A #! 1 B')!B')!B SE@A?! 0  7(B!A8! . B|!  7  7P )!  78 P@ Ak"$ B7A#! . 5"BQE@A! - 5MBQ@A! , B! @A! * B|" S@A7! ) B 4S@A! ( B'4BPP@A! ' B')!B')!B SE@A! & B!A! $ B|! )"P@ Ak"$ Bŀ7A#! $ 5MPP@A! # B|" S@A! "  B'7 Ak"$ B̀7A#! ! B.5PP@A! B'B7  B'7 Ak"$ BӀ7A#!  )@P@ Ak"$ BՀ7A#!   )@4! )@ B|> BBQE@A!  #P@ Ak"$ Bڀ7A#!  #1E@A!  #Bu7 Aj"$ Aj"$A B'B Ak"$ B7#!A!  Ak"$ B7A#!  B'7 B|B7 Ak"$ B7A#!  B|1E@A!  B'B7A!  B*1@A!   5B|>  7 Ak"$ B7A#!  )(! )P! ) !A>!   7 Ak"$ B7A#!   )87 Ak"$ B7A#!  )8!A!  B|7 B| > B |B> Ak"$ B7A$#! B|1! )(! )P! ) ! )8!A=! B!A=!  7  7P P@ Ak"$ B7A#!  B|7 B|B> Ak"$ B7A"#! )(! )P! )X! ) !A!  B' )h Ak"$ B7#!A!  B7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B7 B|B"7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$#)0"P@ Ak"$ B7A#! )!  7 P@ Ak"$ B7A#!  B|!  7 B|B> B |B> Ak"$ B7A$#! 1E@A! B')"P@ Ak"$ B7A#! )!  )7 $  Ak"$ B7Av!A #!  B'7 Ak"$ B7A#!  B'4!B' B|> BBQ@A!   B'7 Ak"$ B7A#!  A j"$ Aj"$A B'7 Ak"$ B7A#!  A!  A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ S  !!""#$%&&''(()*+ #(M@ Ak"$ B7A#! - Ak"$ #7(#)0"P@ Ak"$ B7A#! -   4B|>#P@ Ak"$ B7A#! - #)0"P@ Ak"$ B7A#! - )P@A! , B')PPE@A&! +  B'7 Ak"$ B7A#! * B')! B | 7B! B |)"PPE@A! ( 5PPE@A! ' )!B.5PP@A! &  7 B |)! B | 7A! # B|  Ak"$ B7#!A! "  7 B0|B7 B8|B7 B0|B7 B8| B |7  B0|7 Ak"$ B7A#! " B |)"P@ Ak"$ B7A#! " )! B | 7 )!A! B.5PP@A! B' 7  B'7 Ak"$ B7A#!  B7 Ak"$ B7A#!  B|)!B.5PP@A!   )P7  7  7 B| )X7 Ak"$ B7A#!  B.1E@A!  B> Ak"$ B7A#!  B.5P! B|)! P@A!  ) 7 ))"P@ Ak"$ B7A#!  B.5PP@A!   )70 )()0"P@ Ak"$ B7A#!  )! )H Q@A!  )()0"P@ Ak"$ B7A#!   4!  B|> BBQE@A!  #P@ Ak"$ B7A#!  #1E@A!  #Bu7 B| )7 Aj"$ Aj"$A Ak"$ BÀ7A#! A6! B0| ) Ak"$ Bŀ7#!A2! )  Ak"$ Bǀ7#!A.! B> Ak"$ Bɀ7A#! B.5P! B|)! P@A!  ) 7A.!  )  Ak"$ B̀7#!A.!  B| )P Ak"$ B΀7#!A)!  B'  Ak"$ BЀ7#!A !   )H7 Ak"$ BҀ7A#!  A !  A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  A(k"$ B.7 B|B> Ak"$ B7A#! 5"PPE@A !  >B!A!  > Ak"$ B7A#! 5B|! 5! B T@A!  A(j"$ Aj"$A B< Ak"$ B7A#!  B|)!  7  ! B.7 B| 7 Ak"$ B7A&#!  ) PP@A!  Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@ %  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 B|B7 Ak"$ B7A#!  B|)!  7 B > Ak"$ B7A#!  B|)!  7( B0|B7 B8|B7 P@ Ak"$ B7A#!  B8|)"P@ Ak"$ B7A#!    )B|7@ )B`|!  78 B7`  7H  )@7x  7p  )87  7 B|B> B |B> Ak"$ B7A#!  B.5PP@A#! )( ) 70 ) P@ Ak"$ B7A#! B.5PP@A!! ) )(7 ) ) 5B|> ) )(7 )( ) 7 B'7 B|B7 Ak"$ B7A#! )( B|)7  )(7 Ak"$ B7A#!  B'7 B|B> Ak"$ B7A#!  B< Ak"$ B7A#!  B|)! ) 7B.B.5B|> ) ! B.7 B| 7 Ak"$ B7A&#!  Aj"$ Aj"$A ) B| )( Ak"$ B7#!A!  )(B0| ) Ak"$ B7#!A !  A ~#!@@@@@@@@@@@@@@@@@@@@   A0k"$B!  <' B.7 Ak"$ B7A'#!  B|)"BQ@A!   7( PE@A !  18"@A !  1'"E@A ! B> Ak"$ B7A#! B!A! B.7 B|B> Ak"$ B7A#! A!  B.7 B|B7 Ak"$ B7A%#!  B|1@A!  Ak"$ B7A#!  1'!A!  )(! B| 7 A0j"$ Aj"$A Ak"$ B7A#!  1'!A!  A ~#!@@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ B7A#!  A(k"$  )87 B| )07 B| )@7 Ak"$ B7A#!  )"P@ Ak"$ B7A#!   )8PP<  )87#PPE@A !  #)0"PPE@A !   7 5PP@A !  1@A !  7 Ak"$ B7A#! A(j"$ Aj"$A  B.7 Ak"$ B7A#! B.5P@A! ) B.)7B. ) 7B.1@A!   B.7 Ak"$ B7A#!  A(j"$ Aj"$A B.B< B.7 Ak"$ B7A#!  A!  B7 B|B*7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$B.1E@A! B(|B7 B(|B7 B(|B7B')PPE@A! )HP@ Ak"$ B7A#!  B(| )H)7 B0| )HB|7 B|B7 B|B7 B |)"P@ Ak"$ B7A#! )! B8| 7 B.7 Ak"$ B7A#! B')7 B| B(|7 Ak"$ B7A#! B.7 Ak"$ B7A#!  Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!  B.7 Ak"$ B7A#!   B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@  A k"$ #7#)0"P@ Ak"$ B7A#!  B|!  7 Ak"$ B7A#! )P@ Ak"$ B7A#! ))0"P@ Ak"$ B7A#! )"PP!  < E@A! B.5PP@A!  B7 )! B< $  Ak"$ B7Av!A #!  ))0"P@ Ak"$ B7A#!   B|!  7 Ak"$ B7A#!  B(| 1< A j"$ Aj"$A B7 B|B37 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  4BPP@A!  )PP@A!  1@A!  #7 B'7 Ak"$ B7A#! )P@ Ak"$ B7A#!  ))07 Ak"$ B7A#!  B'7 Ak"$ B7A#! Ak"$ B7A#! ))0"P@ Ak"$ B7A#! )!  7 Ak"$ B7A#! ))0"P@ Ak"$ B7A#! B7 Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!  Bə7 B|B7 Ak"$ B7A#!  Bզ7 B|B7 Ak"$ B7A#!   A ~#!@@@  #(M@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  B< Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ i  !"#$%&'()*+ #(M@ Ak"$ B7A#! - A8k"$#)0"P@ Ak"$ B7A#! -   4B|>#P@ Ak"$ B7A#! - #)0!  7( B'7 Ak"$ B7A#! , )@PPE@A! + )@! B')! "PP"E@A! ) B' )7B'B'4B|>  7 @A.! ' Ak"$ B7A#! & )!  7 B'7 Ak"$ B7A#! % BB 1H7 B| ) 7 B| )7 Ak"$ B7A#! $ )(P@ Ak"$ B7A#! $  )(4! )( B|> BBQE@A,! # #P@ Ak"$ B7A#! # #1E@A,! " #Bu7 A8j"$ Aj"$A  70 B'7 Ak"$ B7A#!  )01@A!  )0)PP@A!  1H"@A!  )0 < )0 ) 7  )0B|7 Ak"$ B7A#!  )(P@ Ak"$ B7A#!   )(4! )( B|> BBQE@A!  #P@ Ak"$ B€7A#!  #1E@A!  #Bu7 A8j"$ Aj"$A  ) 7 Ak"$ Bɀ7A#!  B|1E@A!  1H!A7!  Ak"$ B̀7A#!  )"PP@A!   B'7 Ak"$ Bр7A#!  1H"@A!  )(P@ Ak"$ BՀ7A#!   )(4! )( B|> BBQE@A!  #P@ Ak"$ Bڀ7A#!  #1E@A!  #Bu7 A8j"$ Aj"$A B'7 B|B> Ak"$ B7A#! B|5"B B BS@A! A! B7 B|B7 Ak"$ B․7A#! B7 B|B7 Ak"$ B䀤7A#!  Bѭ7 B|B7 Ak"$ B怤7A#!  B7 B|B7 Ak"$ B耤7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ O  !!!!""#$$$%&'()*+,-./012 #(M@ Ak"$ B7A#! 4 A(k"$  )07 Ak"$ B7A#! 3 1E@A! 2 B'4"BPPE@A! 1  )07 B|B< Ak"$ B7A#! 0 A(j"$ Aj"$A B.5PP@A! . B'7 Ak"$ B7A#! - B|5!  > B'7 Ak"$ B7A#! , 5 B|5|BP@A! +  B'7 Ak"$ B7A#! * B'5PPE@A! ) )0P@ Ak"$ B7A#! ) )0B>B'4!B' B|> BBQ@A! (  B'7 Ak"$ B7A#! ' A(j"$ Aj"$A B'7 Ak"$ B7A#! % A! # )0P@ Ak"$ B7A#! $ )05MPP@A! # B! @A?! ! B'4BPP@A8! B.4B|BB'5Q@A/!   )07 Ak"$ B7A#!  B|)!  7  )07 Ak"$ B7A#!   B'7 Ak"$ B7A#!  ) PP@A,!  A(j"$ Aj"$A  ) 7 Ak"$ B7A#!  A*!  B'7 Ak"$ B7A#!  B|)PPE@A"!   B'7 Ak"$ B7A#!   )07 B|B< Ak"$ B7A#!  A(j"$ Aj"$A  B'7 Ak"$ B7A#!   )07 B|B< Ak"$ B7A#!  A(j"$ Aj"$A B')"P@ Ak"$ B7A#!  )!  )07 $  Ak"$ B€7Av!A #!  B'4!B' B|> BBQE@A !  B'7 Ak"$ BĀ7A#! A !  )0B|7 B|B> B |B> Ak"$ Bƀ7A$#! B|1!A! B'7 B|B> B |B> Ak"$ BȀ7A$#! B|1E@A !   )07 B|B< Ak"$ Bʀ7A#!  A(j"$ Aj"$A  )07 Ak"$ B̀7A#!  B|1E@A!   )07 B|B< Ak"$ B΀7A#!  A(j"$ Aj"$A A #!@@@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$ B'7 Ak"$ B7A#! 5P@A ! B'7 Ak"$ B7A#! B|5PPE@A!  Aj"$ Aj"$A B'7 B|B> B |B> Ak"$ B7A$#!  B|1E@A!  B7 B|B< Ak"$ B7A#!  Aj"$ Aj"$A Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@ 5  #(M@ Ak"$ B7A#!  A k"$#)0"P@ Ak"$ B7A#!  )"P@A3!  "P@ Ak"$ B7A#!   )!  R@A3!  #7 )PP@A#!  B> Ak"$ B7A#!  Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!    B|7 Ak"$ B7A#!  B|5"B_BBR@A&!  ))0"P@ Ak"$ B7A#!  )!  7 Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  B7 A j"$ Aj"$A Ak"$ B7A#!  Ak"$ B7A#! A !  > Ak"$ B7A#! B7 B|B+7 Ak"$ B7A#!  57 Ak"$ B7A#! B7 B|B$7 Ak"$ B7A#!  Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  )!  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B7 B|B!7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ ) P@ Ak"$ B7A#! ) )!#)0! " Q@A! P@ Ak"$ B7A#! )PP@A!  7 B> Ak"$ B7A#! Ak"$ B7A#! )! ) 7  )B|7 Ak"$ B7A#!  Ak"$ B7A#!  Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!  Bڿ7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$B'5P@A!  #)0"P@ Ak"$ B7A#!  1@A!  Ak"$ B7A#!  )!  7 B'7 Ak"$ B7A#!  )P@ Ak"$ B7A#!  )B>B'4!B' B|> BBQ@A!  B'7 Ak"$ B7A#! Ak"$ B7A#! A j"$ Aj"$A B'7 Ak"$ B7A#! A!  B< B'7 B|B> Ak"$ B7A#!  B|5"B B BSE@A!  B7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@ $  #(M@ Ak"$ B7A#!  Ak"$ #7#)0"P@ Ak"$ B7A#!  B.5PP@A"!   ) 7 ) P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  B.5P! ))0! P@A !  ) 70  ) 7 B|B> B |B> Ak"$ B7A#!  ) B7 ) B< ) ) )B|7 1("E@A!  ))0P@ Ak"$ B7A#!  B*1E@A! ) )pPPE@A! ) 1@A! Ak"$ B7A#!  ) B8|7 Ak"$ B7A#!  Aj"$ Aj"$A  ) )7 Ak"$ B7A#!  A!  ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!    5B|>A !  ) B0|  Ak"$ B7#!A !  B| ) Ak"$ B7#!A!  A P ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"####$%&'()*++++,,----../0123444455556789:;<=>?@ABCDEFGHIJKKKLLMNOPQRSTTTTTTTTUVWWWWWXYZ[[[\]^___``aaabcdddeeffghiiijjklllmnoooppqrrssssttuuuuvvwxxxxyzzz{|||||}~~ #(AjM@ Ak"$ B7A#!  Ak"$ #7A!  B| B >  B|5" B|5Q@A-!  B'5PP@A!  B')!B')!  B|5" TE@A!  B|)!  QE@A!   B| B|5B|> B|5 B|5|! B|5" PP@A!  A!   7  7  <. @A+!  B! @A!   7X  7P  </B')!B')!B')! B|5!  7 B| 7 B| 7 B| > Ak"$ B7A#!  B |1E@A!  )! 1-! )! 1/! )P! )X!A!   )7 B| )7 B| 1-< Ak"$ B7A#!  B|)"PPE@A!  B| 7 B|B< Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!  B|)! B|)! B |1! PPE@A*!  )PE@A)!  @A%!  1.! ! ! ! !A!   78  7  )7 Ak"$ B7A#!  B|)! B|1! PP@A(!  )! )8!B!A$!  B| 7 B| < Aj"$ Aj"$A  )S@A"!  )!A"!   7B')!B')!B')!  7 B| 7 B| 7 B| > Ak"$ B7A#!  B |1! )! )! )! 1.! 1-! )! )!A!  B|! BSE@A>!  #)0"P@ Ak"$ B7A#!   5! 5!  > B ! B   BB!  >  |!B'5"P!B')! B')! PE@A!  B" ! B" PPE@A!   B" TE@A!   7 B|5! B|B> B| > B| > B| > BQ!  <- )!A!  @A!   7X  7PB.5PP@A!  B! @A!    }B PP7 Ak"$ BÀ7A#!  B|)! B|1! PP@A!  @A!  B')!  7B')!  7B')!  7B')!  7hB')!  7pB')!  7B')!  7@B')!  7H B'7 Ak"$ Bɀ7A#!  B'5PPE@A!   B'7 Ak"$ Bπ7A#!  A!  )5MPP@A!  B'4BPP@A!  Ak"$ BԀ7A#!  ) )R@A!   )7 Ak"$ Bր7A#!   B'7 Ak"$ Bڀ7A#!  ))0"P@ Ak"$ B܀7A#!  1!  <, @A!  B )S!  </ E@A!  B! )!A!  )B|! !  7x  7 )!  7  )7 B| )h7 B| )p7 B| > Ak"$ B7A#!  B |1E@A!  B! @A!  )xB|" )S@A!  1/"E@A!  B! )! )P!A!  )B|! ! ! !  7  7  7P )!  7  )7 B| )@7 B| )H7 B| > Ak"$ B7A#!  B |1@A!  )P! )B|" )S@A!   7P PP!  </ E@A!  )XP@A!  B.7 Ak"$ B7A#!  B|5PP@A!  B! @A!   B.7 Ak"$ B7A#!  B|5PP@A!  B! @A!  1/"@A!  B! @A!  Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  )!B'5P! ! PE@A!  Ak"$ B7A#!  A!   7 P@ Ak"$ B7A#!  5MPP@A!   7 B|B7 Ak"$ B7A#!  B|)!  7X B|)!  7PB.1E@A!  B.1@A!  B')"P@ Ak"$ B7A#!  )"PP@A!   )7 Ak"$ B7A#!  B|)! B|1! PP@A!  B'4"BPP@A!   B.7 Ak"$ B7A#!  B|5PP@A!  ))0"P@ Ak"$ B7A#!  B.4! 1E@A!  B! @A!  ))0"P@ Ak"$ B7A#!  1E@A!  )X! )P!B!B!A.!  B'7 Ak"$ B7A#!  B|)"P@A!  )P S@A!  A!   B.7 Ak"$ B7A#!  B|5PP!A!  B'7 B| )P7 Ak"$ B7A##!  ))0"P@ Ak"$ B7A#!  )PP@A!  1@A!  B|B7 B'7 B|B7 Ak"$ B7A##!   Ak"$ B7A#!  )! B'7 B| 7 Ak"$ B7A##!  B.)PPE@A!   B|)P@A!   B'7 Ak"$ B7A#!  Ak"$ BÁ7A#!  )!  7 B'7 Ak"$ Bǁ7A#!  )PPE@A!   )7 Ak"$ Bʁ7A#!   B|)"PE@A!  1,"E@A!  ))0"P@ Ak"$ BЁ7A#!  B< B'7 B|B> Ak"$ Bҁ7A#!  A! ~  !  7 PPE@A! ~ B| )7  B|7 Ak"$ Bہ7A#! |  )7 B|B> B |B> Ak"$ B܁7A#! { B*1@A! z B| )7 B|B< Aj"$ Aj"$A  )7 B|B7 Ak"$ B߁7A#! x A! v  B|7 Ak"$ B7A#! v A! t Ak"$ B7A#! t A! r B.7 Ak"$ B7A#! r B|5PPE@A! q B'7 B|B7 Ak"$ B7A #! p B|)PP!A! n 1/"@A! n B!A! l  B'7 Ak"$ B7A#! l Ak"$ B7A#! k )"PPE@A! j B.5PPE@A! i  7 B.7 Ak"$ B7A#! h B|)"PPE@A! g )!  7  7 B'7 Ak"$ B7A#! e )PPE@A! d  )7 Ak"$ B7A#! c 1,"@A! b )B7- )P@ Ak"$ B7A#! b ))! !  7  7 B|B> B |B> Ak"$ B7A#! a B*1@A! ` B| )7 B|B< Aj"$ Aj"$A  )7 B|B7 Ak"$ B7A#! ^ A! \ ))0"P@ Ak"$ B7A#! ] B< B'7 B|B> Ak"$ B7A#! \ A! Z  7  )7 Ak"$ B7A#! Z )!B!A! X Ak"$ B7A#! X B!B!A! V !B!A! U B7 Ak"$ B7A#! U B|1!A! S  Ak"$ B7A#! S )P! 1/!A! Q  )7 Ak"$ B7A#! Q B|)"PPE@A! P )PP@A! O  )PS@A! N )P!A! L )P!A! K  B'7 Ak"$ B7A#! K Ak"$ B7A#! J )!  7 B'7 Ak"$ B7A#! I )PPE@A! H  )7 Ak"$ B7A#! G 1,"E@A! F ))0"P@ Ak"$ B7A#! F B< B'7 B|B> Ak"$ B7A#! E A! C  )7 Ak"$ B7A#! C B|1P!A! A B< B'7 B|B> Ak"$ B7A#! A B|5"B B BS@A! @ 1,!A! >  )7 B|B> Ak"$ B7A#! > B|)!  7 B'7 Ak"$ B7A#! = B| )7 B|B< Aj"$ Aj"$A  7  7 B|B> B |B> Ak"$ B7A#! ; B*1@A! : B| )7 B|B< Aj"$ Aj"$A  )7 B|B7 Ak"$ B7A#! 8 A! 6 B.7 Ak"$ B7A#! 6 B|)"PP@A! 5 )X! )P!A! 3 )B7- )! !  7  7 B|B> B |B> Ak"$ Bǂ7A#! 3 B*1@A! 2 B| )7 B|B< Aj"$ Aj"$A  )7 B|B7 Ak"$ Bʂ7A#! 0 A! .  )7 Ak"$ B̂7A#! . B|1! )X! )P!A! , B< B'7 B|B> Ak"$ B΂7A#! , A! * )P! )X!A?! )  >4 B'7 Ak"$ Bт7A#! ) B|5!  >0 B'7 Ak"$ B҂7A#! ( 44 B|5}B 50BBX!A! & B.7 Ak"$ BԂ7A#! & B|5PPE@A! % B'7 Ak"$ Bւ7A#! $ A! "  B'7 Ak"$ Bۂ7A#! "  )7 B|B> Ak"$ B݂7A#! ! B|)!  7 B'7 Ak"$ B7A#! )PPE@A!  B| )7 B|B< Aj"$ Aj"$A B| 7 B| < Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!  A!  Ak"$ B7A#!  )PP@A!  )X! )P!A!  B|B7 B|B< Ak"$ B7A#!  A!  Ak"$ B7A#!  )!A!  B7 B|B#7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B7 B|B!7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#! Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@  #(M@ Ak"$ Bď7A#!  Ak"$B'4BPP@A!  #)0"P@ Ak"$ Bď7A#!  )!  7 Ak"$ Bď7A#! B|1E@A!  B.7 Ak"$ Bď7A#! B|5PP@A! B! E@A!  B')PPE@A!  B|B< Aj"$ Aj"$A B.7 Ak"$ Bď7A#!  B|5PP!A !  B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@   #(M@ Ak"$ Bȏ7A#! Ak"$ B'7 Ak"$ Bȏ7A#! )PE@A!  B'7 Ak"$ Bȏ7A#!  B|)"PE@A!  Aj"$ Aj"$A ) S@A!  A!  Ak"$ Bȏ7A#!  A!  A ~#!@@@@@@@@@@@@   #(M@ Ak"$ B̏7A#! Ak"$#)0"P@ Ak"$ B̏7A#! 1E@A ! B< B'7 B|B> Ak"$ B̏7A#! B|5"B B BS@A!  Ak"$ B̏7A#!  Aj"$ Aj"$A B7 B|B!7 Ak"$ B̏7A#!  B7 B|B7 Ak"$ B̏7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!""#$$$$$$%%%%%%&&''(())))****+++++,,,-./0112222223333334455667 #(M@ Ak"$ BЏ7A#! 9 Ak"$ )pP@ Ak"$ BЏ7A#! 9  )p)"P@A! 8 B*1E@A! 7  !A! 5  7X  7 B|B7 Ak"$ BЏ7A#! 5 )X)! ! PP@A ! 3 )p)! !  7` !B!B!A! 1  7P  7 B|B> B |B> Ak"$ BЏ7A#! 1 )P)! ) B|! ! )`! )P!  7 PP@A! / B0|B7 B8|B7 B0| 7 B8| 7 )pB7#)0"P@ Ak"$ BЏ7A#! / )! "PPE@A! .  7` B'7 Ak"$ BЏ7A#! - B|5! B|B7 B|B7B!A2! +  B| 7 B|!  SE@A! *  B0|)"P@A! )  "PPE@A>! ( )! B0| 7 PE@A>! ' B8|B7 P@ Ak"$ BЏ7A#! &  B7 B|)"PPE@A! % "P@ Ak"$ BǀЏ7A#! %   7A-! #  B| 7A-! " B SE@A! "  7 B'7 Ak"$ B׀Џ7A#! !  B|)! B|)! PE@A! B'B'4 )|> B|B7 B|B7 B'7 Ak"$ BЏ7A#!   )"!A!   7( B7 B|B< Ak"$ BЏ7A#!  )(B|! )! PPE@A!  B'5PP@A!  ) }!  B0|)PE@A!  Aj"$ Aj"$A  )`7 B| B0|7 B| 7 Ak"$ BЏ7A#!  A!  "P@ Ak"$ BЏ7A#!   B7B')"PPE@A!  "P@ Ak"$ BЏ7A#!    7 B' 7A!  B' 7A!  ) !A!   B'7 Ak"$ BЏ7A#!   B0|)! B0|)! PE@A!  B'B'4 ) |> B0|B7 B8|B7 B'7 Ak"$ BЏ7A#!  ) !A!  7 B7 B|B< Ak"$ BЏ7A#! ) B|! PPE@A! B'5PP@A!  Aj"$ Aj"$A "P@ Ak"$ BЏ7A#!   B7B')"PPE@A!  "P@ Ak"$ BЏ7A#!    7 B' 7A!  B' 7A!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!!!!""""""""########$$%%%%%&&&&'''(()*+,-..../00000000122223333344445556677777789:;<=>?@@@ABCDEEEEEEFGHIJKLM #(M@ Ak"$ Bԏ7A#! O Ak"$#)0"P@ Ak"$ Bԏ7A#! O 4"BPP@A! N #7@ )PP@A! M )@P@ Ak"$ Bԏ7A#! M )@)0"P@ Ak"$ Bԏ7A#! M 1@A! L )@!A ! J  7 Ak"$ Bԏ7A#! J )@! )0"P@ Ak"$ Bԏ7A#! I )"P@ Ak"$ Bԏ7A#! I  B<NB'5PP@A! H 5MPP@A! G )0"P@ Ak"$ Bԏ7A#! G 1E@A! F )PP@A! E 5 5 R@A! D  7 B|B7 Ak"$ Bԏ7A#! C B*1E@A! B Ak"$ Bԏ7A#! A )"PP"@A! @ PPE@A! ?  <, PPE@A! > PPE@A! = B! PPE@A! ;  <-  78 )@)0"P@ Ak"$ Bԏ7A#! ; 1@A! : B'1@A! 9 B! @A2! 7 @A0! 6 P@ Ak"$ Bԏ7A#! 6 )PP@A ! 5  7 B| < Ak"$ Bԏ7A#! 4 Aj"$ Aj"$A Ak"$ Bԏ7A#! 2 )8! 1-!A+! 0  B'7 Ak"$ Bԏ7A#! 0  )87 Ak"$ Bԏ7A#! / B|1@A! . )8P@ Ak"$ Bԏ7A#! .  )8B7B')"PPE@A! - "P@ Ak"$ B€ԏ7A#! -   )87 B' )87B'B'4B|> B'7 Ak"$ Bπԏ7A#! + )@!A ! ) B' )87A! (  B'7 Ak"$ Bڀԏ7A#! ( )8! 1,! 1-!A*! &  7 Ak"$ B߀ԏ7A#! & B|1P! )8! 1,! 1-!A)! $ Ak"$ Bԏ7A#! $ )8! 1,! 1-!A'! " Ak"$ Bԏ7A#! " B|1! )! 1,!A$! )@)0"P@ Ak"$ Bԏ7A#! ! )!  7 Ak"$ Bԏ7A#! B|1! B|)! 1,!A#!  )@)0"P@ Ak"$ Bԏ7A#!  )"P@ Ak"$ Bԏ7A#!   5B|~BBӸ!XE@A!!  BB'4SE@A!!   B'7 Ak"$ Bԏ7A#!  )@)0"P@ Ak"$ Bԏ7A#!  )!  7 B|B> Ak"$ Bԏ7A#!  B|)!  70 B'7 Ak"$ Bԏ7A#!  1,! )0!A!!  B.5PPE@A !   </ )@)0"P@ Ak"$ Bԏ7A#!  )! ! B.7 B| 7 Ak"$ Bԏ7A#!  B|)! 1/"E@A!  B!A !  PP!A !   7P  <. B|B> B |B> Ak"$ Bԏ7A#!   )P7 B|B7 Ak"$ Bԏ7A#!  )P! 1.!A!  B*1@A!  B!B!A!   7H Ak"$ Bԏ7A#!  )@! )H!A! Ak"$ Bԏ7A#! )@!A ! Ak"$ Bԏ7A#! )@P@ Ak"$ Bԏ7A#! )@)0"P@ Ak"$ Bԏ7A#! )!  7 B|B< Ak"$ Bԏ7A#! A!  B7 B|B"7 Ak"$ Bԏ7A#!  B7 B|B7 Ak"$ Bԏ7A#!  B׵7 B|B7 Ak"$ Bԏ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ;  !!!""#$%&' #(M@ Ak"$ B؏7A#! ) Ak"$ )HP@ Ak"$ B؏7A#! )  )HB,|7 Ak"$ B؏7A#! ( )!  70  )HB,|7 Ak"$ B؏7A#! ' )! )0PE@A8! & P@A7! %  7 )PP@A2! $ )P!  7P  SE@A! " #)0"P@ Ak"$ B؏7A#! " )! )H RE@A! ! B| 7 B| 7 B|B< Aj"$ Aj"$A  )HB|7 Ak"$ B؏7A#!  B|5!  >  )HB|7 Ak"$ B؏7A#!  5 B|5BBWE@A!  ) ! )P!A!   )HB|!  78  7 Ak"$ B؏7A#!  B )H)MS@A)!  B!B!  <  7(#)0"P@ Ak"$ B؏7A#!  )! )H Q@A$!    )87 Ak"$ B؏7A#!  B| )P7 B| )(7 B| 1< Aj"$ Aj"$A  )HB|7 Ak"$ B؏7A#!  )H)MB B|5S@A'!  )(! 1!A!   )H7 Ak"$ B؏7A#!  A!   )H7 B| )P7 Ak"$ B؏7A#!  B!A,!  B!  <B )H)MSE@A1!  )H7 B| )P7 Ak"$ B؏7A#! B|)"PPE@A+! BB S! 1!A!  B!A0!   Ak"$ B؏7A#!  )! ) !A !  B| )P7 B|B7 B|B< Aj"$ Aj"$A PPE@A:!   )0S@A!  )0!A!  A #!@@@@  #(M@ Ak"$ B܏7A#!  Ak"$  )7 Ak"$ B܏7A#!  B |B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@ 4  #(M@ Ak"$ B7A#!  A k"$ #7B*1@A0!   )(7 B|B> B |B> Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   B70#P@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!   B7 )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  )"PP@A!  Ak"$ B7A#!  A j"$ Aj"$A )! )!  )(7 B| 7 $  Ak"$ B7Av!A #!  ))0"P@ Ak"$ B7A#!  B|1!B.5PP@A.!  B7 ))0"P@ Ak"$ B7A#!  B.5PP@A,!  B7 @A! B*1@A*!  )(7 B|B> B |B> Ak"$ B7A#!  )(7 B|B< Ak"$ B7A#! A!   )(7 B|B7 Ak"$ B7A#!  A'!  B|B Ak"$ B7#!A%!  B|B Ak"$ B7#!A!!  #)0"P@ Ak"$ B7A#!  1! )!  < B| 7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@ ? #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!    )B|7 Ak"$ B7A#!  5B_BBR@A;!  )7 B|B> B |B> Ak"$ B7A#! #)0"P@ Ak"$ B7A#! )"P@ Ak"$ B7A#!  B70#P@ Ak"$ B7A#! #)0"P@ Ak"$ B7A#!  B7 B'7 Ak"$ B7A#!  )B7B')"PPE@A6! "P@ Ak"$ B7A#!   )7 B' )7B'B'4B|> B'7 Ak"$ B7A#!  Ak"$ B7A#!  Aj"$ Aj"$A B' )7A,!   )7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A #!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$B*1@A!   )7 Ak"$ B7A#!  Aj"$ Aj"$A Ak"$ B7A#!  A!  A ~#!@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$B*1@A!   )07 Ak"$ B7A#!  A(j"$ Aj"$A #)0"P@ Ak"$ B7A#!  # )7 B< B|B7 B|B7 B|B7 B |B7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@ % #(M@ Ak"$ B7A#!  Ak"$B*1@A ! )P@ Ak"$ B7A#!   )B|7 Ak"$ B7A#! B|5B_BBR@A"! )B<  )7 B|B> B |B > Ak"$ B7A#! #)0"P@ Ak"$ B7A#! )"P@ Ak"$ B7A#!  B70#P@ Ak"$ B7A#! #)0"P@ Ak"$ B7A#!  B7  )7 B|B > B |B > Ak"$ B7A#! Ak"$ B7A#!  Aj"$ Aj"$A B< B|B7 Ak"$ B7A#!  A!   )7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A #!@@@@@  #(M@ Ak"$ B7A#!  Ak"$ Ak"$ B7A#!  B7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@ ' #(M@ Ak"$ B7A#! A0k"$B*1@A!  )8P@ Ak"$ B7A#!  )8)0"P@ Ak"$ B7A#!  )! !  7(  )87 B|B> B |B> Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   B70#P@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!   B7  )(7 B| )87 B|B< Ak"$ B7A#!  Ak"$ B7A#!  A0j"$ Aj"$A #)0"P@ Ak"$ B7A#!  # )7 B< B|B7 B|B7 B|B7 B |B7 Ak"$ B7A#!  A!  A #!@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$B*1@A!  B7 Ak"$ B7A#!  A(j"$ Aj"$A  B< B|B7 B|B7 B|B7 B |B7 Ak"$ B7A#!  A!  A ~|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ S  !"#$%& #(M@ Ak"$ B7A#! ( A8k"$ #70  )@7 B|B> B |B> Ak"$ B7A#! '  )@7 B|B< Ak"$ B7A#! & 1@A! % )@P@ Ak"$ B7A#! % B.5PP@A! $ )@B70 )@)! )@B7 )0P@ Ak"$ B7A#! # )0)0"P@ Ak"$ B7A#! # B7 )@B< )@B<B.5PP@A! " )@B7( )@B7 )@B7 )@B7B.5PP@A! )@B7 )@B<B.5PP@A?!  )@B7 )@B7 )@B7  7(B.5PPE@A!  B )@)S@A9!  #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   B70#P@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!   B7 )0)0"P@ Ak"$ B7A#!  )!  7 B| )@7 Ak"$ B7A#!  Ak"$ B7A#!  )0)0"P@ Ak"$ B7A#!  5"PP@A!  )!  7 B| )@7 Ak"$ B7A#!  )(PP@A3!  Ak"$ B7A#!  A8j"$ Aj"$A )0)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   B8|7 Ak"$ B7A#!  A1!  B.7 Ak"$ B7A#!  B|)! B | 7 )@)! B |+! B.7 B|  #!7 Ak"$ B7A#!  )@B7A!  )@B|B Ak"$ B7#! )@B|B Ak"$ B7#! )@B|B Ak"$ B€7#!A! )@B|B Ak"$ BĀ7#!A! )@B(|B Ak"$ Bƀ7#! )@B |B Ak"$ Bǀ7#!A! )@B0|B Ak"$ Bɀ7#!A! B'7 B|B> Ak"$ Bˀ7A#! A!   > Ak"$ B̀7A#!  Bڳ7 B|B7 Ak"$ B΀7A#!   57 Ak"$ Bπ7A#!  Ak"$ BЀ7A#!  Ak"$ Bр7A#!  B7 B|B7 Ak"$ BҀ7A#!   A #!@@@@@@@  # )7@#P@ Ak"$ B7A#!  # )78#B7`#B7X##7H#)PPP@A!  Aj"$A Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@ <  A(k"$ #7#)0"P@ Ak"$ B7A#!   4B|>#P@ Ak"$ B7A#!  #Bu7#B<  )07 B| )87 Ak"$ B7A#!  ) )87p ) )07x  )7 B|B> B |B> Ak"$ B7A#!  ))p" ))TE@A;!  B|B7 B |B7 B|B7 B | )7  B|7 Ak"$ B7A#!  B*1@A8!  B'7 Ak"$ B7A#!  B|5PP@A5!  ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   5MPP@A2!  ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!    5> )B< ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   B78 ))0"P@ Ak"$ B7A#!    7 ))0"P@ Ak"$ B7A#!  B7  B|7 B|B> Ak"$ B7A"#!  B'5PP@A/!  ))0"P@ Ak"$ B7A#!   4B|> A(j"$ Aj"$A B7 Ak"$ B7A#!   )07 B| )87 Ak"$ B7A#! A,! B7 Ak"$ B7A#!  )07 B| )87 Ak"$ B7A#! A!  B7 Ak"$ B7A#!   )07 B| )87 Ak"$ B7A#!  A !  B7 Ak"$ B7A#!   )07 B| )87 Ak"$ B7A#!  A !  )) T@A!  A !  A #!@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ B'7 Ak"$ B7A#! B'7 Ak"$ B7A#!  5PP@A!   B'7 Ak"$ B7A#!  Aj"$ Aj"$A B'7 B|B> Ak"$ B7A"#!  B'7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ B7A#!  A k"$#)0"P@ Ak"$ B7A#!  )! !  7 B'7 Ak"$ B7A#!  BB'4S@A!  B! E@A! B*1@A! )P@ Ak"$ B7A#! ) )5B|>B'4!B' B|> BBQ@A!  B'7 Ak"$ B7A#! A j"$ Aj"$A B'7 Ak"$ B7A#!  A!   )7 Ak"$ B7A#!   )7 Ak"$ B7A#!  A!  )P@ Ak"$ B7A#!   )B|7 B|B> B |B> Ak"$ B7A$#!  B|1!A !  A #!@@@@@@@@@@@@@@@@@   Ak"$B'4BBQ@A!  #7 )PPPE@A!  )P5BQ@A ! B')PP@A! B|B< Aj"$ Aj"$A B|B< B(|B7 B0|B7 B8|B7 B|B7 B(|Bė7 B0| B|7 B8| )P7 B| ) 7  B(|7 Ak"$ B7A#! B|1E@A! B|B< Aj"$ Aj"$A  )PB|7 B|B> B |B> Ak"$ B7A$#!  B|1E@A!   )P7 Ak"$ B7A#!  Ak"$ B7A#!  B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A A ~#!@@@@@@@@@  A k"$#)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  5!  5RE@A!  #7B*1@A!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!    5B|> A j"$ Aj"$A B|B7 B|B7 B|Bȗ7 B|#7  B|7 Ak"$ B7A#!  A !  A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ B'7 Ak"$ B7A#!  Ak"$ B7A#!  )!  7 PP!  < @A!  B'7 Ak"$ B7A#! 1"E@A!  )7 Ak"$ B7A#! B(|B< A j"$ Aj"$A B(|B< A j"$ Aj"$A B'7 Ak"$ B7A#!  B|5PP@A!  )! 1!A!  B'7 B|B> Ak"$ B7A"#!  B'7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ W  #(M@ Ak"$ B7A#! A0k"$ #7  )87 B|B> B |B> Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   B70#P@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!   B7 B'7 Ak"$ B7A#!  B'1@A!  Ak"$ B7A#!  )!  7( PP!  < @A!  )8P@ Ak"$ B7A#!   )8B7B')"PPE@A!  "P@ Ak"$ B7A#!    )87 B' )87B'B'4B|>  B'7 Ak"$ B7A#!  1"@A!  ) P@ Ak"$ B7A#!  ) )0"P@ Ak"$ B7A#!  )PP@A!  Ak"$ B7A#!  Ak"$ B7A#!  A0j"$ Aj"$A Ak"$ B€7A#!   )87 B|B< Ak"$ BÀ7A#!  A>!  )(7 Ak"$ Bŀ7A#!  )87 B|B< Ak"$ Bƀ7A#! A9! B' )87A.! B'7 Ak"$ B΀7A#! B|5PPE@A3!  B'7 B|B> Ak"$ BЀ7A"#!  B'7 Ak"$ Bр7A#!  A3!   ) 7 B|B< Ak"$ BԀ7A#!  B|1@A!  B!A!  A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! A0k"$ B7 Ak"$ B7A#! )!  7 48B B !B WE@A !  B!A!  B|! B B BT"B B S@A!  B|B7 B |B> B(|B7 B|B̗7 B | > B(| 7  B|7 Ak"$ B7A#!  ) ))B|7 )B7 ))"P@ Ak"$ B7A#!  B7 B| )7 A0j"$ Aj"$A A #!@@@@  A8k"$ B|A#! B|BЗ7 B| B|7 B| B|7 B | 4@> B(|#7 B0| )87  B|7 Ak"$ B7A#!  A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ p  !!"""##$%%%%%&'((())*+,-./0123455567 #(M@ Ak"$ B7A#! 9 Ak"$ )pPPE@A! 8 #)0"P@ Ak"$ B7A#! 8   4B|>#P@ Ak"$ B7A#! 8 4B|Bx"B !B B W@A! 7 #7H  70#)0"P@ Ak"$ B7A#! 7 )! !  7P  7 Ak"$ B7A#! 6 B|)"PPE@A! 5 P@ Ak"$ B7A#! 5 )P@A! 4  7@  B|7 Ak"$ B7A#! 3 B|5BR@A! 2 )@) )0B }B`|!  7( 4B "B !B S@A! 1  )@B8|7 B|B87 Ak"$ B7A#! 0 )@ )(78 )@ )(7 B|B7 B|B7 B|)"P@ Ak"$ B7A#! 0  )@ )B|7@ )@ )@7H )@)8Bx|""P@ Ak"$ B7A#! 0  )p)!  )@)@7 )@ 78 )@ 7@B.5PP@A! / )@ )p7P )@ )7  )7 Ak"$ B7A#! - B.5P! B|)! P@A! , )@ 7 )@ )p)7 )H)0"P@ Ak"$ B7A#! + )"PPE@A2! * B.5P! )! P@A! ) )@ 7  )@7 B|B< Ak"$ B7A#! ' B|1@A! &  )@7 B|B> B |B> Ak"$ B7A#! % )PP@ Ak"$ B7A#! % )P) )P) Q@A! $ )@ )P) 7 )P )P) B|7 B*1@A! # )H)0"P@ Ak"$ B7A#! #  4!  B|> BBQE@A! " #P@ Ak"$ B7A#! " #1E@A! ! #Bu7 B| )@7 Aj"$ Aj"$A )@)!  )@7 B| 7 Ak"$ Bƀ7A#!  A9!  B'7 B|B7 Ak"$ BȀ7A#!  B|)! )P Bq|7 )P B|7 A8!  B'7 B|B> Ak"$ Bʀ7A#!  A4!  )@B|  Ak"$ B̀7#!A2!  )@B|  Ak"$ B΀7#!A-!  )@B| )p Ak"$ Bр7#!A)!   7 B| )x7 B| B 7 Ak"$ BԀ7A#!  B.1E@A!  )H)0"P@ Ak"$ Bր7A#!  )"P@ Ak"$ B؀7A#!  1@A!   )p)7 Ak"$ Bۀ7A#!  B|)! B|)!  7 B| 7 B|B< Ak"$ B܀7A#!  B|)"P@ Ak"$ B݀7A#!  4!B SE@A!    )(7 B| )(7 B| B7 B|B7 B | B|7 Ak"$ Bာ7A#!  A! B> Ak"$ B〬7A#! B|)!  78  7 B|B> B |B> Ak"$ B䀬7A#!  )87 Ak"$ B倬7A#! )8!A! B7 B|B7 Ak"$ B瀬7A#! B7 B|B7 Ak"$ B逬7A#!  B7 B|B77 Ak"$ B뀬7A#!  #)0"P@ Ak"$ B퀬7A#!  B> B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@    #(AjM@ Ak"$ B7A#!  Ak"$B.4"BWE@A!  B|B7 Aj"$ Aj"$A )P@ Ak"$ B7A#!  ))P@A!  ))"PPE@A!  )! )!  7`  7 B7 B|!    B B SB B !  7h B| 7 B| 7 Ak"$ B7A#!  B|)! )hPPE@A!   7 B7 )hB|! B| B }B?B(|7 B| 7 B| )7 B | )`7 Ak"$ B7A#!  B|A#! B7 B|B7 B|B7 B| )7 B |B7 B(| B|7 B0|B7 B8|B7 B|B7 B|B7 Ak"$ B7A#!  B|)!  7X B7 B| 7 B| 7 Ak"$ B7A#!  B|)!B )XB )XS!  B|R@A!  B|A#! B| 7 B| )X7 B| )X7 B| ))7 B| ))7B.5PP@A!  ) B|)7 )B| B|A#! B7 Ak"$ B7A#! B|)" )h7  )h7B.5PP@A!  )7 B| 7 Aj"$ Aj"$A  ) Ak"$ B7#!A!  B7 B| )7 B| B|7 Ak"$ B7A#!  A!   7  7 B| B|7 B| B7 Ak"$ B7A#!  )!A!  B!B!A!  B7 B| )h7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@ ?  #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!    ) B|7 Ak"$ B7A#!  5BR@A=!  ) )! ) )!  }BR@A:!  )P@ Ak"$ B7A#!   ) ))7 ) ) 7 )4B|! ) >B B B WE@A8!   B'7 Ak"$ B7A#!  )!A!  B'B'4B|> 4!B WE@A2!   B|> )! "PPE@A"!  )7 P@ Ak"$ B7A#! )PE@A,!  B')7B' 7A!  B')7B' 7A!   B'7 Ak"$ B7A#!  Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!  ) B7 ) B7 ) B7A !  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@ D  #(M@ Ak"$ B7A#!  A k"$ )(!A$!  B'B'4B|> P@ Ak"$ B7A#!    )7  7  4B|> 4B SE@A!  B')! "PP"E@A!  B' )7 @A!  B')! "PP"E@A!  B' )7 @A!   B'7 Ak"$ B7A#!  )(! P@ Ak"$ B7A#!   )"PE@A4!  B')PE@A.! B')P@A4!  B'7 Ak"$ B7A#! )(!A !  "PP"E@A;!  )7 E@A!   4B|> )P@A?!  B0| 7 A j"$ Aj"$A  7 B|B7 B|B7 B|Bԗ7 B| 7  B|7 Ak"$ B€7A#!  ) ))B|7 )!A>!  B0|B7 A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@ + #(M@ Ak"$ B7A#! Ak"$ B'7 Ak"$ B7A#! )!A ! B'B'4B|> P@ Ak"$ B7A#!  )"P@A%!  "PPE@A!   )7  4B|> P@ Ak"$ B7A#!  )PE@A!   B')7B' 7A!   B')7B' 7A!   B'7 Ak"$ B7A#!  Aj"$ Aj"$A A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B>7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ %  #(M@ Ak"$ BĐ7A#!  A k"$ )(P@ Ak"$ BĐ7A#!  )( 40> )(B> )(B7 )(B7B.5P! )(B|! P@A!  )( 7 )(!B!A!  B|! BSE@A !  B| B~|"B7 B 7B.5P! B| B|! P@A !   7A!    Ak"$ BĐ7#!A!   B-|7 Ak"$ BĐ7A#!  )()@PPE@A!  B')!B')! 40B B B! 40 BB|B B "B! 40 B}"B B !B WE@A !  B!B B BT! B B " TE@A!    B|7 B| > Ak"$ BĐ7A#!  B')!B')!B')!  7 B| 7 B| 7 B| 40> Ak"$ BĐ7A#!  A j"$ Aj"$A 40BPE@A! B.)"PPE@A"! )( 7@A! Ak"$ BĐ7A#! )( )7@A!  )(B|  Ak"$ BĐ7#!A!   7 B| 7 Ak"$ BĐ7A#!  Ak"$ BĐ7A#!  B7 B|B7 Ak"$ BĐ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ c  !"#$$$$%%%%&&''( #(M@ Ak"$ BȐ7A#! * Ak"$ )`!A! ( B'B'4B|> P@ Ak"$ BȐ7A#! ( 5 ! 5 !  RE@A! ' B|!  > B | BBB|)"P@ Ak"$ BȐ7A#! '  B')7B' 7B')PE@A! & B' 7A! $ )"PPE@A,! $ "P@ Ak"$ BȐ7A#! $  B')7B' 7B')PE@A*! # B' 7 B'B'4B|> B7 B )MS@A! B.5PP@A!   B|7 B|B7 Ak"$ BȐ7A#!  )`B7 )`B7B.5P! )`B|! P@A>!  )` 7 B!A3!  ) B|! BSE@A9!   7 )`B| B|!  7@  7 B|B7 Ak"$ BȐ7A#!  )`B| ) B~|"B7 B 7B.5PP@A7!   )@7A2!   )@ Ak"$ BȐ7#!A2!  B|B7 B|B7 B|Bؗ7 B| )`7  B|7 Ak"$ BȐ7A#!   )`)@7 Ak"$ BȐ7A#!  )`B7@  )`7 Ak"$ BȐ7A#!   )`7 Ak"$ BȐ7A#!  )`B7, )`B> Aj"$ Aj"$A )`B|  Ak"$ BȐ7#!A1!   7 Ak"$ BȐ7A#!   )`B-|7 Ak"$ B€Ȑ7A#! )`!A.! #)0"P@ Ak"$ BĀȐ7A#! )!  78 P@ Ak"$ BȀȐ7A#!  B|!  7(  7 Ak"$ B΀Ȑ7A#!  )`B|!  70  7 Ak"$ BҀȐ7A#! )`)M! )`)M! )`)M!  )87 B| 7 B| 7 B| 7 Ak"$ BԀȐ7A#! )`B7M )`B7MB.5PP@A!  )`B7M )`B>M )`B>M )`B>N  )`B,|7 B|B7 Ak"$ B׀Ȑ7A##!    )07 Ak"$ BۀȐ7A#!    )(7 Ak"$ B߀Ȑ7A#!  )`!A-!  )`B|B Ak"$ BȐ7#!A!  A -~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"######$$$%&&&&&&'(((((())))**+,,,----.//01223344445567888899::;<<<<==>?@@@@AAAABBCDDDEEEEEEEFGGGGGGGGGGHIIIIJJKLMNOPQQQQRRRSSTUVWXYYZZ[\]^__```aabcdefghijklmnopqrstuvwxyz #(AjM@ Ak"$ B̐7A#! | Ak"$B.4"BS@A! { 4B "B "BW@A! z  >D  7  7B*1@A! y  Ak"$ B̐7A#! x B')"P! )! PE@A ! w B'B') 4D  }~|7 B' 7 4B|B B B"BB 4|B|B B B!  >@B')B B )S@A! u )! 4D"!A! s   )x|7 B| )7 Ak"$ B̐7A#! s 4TB|! )! 4@! )! 4D! B "B SE@A! q B !B')!B')!  TE@A! p  >T  7 B!  7x  |)"PPE@A! o  7  7 B| > Ak"$ B̐7A#! n B')!B')! ) T@A! m A! l B7 Ak"$ B̐7A#! l B|)! 4T!A! j #7#)0"P@ Ak"$ B̐7A#! k )"PP"E@A ! j "P@ Ak"$ B̐7A#! j  4 S@A! i E@A,! h B*1@A! g )P@ Ak"$ B̐7A#! g ))0"P@ Ak"$ B̐7A#! g )"P@ Ak"$ B̐7A#! g  B78 )P@ Ak"$ B̐7A#! f ))0"P@ Ak"$ B̐7A#! f B7B')!B')! PPE@A! e )"P@ Ak"$ B̐7A#! e B78 B>  7 Ak"$ B̐7A#! d B*1@A! c B.B7 4D! 4"!A8! a  >H   B|)7 Ak"$ B̐7A#! a 4HB|! 4D! 4! B "B SE@A:! _ B !B')!B')!  T@A6! ^ A! ] B')B! B!  7p  R@A! ] B|! )!B!A=! [ B|!  7 B !B B WE@A! Z B !B')!B')!  TE@A! Y P@ Ak"$ B̐7A#! Y )0"P@ Ak"$ B€̐7A#! Y B|)! )!  Q@AL  7 P@ Ak"$ Bƀ̐7A#! X B>  7 Ak"$ BȀ̐7A#! W B|1@A! V B')! "PPE@A! U B' )7B'B'4B|>  ) 78 ) )7 )! )p! 4! )! ! 4L!AB'B7B!A! N B ! ! ! B"PP@A! M BBQE@A! L B')"B|!B')!B')" T@A! K B' B|7  B| > B|! ! B XE@A! H  ! !A! F  >P B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B̐7A#! F B(|)! B0|)!B' B8|)7B.5PP@A! E B' 7 5P! )! 4! ! ! )p!A! B B'  Ak"$ B̐7#!A! A B.7 B| > Ak"$ B̐7A"#! A B| )7 Aj"$ Aj"$A  B.7 Ak"$ B̐7A#! ? )B !B')!  XE@A! > B' 7 4@B B !B')!  XE@A! = B' 7B')!  XE@A! < B' 7 B.7 Ak"$ B̐7A#! ; )p! 4!A;! 9 Ak"$ B̐7A#! 9 A5! 7  ) 7 B< B|B7 B|B7 B|B7 B |B7 Ak"$ B̐7A#! 7 )P@ Ak"$ B̐7A#! 7 ))0"P@ Ak"$ B̐7A#! 7 )!  7 Ak"$ B̐7A#! 6 4@! 4D!A"! 4 B>#P@ Ak"$ B̐7A#! 5 #)0"P@ Ak"$ B̐7A#! 5 )"P@ Ak"$ B̐7A#! 5   )@7 Ak"$ B̐7A#! 4 A5! 2  B.7 Ak"$ B̐7A#! 2 B')! ) B B WE@A! 1 )B " XE@A! 0 B' 7 4@B !B')"B B ! B !B')!B')!  WE@A! . B " XE@A! - B' 7B')!  XE@A! , B' 7  B.7 Ak"$ B̐7A#! * 4@!A ! ( B7 B !  7X B| 7 B| 7 B| 7 Ak"$ B̐7A#! ( B |)!B' )X7B' )X7B.5PP@A! ' B' 7 B')!B')! B7 B| )X7 B| 7 B| 7 Ak"$ B̐7A#! % B |)!B' )X7B' )X7B.5PP@A! $ B' 7A! " B'  Ak"$ B̐7#!A! ! B'  Ak"$ Bā̐7#!A! B7 )B !  7` B| 7 B| 7 Ak"$ BƁ̐7A#! B|)!  7B')!B')! B7 B| 7 B| )`7 B| 7 B | 7 Ak"$ Bǁ̐7A#!  B' )`7B' )`7B.5PP@A!  B' )7A!  B' ) Ak"$ Bʁ̐7#!A!   B|B7 B| B 7 B< B|B7 B| B|7 B|B7 B |B7 Ak"$ B΁̐7A#!  A!   7 B| 7 Ak"$ Bс̐7A#!   7 B| 7 Ak"$ BӁ̐7A#!   7 B| 7 Ak"$ BՁ̐7A#!   7 B| 7 Ak"$ Bׁ̐7A#!   7 B| 7 Ak"$ Bف̐7A#!  B7 B| 7 Ak"$ Bہ̐7A#!   )7 B| 7 Ak"$ B݁̐7A#!  7 B| 7 Ak"$ B߁̐7A#!  7 B| 7 Ak"$ B̐7A#!  7 B| 7 Ak"$ B̐7A#!   7 B| 7 Ak"$ B̐7A#!  Bʹ7 B|B7 Ak"$ B̐7A#!   A #!@@@@@@@@@   #(M@ Ak"$ BА7A#!  Ak"$  )7 Ak"$ BА7A#!  )P@ Ak"$ BА7A#!   ))@7 Ak"$ BА7A#!  B*1@A!  Aj"$ Aj"$A Ak"$ BА7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@ &  A(k"$#)0"P@ Ak"$ BԐ7A#!  )PP@A$!  )0P@ Ak"$ BԐ7A#!  )0)8"PP"@A!  )05PP@A!    )07#P@ Ak"$ BԐ7A#!   )0#)078 )0B> A(j"$ Aj"$A  7 E@A!  "P@ Ak"$ BԐ7A#!   )!A!  B!  7 )05!  > Ak"$ BԐ7A#!  B7 B|B 7 Ak"$ BԐ7A#!  ) 7 Ak"$ BԐ7A#! Bޔ 7 B|B7 Ak"$ BԐ7A#!  )7 Ak"$ BԐ7A#! B7 B|B 7 Ak"$ BԐ7A#!  57 Ak"$ BԐ7A#!  Ak"$ BԐ7A#!  Ak"$ BԐ7A#!  B7 B|B7 Ak"$ BԐ7A#!  BŪ7 B|B7 Ak"$ BԐ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@ #  #(M@ Ak"$ Bؐ7A#!  A8k"$#)0"P@ Ak"$ Bؐ7A#!  )"P@A!!   70 !  7( P@ Ak"$ Bؐ7A#!   )8!  7  R@A!  5BR@A!  #7 B*1@A!  ) P@ Ak"$ Bؐ7A#!  ) )0"P@ Ak"$ Bؐ7A#!  B7 B78 B> B| 7 A8j"$ Aj"$A  7 Ak"$ Bؐ7A#!  )(!A !  5!  > Ak"$ Bؐ7A#!  B7 B|B 7 Ak"$ Bؐ7A#!   )07 Ak"$ Bؐ7A#!  B7 B|B7 Ak"$ Bؐ7A#!  )(7 Ak"$ Bؐ7A#! B7 B|B7 Ak"$ Bؐ7A#!  )7 Ak"$ Bؐ7A#! B7 B|B 7 Ak"$ Bؐ7A#!  57 Ak"$ Bؐ7A#!  Ak"$ Bؐ7A#!  Ak"$ Bؐ7A#!  B7 B|B7 Ak"$ Bؐ7A#!  B7 B|B7 Ak"$ Bؐ7A#!   A #!@@@@@@@@@  #(M@ Ak"$ Bܐ7A#!  Ak"$ B'7 Ak"$ Bܐ7A#!  B'B'4 4|>B 4B B S@A !   B'7 Ak"$ Bܐ7A#!  Aj"$ Aj"$A Ak"$ Bܐ7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!"####$%%%%%%&''(((()*+,---../0111111223456666778999:;<=>?@ABBBCDEFGHIJKLMNO #(M@ Ak"$ B7A#! Q Ak"$B.1@A! P B.1@A! O B.5PP@A! N B.1E@A! M B! B'4!B')B')}" }!B'4!  }!B'4!  }"B B ! B B S@A! K BS@A! J  B.7 Ak"$ B7A#! I B!B!A! G )0B|! ! !  78B')! B')SE@A%! F  70  B|)!  7H  7 B|B< Ak"$ B7A#! E B|1E@A! D )8!A! B )HP@ Ak"$ B7A#! C   )HB|7 Ak"$ B7A#! B B|5"B_"B|"BBX@A! A B"BQE@A"! @ )8B|! !A! = B Q@A! = )8!A ! ;  B.7 Ak"$ B7A#! ; )8P@A! : B.)PP@A4! 9 B')!B')!B SE@A! 8 B!A/! 6 B|! )"P@ Ak"$ B7A#! 6 B )MS@A3! 5 B|" S@A.! 4 A! 3 Aj"$ Aj"$A Ak"$ B7A#! 2 B|)! )! PPE@A,! 1 B. 7B'!A:! / P@ Ak"$ B7A#! 0 B|! )"PPE@A! .  " QE@A7! -  )7 B')! "PP"E@A! + B' )7B'B'4B|> E@A! )   7  B|7 Ak"$ B̀7A#! ( Aj"$ Aj"$A Aj"$ Aj"$A B.1E@A! % B< Ak"$ Bр7A#! $ B|)!B.5!  >, ! B.7 B| 7 Ak"$ BԀ7A&#! # 5,PPB!A! ! Aj"$ Aj"$A Aj"$ Aj"$A #)0"P@ Ak"$ Bـ7A#! B> B'7 Ak"$ Bހ7A#!  B7 B|B%7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   B'7 Ak"$ B7A#!  B7 B|B67 Ak"$ B7A#!   > )H)!  7@ Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )@7 Ak"$ B7A#!  BЊ7 B|B 7 Ak"$ B7A#!   57 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Bի7 B|B7 Ak"$ B7A#!   7@  >(  >$  > Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  4(7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  4$7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )@B B 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   4 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  B')!B')!B SE@A!  B!B!A!  B|! )"P@ Ak"$ B7A#!  5BRE@A! B|" S@A! B| < Aj"$A  )8! "PPE@A! #)0 QE@A! B!  !A!  )"PPE@A!   )QE@A!  B!A!  B< Bu7B!A!  B!A!  A 5 ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOOOOOOOPQRSTUVWXXXXYYZ[\]^_`abcdefghiiiiiijklmmmnnnnnoopppqrstuvwxyz{|}~ #(AjM@ Ak"$ B7A#!  Ak"$ Ak"$ B7A#!  B.)! )!  7H PE@A !  B. 7  B'7 Ak"$ B7A#!  B.)!  7B'5!  >0B'5!  >,B'4!  >(B'4!  >$B')!  7B')!  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )H )}B=#!7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B.47 Ak"$ B7A#!  BŊ7 B|B 7 Ak"$ B7A#!   507 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   ) )}B B 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   5,7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   4(7 Ak"$ B7A#!  BÇ7 B|B 7 Ak"$ B7A#!   4$7 Ak"$ B7A#!  Ak"$ B7A#!  1"@A!  B')!B')!B SE@A!   7B!A%!  )B|! !  7x  7 )!  7 P@ Ak"$ B7A#!  )8! B |! !  7  7 Ak"$ B7A#! ~ B|5!  >  )B |7 Ak"$ B7A#! } B|5!  > 1"E@A! | )PPE@A! { ))!  7p )5!  >0 )5!  >, )5!  >< )4!  >( ))M!  7 Ak"$ B7A#! y B7 B|B7 Ak"$ B7A#! x  )x7 Ak"$ B7A#! w B7 B|B 7 Ak"$ B7A#! v  507 Ak"$ B7A#! u B7 B|B 7 Ak"$ B7A#! t  5,7 Ak"$ B7A#! s BΒ7 B|B 7 Ak"$ B7A#! r  5<7 Ak"$ B7A#! q B7 B|B7 Ak"$ B7A#! p  )p7 Ak"$ B7A#! o B7 B|B 7 Ak"$ B7A#! n  5 5 }B7 Ak"$ B7A#! m B7 B|B 7 Ak"$ B7A#! l  4(7 Ak"$ B7A#! k B7 B|B 7 Ak"$ B7A#! j  )7 Ak"$ B7A#! i Ak"$ B7A#! h Ak"$ B€7A#! g )xB|" )S@A$! e 1"E@A! d B')!A! b  7  7P )!  7 4!  >( 4!  >$ )!  7 )!  7 4!  >8 4!  >4 1!  < 1!  < Ak"$ Bǀ7A#! b B7 B|B7 Ak"$ BȀ7A#! a  )7 Ak"$ Bɀ7A#! ` B7 B|B7 Ak"$ Bʀ7A#! _  4B B 7 Ak"$ Bˀ7A#! ^ B7 B|B7 Ak"$ B̀7A#! ]  )`7 Ak"$ B̀7A#! \ Bۊ7 B|B 7 Ak"$ B΀7A#! [  4(7 Ak"$ Bπ7A#! Z B7 B|B 7 Ak"$ BЀ7A#! Y  4$7 Ak"$ Bр7A#! X B7 B|B 7 Ak"$ BҀ7A#! W  )7 B| )7 Ak"$ BӀ7A#! V B7 B|B7 Ak"$ BԀ7A#! U  487 Ak"$ BՀ7A#! T B7 B|B7 Ak"$ Bր7A#! S  447 Ak"$ B׀7A#! R Bׇ7 B|B 7 Ak"$ B؀7A#! Q  1< Ak"$ Bـ7A#! P B7 B|B 7 Ak"$ Bڀ7A#! O  1< Ak"$ Bۀ7A#! N BÄ7 B|B 7 Ak"$ B܀7A#! M  )P7 Ak"$ B݀7A#! L Ak"$ Bހ7A#! K Ak"$ B߀7A#! J ))! PPE@A! H )! )! )! "P! ! PE@A! G 4! PPE@A! E )!  7`  > PPE@A! C )!A! A B!A! @ B!A! ? B!A! >  B.7 Ak"$ B7A#! > B!A! <  7  7@ ))!  7 Ak"$ B7A#! < B7 B|B7 Ak"$ B7A#! ;  )7 Ak"$ B7A#! : B7 B|B 7 Ak"$ B7A#! 9  5B7 Ak"$ B7A#! 8 Bޔ 7 B|B7 Ak"$ B7A#! 7  )7 B| )@7 Ak"$ B7A#! 6 B7 B|B7 Ak"$ B7A#! 5  )h7 Ak"$ B7A#! 4 B̄7 B|B 7 Ak"$ B7A#! 3  )X7 Ak"$ B7A#! 2 Ak"$ B7A#! 1 Ak"$ B7A#! 0 )B|! B')! B')SE@A! .  B|)"P@ Ak"$ B7A#! . )0! )! P! ! PE@A! - )!  7  7h PPE@A! + )!  7  7X  B|7 Ak"$ B7A#! ) )1! B|5!  >B XE@A! ( B!B!A! & B' B|")! )!A! % B!A! $ B!A! # B.7 Ak"$ B7A#! # B'7 Ak"$ B7A#! " Aj"$ Aj"$A B'7 Ak"$ B7A#! Aj"$ Aj"$A B!A/!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )xP@A!  Ak"$ B7A#!   5 5 }B7 Ak"$ B7A#!  Ak"$ B7A#!  B')B| )xQE@A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A! B'5!  >0B'4!  >(B'4!  >$B'5!  >, Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  507 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  4(7 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!   4$7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   5,7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  1!A"!  A ~#!@@@@@@@@@@@@@@@@@@@@@@ 8  #(M@ Ak"$ B7A#!  Ak"$ B'7 Ak"$ B7A#!  B'1! 1 P!  Q@A1!  B' < 1 "E@A+!  B'4!B'B>B')!B')! PE@A!   >B'B'4 |>B'B7B'B7 B'7 Ak"$ B7A#!  4!A!   > B7 B|B< Ak"$ B7A#!  4B|! BPPE@A! B'5PP@A! Aj"$ Aj"$A "P@ Ak"$ B7A#!  B7B')"PPE@A)! "P@ Ak"$ B7A#!   7 B' 7A !  B' 7A'!   B'7 Ak"$ B7A#!  A!   B'7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$B'1E@A!   ) 7 B|B< Ak"$ B7A#!  B|1! B(| < Aj"$ Aj"$A B(|B< Aj"$ Aj"$A A #!@@@@  #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )B')7B' )7B'B'4B|> Ak"$ B7A#!  Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@ "  #(M@ Ak"$ B7A#!  A(k"$B'4"BP@A!  B.4"BPPE@A !   B|!  B B S! 48B B !   !B SE@A!   B B SE@A!  48! B B B B S!B'  }>B')! "PPE@A!  )!B' 7 PE@A!  B'B7  7 B|!A!  )07 B| 7 B|B< Ak"$ B7A#! 4B|! ) ! B B B SE@A!  >B')! "PPE@A!  )!B' 7 PE@A!  B'B7A!  B| 7 A(j"$ Aj"$A B|B7 A(j"$ Aj"$A Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ 58BB! 58BB!B B BT! B" )(TE@A!   >  ) B|7 Ak"$ B7A#!  B|5! B|  5BPP< Aj"$ Aj"$A  7 B| )(7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$ 40B B B! 40 BB|B B "B! 40 B}"B B !B WE@A! B!B B BT! B B " ) TE@A!   ) B|7 B| B> Ak"$ B7A#!  Aj"$ Aj"$A  7 B| ) 7 Ak"$ B7A#!  Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A0k"$ )8P@ Ak"$ B7A#! )8B|!  7(  7 Ak"$ B7A#! 5PP@A!  )8B|!  7   7 Ak"$ B7A#!  )(7 Ak"$ B7A#!  B|5P@A!    ) 7 Ak"$ B7A#!  A0j"$ Aj"$A B')!B')!B')! )84!  7 B| 7 B| 7 B| > Ak"$ B7A#!  A !  A0j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$  ) 7 Ak"$ B7A#!  1E@A!   ) 7 Ak"$ B7A#! ) P@ Ak"$ B7A#! B')!B')! ) 4"B!  BB|B B "B!  B}"B B !B WE@A! B!B B BT! B B " TE@A!   B|7 B| > Ak"$ B7A#! ) B')7B' ) 7 B'7 B|B> Ak"$ B7A#! Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B#7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  A(k"$B')! !  7 PPE@A! B')!B')! 4"BBB!  |B B "B!  B}"B B !B WE@A! B!B B BT! B B " TE@A!   B|7 B| > Ak"$ B7A#! B')!B')!B')! ) 4!  7 B| 7 B| 7 B| > Ak"$ B7A#! B' ) )7 B'7 B|B> Ak"$ B7A#!  B0| ) 7 A(j"$ Aj"$A  7 B| 7 Ak"$ B7A#!  Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@   #(M@ Ak"$ B7A#! A(k"$ )0P@ Ak"$ B7A#!  )0B |7 Ak"$ B7A#! B|5!  > )0B |!  7  7 Ak"$ B7A#! B|5!  >  )0B|7 Ak"$ B7A'#!  B|)!  7  ) 7 Ak"$ B7A#!  B|5 5QE@A!  5 5QE@A !  )P! B8| < A(j"$ Aj"$A B!A !  A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A0k"$ 1H"E@A!  )8P@ Ak"$ B7A#!  )8)!  7 )8B|! )@!  7 B| 7 B| 7 Ak"$ B7A%#!  B|1E@A!  ) P@A!  ) !  7( )8P@ Ak"$ B7A#!  )8B |7 Ak"$ B7A #! B|5! )85 " }"BBT@A!  )87 B| )(7 B| > B| > Ak"$ B7A#!  B|1E@A!  A0j"$ Aj"$A )8B | BBB|!  )(7  )8B |7 B| B|> Ak"$ B7A#!  A0j"$ Aj"$A A0j"$ Aj"$A )@!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@ >  #(AjM@ Ak"$ B7A#!  Ak"$ B8|A#! 5 5}"BB"B"BR@A  7 P@ Ak"$ B7A#!   B |7 B| > B |  |> Ak"$ B7A#!  B|1E@A8!  B8| 5BB|" )7 ) !B!A!    B8| B|)7 B TE@A!  B8| BB|)"P@ Ak"$ B7A#!  B|"B"BT@A !  A:!  B(|B7 B0|B7 B(| B8|)7 B0| )7 B'7 Ak"$ B7A#!   B(|)! B(|)! PE@A(! B'B'4 5|B|> B(|B7 B0|B7 B'7 Ak"$ B7A#! B|B< Aj"$ Aj"$A "P@ Ak"$ B7A#!  B7B')"PPE@A6! "P@ Ak"$ B7A#!   7 B' 7A!!  B' 7A4!  B|B< Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@ >  #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!  ) B |!  7 Ak"$ B7A #!  5! ) 5 ! )(! ) !B!A !  B | BBB|!  7 B|! B|!  > P@ Ak"$ B7A#!   )"P@A!   }"BBTE@A!   "PPE@A!  )!  7 PE@A!  B7A!  B |7 B| > Ak"$ B7A#! )0 5B}! )()PE@A! Aj"$ Aj"$A  70 B'7 Ak"$ B7A#!  )()! )()! PE@A.!  B'B'4 )0|> )(B7 )(B7 B'7 Ak"$ B7A#!  A!  "P@ Ak"$ B7A#!   B7B')"PPE@A Ak"$ B7A#!  B|1E@A!  B| )(7 B|B< A8j"$ Aj"$A B|B7 B|B< A8j"$ Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@    #(M@ Ak"$ B7A#!  A8k"$ )@P@ Ak"$ B7A#!  )@B |!  70  7 Ak"$ B7A #!  B|5!  >$  )@B |7 Ak"$ B7A #!  B|5 5$}"B!  B}"B"P@A!  B T@A!  5P! )H! 5$! )@!B!A !  P@ Ak"$ B7A#!  B |!  |" BB!   |BBB| B|)7 B|! B T@A !   >  )07 B| > B |  |> Ak"$ B7A#! B|1E@A! B| 5 > A8j"$ Aj"$A 1T"E@A! )@)"PPE@A!  7( )@5BQ@A!  )@B|!  7 B| 7 B|B7 Ak"$ B7A%#!  B|1E@A!  )HP@ Ak"$ B7A#!  )H 5PBBB| )(7 B|B> A8j"$ Aj"$A B> Ak"$ B7A#!  )(!A!  B|B> A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ )HP@ Ak"$ B7A#! )H5 !  >$  )P7 )HB |!  78 B| 7 B| > B| 1X< Ak"$ B7A#! 5"P@A!  5$|B|! )8 BBB|)! ! BQE@A ! B| 7 Aj"$ Aj"$A  >(  >,  70  )HB |7 Ak"$ B7A #!  B 5$ B|5} 5(|B|BX@A!   )HB |7 B| 5,> Ak"$ B7A#!  B| )07 Aj"$ Aj"$A B|B7 Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ q  !"""#$%&''''()*+,,,,-./0111123456789:;;;<<<<=>??@ABCCDEFGGHI #(AjM@ Ak"$ B7A#! K Ak"$ )P@ Ak"$ B7A#! K ))"BQ@A! J BQ@A! I )B7B!A ! G  7P  ) B|B|)7 Ak"$ B7A#! G )PB|!  ))T@A! E ))P@A! D B.1@A! C B!B!B!  7`  7h  78 ))B|! ) B|!  7B!A! @  7H B|  B|7 B|)"P@ Ak"$ B7A#! A $ ) Ak"$ B7Av!A #! @ )HB|! )8! )`! )h! )!  ))T@A! > B.1E@A! =  Ak"$ B7A#! < )P@ Ak"$ B7A#! < B.)!  7B.)!  7x )!  7X  ))7 Ak"$ B7A#! ; B|)! B|)!  7 B| 7 Ak"$ B7A#! : B|)!  7@ B|)!  7 B|B7 B|B7 B|B7 Ak"$ B7A#! 9 B7 B|B7 Ak"$ B7A#! 8  )7 B| )@7 Ak"$ B7A#! 7 B7 B|B7 Ak"$ B7A#! 6 Ak"$ B7A#! 5 B.)!  B|7 B|B7 B|B7 B| )8 }7 Ak"$ B7A#! 4 B(|)! B |)!  B|7 B| 7 B| 7 Ak"$ B7A#! 3 B |)!  7p B|)!  7 Ak"$ B7A#! 2  )7 B| )p7 Ak"$ B7A#! 1 B7 B|B7 Ak"$ B7A#! 0 Ak"$ B7A#! /  B|7 B|B7 B|B7 B| )X )8}7 Ak"$ B7A#! . B(|)! B |)!  B|7 B| 7 B| 7 Ak"$ B7A#! - B |)!  7p B|)!  7 Ak"$ B7A#! ,  )7 B| )p7 Ak"$ B7A#! + B7 B|B 7 Ak"$ B7A#! * Ak"$ B7A#! )  )x )`}!B!A;! ' B| | B0|< B|! ! B XE@A=! & B !  B ~}! BT@A:! % A! $ BTE@A! $ B| | B0|<  B|7B }!B }! B| B|  B?|7 B| 7 Ak"$ B€7A#! # B |)!  7p B|)!  7 Ak"$ BÀ7A#! "  )7 B| )p7 Ak"$ BĀ7A#! ! BƂ7 B|B7 Ak"$ Bŀ7A#! Ak"$ Bƀ7A#!   ) )h}!B!A!  B| | B0|< B|! ! B XE@A!  B !  B ~}! BT@A!  A!  BTE@A!  B| | B0|<  B|7B }! B| B| B }B?|7 B| 7 Ak"$ BҀ7A#!  B |)!  7p B|)!  7 Ak"$ BӀ7A#!   )7 B| )p7 Ak"$ BԀ7A#!  B7 B|B7 Ak"$ BՀ7A#!  Ak"$ Bր7A#!  Ak"$ B׀7A#!  Ak"$ B؀7A#!  Ak"$ Bـ7A#!  )B7 Aj"$ Aj"$A  Ak"$ Bހ7A#!  B.)!B.)! )!A ! )B7 Aj"$ Aj"$A Aj"$ Aj"$A  7 B|B7 Ak"$ B怬7A#!  7 B|B7 Ak"$ B耬7A#!  7 B|B7 Ak"$ B뀬7A#!   7 B|B7 Ak"$ B퀬7A#!  B7 B|B27 Ak"$ B7A#!   A ~#!@@@@@@@@   Ak"$ #7 B'7 Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  5!B 4SE@A !  B! 1! B |  B PP> B$| < B%| BBPP< Aj"$ Aj"$A BBPP!A!  A #!@@@@@@@  #(M@ Ak"$ B7A#!  B. 4>B.5PP@A!  B' )7 Aj"$A B' ) Ak"$ B7#!A!  A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$B.4! B7 B| 7 B| 7 Ak"$ B7A#!  B.4! B|)!B' 7B' 7B.5PP@A!  B' 7 B!A! 4$B|! B "B B.4SE@A!  >$  7(B') B B|)!  70  7 Ak"$ B7A#! B|)! B8|B7 B|B7 B8| )07 B| 7 )(B !B')!B')! B8|)!  TE@A!  B|" 7B.5PP@A!   7A!    Ak"$ B7#!A!  Aj"$ Aj"$A B'  Ak"$ B7#!A!   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@    #(M@ Ak"$ B7A#!  Ak"$B!A!  B|! B')! B.4|B|"B B !  B|)P! P@A!  B7 B !  78 B !  70 B| 7 B| 7 Ak"$ B7A#!  B|)!B' )07B' )07B.5PP@A!  B' 7 B!A! 4$B|! B "B )8B SE@A!  7(  >$B')!  B.4|B|B B B|)!  7 Ak"$ B7A#! )(B !B')!B')! B|)! B|)!  TE@A!  B|" 7B.5PP@A!   7A!    Ak"$ B7#!A!  Aj"$ Aj"$A B'  Ak"$ B7#!A!   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ *  !"#$%&'() #(M@ Ak"$ B7A#! + A k"$B.B*7B.B7 B.7 B|B7 B|B7 Ak"$ B7A!#! * 1@A(! ) B.)PP@A&! ( B.B*7 B.7 B|B*7 B|B7 Ak"$ B7A!#! ' B|1E@A$! & B.)B*R@A"! % B.)"BR@A"! $ B.7 Ak"$ B7A#! # B|)BR@A ! " B.7 B|B 7 Ak"$ B7A##! ! B.7 Ak"$ B7A#! B|)B R@A!  B.7 B|B 7 Ak"$ B7A#!  B|)BR@A!  B.7 Ak"$ B7A#!  B|)BR@A!  B.7 B|B7 Ak"$ B7A #!  B|)BR@A!  B.7 Ak"$ B7A#!  B|)BR@A!  A j"$ Aj"$A BҔ7 B|B 7 Ak"$ B7A#!  BҔ7 B|B 7 Ak"$ B7A#!  BŔ7 B|B 7 Ak"$ B7A#!  BŔ7 B|B 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#! BÓ7 B|B 7 Ak"$ B7A#! B܏7 B|B 7 Ak"$ B7A#! B܏7 B|B 7 Ak"$ B7A#!  B܏7 B|B 7 Ak"$ B7A#!  B܏7 B|B 7 Ak"$ B7A#!   A ~}|#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ V  !"###$%&&'()***+,-./0123456789:;<=>?@ABCDEFGHIJKLM #(M@ Ak"$ Bđ7A#! O Ak"$ B,|B> B(|C8 B$|C8 B8|D9 B0|D9 B |B>B!B!Bڤ!A! M B|! B WE@A! L B " WE@A! K  }! B !A! I B WE@A1! I B,|B>B! BBR@A! G B,|4BBR@A! F B|B> B|B>  B|7 B|B> B |B> Ak"$ Bđ7A$#! E B|1E@A! D B|5BR@A! C B|B>  B|7 B|B> B |B> Ak"$ Bđ7A$#! B B|1@A! A B|5BR@A! @ B|B>  B|7 B|B> B |B~> Ak"$ Bđ7A$#! ? B|1E@A! > B|5BR@A! = B |B> B |B< B!|B< B"|B< B#|B<  B!|7 B|Bp< Ak"$ Bđ7A#! < B |1"BR@A! ; B!|1BR@A! : B"|1BR@A! 9 B#|1BR@A! 8 B |B> B |B< B!|B< B"|B< B#|B<  B!|7 B|B< Ak"$ Bđ7A#! 7 B |1"BR@A! 6 B!|1BR@A! 5 B"|1BR@A! 4 B#|1BR@A! 3 B8|B7 B8|+" a@A! 2  bE@A! 1 B0|B~7 B0|+! B8|+" a@A>! 0  bE@A B(|*" [@A:! . B$|B~> B(|* B$|*[@A8! - Ak"$ Bđ7A#! , B!A+! * B|! B B BT"B B "BS@A*! ) BBR@A6! ( Ak"$ Bđ7A#! ' 1E@A4! & Aj"$ Aj"$A B,| >A ! # B7 B|B7 Ak"$ Bđ7A#! # B7 B|B7 Ak"$ Bđ7A#! ! B7 B|B 7 Ak"$ Bđ7A#!  B7 B|B 7 Ak"$ Bđ7A#!  BŒ7 B|B 7 Ak"$ Bđ7A#!  B7 B|B 7 Ak"$ Bđ7A#!  B7 B|B 7 Ak"$ Bđ7A#!  B7 B|B 7 Ak"$ BÀđ7A#!  Bψ7 B|B 7 Ak"$ Bŀđ7A#!  B7 B|B 7 Ak"$ Bǀđ7A#!  B7 B|B7 Ak"$ Bɀđ7A#!  B7 B|B7 Ak"$ Bˀđ7A#! B7 B|B7 Ak"$ B̀đ7A#! B7 B|B7 Ak"$ Bπđ7A#! B7 B|B7 Ak"$ Bрđ7A#!  B7 B|B7 Ak"$ BӀđ7A#!  B7 B|B 7 Ak"$ BՀđ7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ;  !"#$%&'()*+,- #(M@ Ak"$ Bȑ7A#! / Ak"$B.B>B.B> B7 B|B7 Ak"$ Bȑ7A#! . )! )!A! , )(! )X! PPE@A0! +  78  7`  7 B| 7 B|B,< Ak"$ Bȑ7A-#! * B|)"BSE@A-! ) B!B! )8!  7X  78  7(  )`7 B| 7 B|B=< Ak"$ Bȑ7A-#! ' B|)"BS@A! &  )8XE@A5! % B|" )8XE@A3! $  70 )8 }B|!  7 )` B }B?|!  7P BQ@A$! # B')!B')!B SE@A! "  7HB!A! B|! )! )! )!  Q@A!  B|" S@A!  A!   7@  7p  7h  7 B| )`7 B| 7 Ak"$ Bȑ7A*#!  B|1@A!  )0! )P! ) ! )H! )p! )@!A!    )P7 B| ) 7 Ak"$ Bȑ7A#!  B|)! B|1! B B QE@A !  E@A !  )hP@ Ak"$ Bȑ7A#!  )h > )0! )P! ) ! )H! )p! )@!A!   )`7 B|B7 B| 7 Ak"$ Bȑ7A*#!  B|1@A*!  )0! )P! ) !A !   )P7 B| ) 7 Ak"$ Bȑ7A#!  B|)! B|1E@A!  B' 7A!   )8XE@A9!  B|" )8XE@A7!  )8 }B|! )` B }B?|! ! !A! B.B.4B.4B.4BPP< Bԋ7 B|B 7 Ak"$ Bȑ7A#! B|)!  B|)7 B| 7 Ak"$ Bȑ7A#! B.B'5> Aj"$ Aj"$A  7 B| )87 Ak"$ Bȑ7A#!  7 B| )87 Ak"$ Bȑ7A#!   7 B| )87 Ak"$ Bȑ7A#!   7 B| )87 Ak"$ Bȑ7A#!   A ~#!@@@@@@  #(M@ Ak"$ B̑7A#!  B 1BXE@A!  B|B7 B|B7 Aj"$A B' 1BB|")! )! B| 7 B| 7 Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ BБ7A#!  A(k"$#)0"P@ Ak"$ BБ7A#!    4B|> )0P@ Ak"$ BБ7A#!  #P@ Ak"$ BБ7A#!   )0B(|7 B|B> Ak"$ BБ7A#!  5"B B BS@A!  A(j"$ Aj"$A B|B7 B |B7 B|Bܗ7 B | )07  B|7 Ak"$ BБ7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@ '  #(M@ Ak"$ Bԑ7A#!  Ak"$ ) P@ Ak"$ Bԑ7A#!  ) B(|!  7 B|B> Ak"$ Bԑ7A#!  5"B B BSE@A !  BQ@A%!  B Q@A%!   ) B,|7 B|B> Ak"$ Bԑ7A#!  B|5P@A!  #)0"P@ Ak"$ Bԑ7A#!   4!  B|> BBQE@A! #P@ Ak"$ Bԑ7A#! #1E@A! #Bu7 Aj"$ Aj"$A   ) 7 Ak"$ Bԑ7A#! ) ) ! "PP@A#!    ) 7 Ak"$ Bԑ7A#!  A !   B|7 Ak"$ Bԑ7A#!  A!  B7 B|B7 Ak"$ Bԑ7A#!   A ~#!@@@@@@@@   #(M@ Ak"$ Bؑ7A#!  Ak"$ )P@ Ak"$ Bؑ7A#!  ))(P"P@A!   ))7 B| ) 7 Ak"$ Bؑ7A#!  Aj"$ Aj"$A  Ak"$ Bؑ7A#!  ) )7(A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ K  !!!!""""####$%&' #(M@ Ak"$ Bܑ7A#! ) Ak"$#)0"P@ Ak"$ Bܑ7A#! ) # )R@A! (  )P7 Ak"$ Bܑ7A#! ' B|1@A! & Ak"$ Bܑ7A#! % )!  70 P@ Ak"$ Bܑ7A#! %  )P! B7( B7 B>0 )`BP! B" BB~}!  7( PE@A! $ B.)PP@A! # B! )`BPPE@A! ! B.)PPE@A! P@A8!   7  7 B( B|!  7@A!  )@!   7 Ak"$ Bܑ7A#!  )@B|!  78  7 B|B> Ak"$ Bܑ7A#!   )P7 Ak"$ Bܑ7A#!  B|1@A1!   )@7 B| )P7 B| )07 B| 1X< Ak"$ Bܑ7A#!   B7 B| )@7 B|B< B|B< B| )hB|7 Ak"$ Bܑ7A#!  )050PPE@A/!  B! E@A!  )0)(!B S@A-!   )07 Ak"$ Bܑ7A#!  Aj"$ Aj"$A   ) }7 B| )hB|7 Ak"$ Bܑ7A#!  A+!  )P7 Ak"$ Bܑ7A#! B|1!A)!  )87 B|B> Ak"$ Bܑ7A#!   )@7 Ak"$ Bܑ7A#! A*!   Ak"$ Bܑ7A#!  )! )0! )(!A!   Ak"$ BĀܑ7A#!  )! )0B7( )0! )(!A!  Aj"$ Aj"$A B7 B|B7 Ak"$ Bʀܑ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 4  #(M@ Ak"$ B7A#! ! Ak"$ )X!  70  )X7 B|B> Ak"$ B7A#! )0B!B(  BB~}B|!  7H B|!  7@  7 Ak"$ B7A#!  5P@A1!    )H7 Ak"$ B7A#!   )@7 Ak"$ B7A#!  B|5P@A+!   )H7 B| )X7 Ak"$ B7A#!  B|)!  78 B|)!  7( PP!  <' @A)!    )H7 Ak"$ B7A#!  1'"E@A!  )8) "PP@A'!  )850PP@A2!  1`"@A$!   )87 B| )hB|7 Ak"$ B7A#!  )850BQE@A!  #)0"P@ Ak"$ B7A#!  4BP@A!!  Aj"$ Aj"$A Ak"$ B7A#!  A!  )X7 Ak"$ B7A#! B|1E@A! )8B>0A!  )( }7 B| )hB|7 Ak"$ B7A#! A!   )@7 B|B> Ak"$ B7A#!  A!    )H7 Ak"$ B7A#!  Aj"$ Aj"$A Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$  ) 7 Ak"$ B7A#!  B|5"P@A!   ) 7 B | B|> Ak"$ B7A$#!  B|1E@A!  B(|B< Aj"$ Aj"$A B(|B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ i  !"##$$%%&&''(())**++,-./01112234556666666789:;<=>?@ABCCCDDDDDEFG #(M@ Ak"$ B7A#! I Ak"$ )(P@ Ak"$ B7A#! I B.5P! )(B|! )(B|! P@A! H )(#7 )( ) 7 )(B7 )(B7 )P@ Ak"$ B7A#! G )B|! ))! ) !B!A ! E )! ! ! PPE@A! D )!  Q@A! C  TE@A ! B B|!A! @ B|!A! ? 10"E@A>! ? B.5PP@A  )(7 )( 50>0 )( ) 7 B.5P! )8! P@A:! < )( 78 B.5P! )! P@A8! : )( 7 B.5P! )! P@A6! 8 )( 7 PPE@A! 6 B.5PP@A4! 5  )(78 )()"PPE@A! 3 B.5PP@A2! 2  )(78 B.5PP@A0! 0 )( 7@ )(B|!B.5P! )H! P@A.! . )( 7H PPE@A*! , B.5PP@A%! + B78 B7 B7 B7H Aj"$ Aj"$A B8|B Ak"$ B7#! B|B Ak"$ B7#! B|B Ak"$ B7#! B|B Ak"$ B7#!A$! ' B.5PP@A,! ' )( 7HA"! %   Ak"$ B7#!A"! $   Ak"$ B7#!A!! # )(B|  Ak"$ B7#!A! " B8| )( Ak"$ B7#!A! ! B8| )( Ak"$ B7#!A!   Ak"$ B7#!A!    Ak"$ B7#!A!  )(B8|  Ak"$ B7#!A!   )( Ak"$ B7#!A!  )H"PPE@A!  B.5PP@A!   )(7@ B.5PP@A!   )(7H )(B7@A$!  B| )( Ak"$ BĀ7#! )(B|B Ak"$ Bŀ7#!A$!  B| )( Ak"$ Bǀ7#!A!  B.5PP@A!   )(7@A!  B| )( Ak"$ Bˀ7#!A!  #)0"P@ Ak"$ B΀7A#!   5! 5!  > B ! B BB  !  > )(  |B>0B.5PP@A!  )( 78  )(7 )(! )!A!  )(! )! )8"PPE@A!  50 50TE@A!  )Q@A! ) R@A!  7 B| 7 Ak"$ Bۀ7A#! A!   7 B| 7 Ak"$ B݀7A#!  A!  Aj"$ Aj"$A )(B8|  Ak"$ B7#!  )( Ak"$ B7#!A!  )(# Ak"$ B7#! )(B| ) Ak"$ B7#! B Ak"$ B7#! B Ak"$ B7#!A!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ]  !"##$%&'()*+,-./0123456777889:;;<<==>>??@@AABBBBCCDE #(M@ Ak"$ B7A#! G A0k"$ )8P@ Ak"$ B7A#! G )8B|! )8)! )@! !A! E )! PPE@A! D )!  Q@A ! C  TE@A ! B B|!A! @ B|!A! ?  7(  7 ) PP@A! ? B! )@"PP@A.! =  7 )8!A! ; )! )8! )! )(! )"PP"E@A! : E@A! 9 )"PPE@A! 8 50 50TE@A! 7  7 B| 7 Ak"$ B7A#! 6 A! 4  7 B| 7 Ak"$ B7A#! 4 A! 2 )PP@A! 2 )8"PPE@A*! 1  )QE@A&! 0 B.5PP@A$! / B7 B.5PP@A! - B78 B7 B7 B7 B>0 B| 7 B| 7 A0j"$ Aj"$A B8|B Ak"$ B7#! B|B Ak"$ B7#! B|B Ak"$ B7#! B|B Ak"$ B7#!A! ) B|B Ak"$ B7#!A! ( B.5PP@A(! ( B7A! & B|B Ak"$ B7#!A! % B.5PP@A,! % B7A! # B Ak"$ B7#!A! " B.5PP@A! "  7  50>0B.5P! )8! P@A!  78 B.5P! )! P@A!   7 PPE@A7!  B.5PP@A!   78 B.5P! )! P@A!   7 PPE@A>>>?@@@@ABCCDDDDEFFGGHHHHIIIJKLMNOPPQRSTUVWXXYZ[\]^_``abcdefghhijklmnoppqr #(M@ Ak"$ B7A#! t Ak"$ )x )pS@A! s )XP@ Ak"$ B7A#! s )X)"P@A! r )p )p|" )xS@A! q )pBS@A! p )x! )p!A ! n  B|! B SE@A ! m  S@A! l BWE@A! k ! BQE@A8! i  BTE@A0! h BXE@A(! g  B|B"BTE@A! f B' |1"BTE@A! e B' B|3! B T! )h! ! @A! b B T@A! a  70  7( )X)PE@A#! `  78  7  7 B|B7 B|B< Ak"$ B7A#! _ B|)!  7@   ) |7 B| )8 ) }7 Ak"$ B7A#! ^ )@!  7H  7 B| )`7 B| )07 Ak"$ B7A#! \ B| )H7 B| )h7 B| )(7 Aj"$ Aj"$A  7 B| )X7 B|B< Ak"$ B7A#! Z B|)! )0PPE@A!! Y B.1E@A!! X  7H ! )`! )X)! )X)!  7 B| 7 B| )0 } |7 Ak"$ B7A#! W )H!A!! U  Bx|B"BTE@A! U B' |1"BTE@A! T B' B|3!A! R B| TE@A4! R !A! P  B?|B@!A! O BQE@A! O  B"BTE@A! N BXE@A! M  B|B"BTE@A! L B' |1"BTE@A! K B' B|3! )hB! B!B T! B!A! H  Bx|B"BTE@A! H B' |1"BTE@A! G B' B|3!A! E B| T@A! E  B?|B@!A! C  B| PE@A! C B } "B|B9BB' B~B:|1|B?"BT! )h B !  B !  B "BTE@A! B BXE@A! A  B|B"BTE@A! @ B' |1"BTE@A! ? B' B|3! B B  T!  B ! ! ! !A! <  Bx|B"BTE@A! < B' |1"BTE@A! ; B' B|3!A! 9 B| T@A! 9  B?|B@!A! 7  )h~!  ~!  BTE@A! 7  ~!B!  BTE@A! 5 BXE@A! 4  B|B"BTE@A! 3 B' |1"BTE@A! 2 B' B|3!  ! ! ! !A! /  Bx|B"BTE@A! / B' |1"BTE@A! . B' B|3!A! , B| T@A! ,  B?|B@!A! * B  T!  ~!A! ) ! )x!A ! ( )x!A ! ' )x"!A ! & B|B.7 B| )h7 B| )x7 Aj"$ Aj"$A B7 B|B 7 Ak"$ B7A#! %  7 B|B7 Ak"$ B7A#! #  7 B|B7 Ak"$ B7A#! !  7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! B!A!  B. B|! B7 B7 B|! BS@A!  B!A !  B. B|! B7 B7 B|! BS@A!  Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ (  #(M@ Ak"$ B7A#!  A8k"$ 1@B"BTE@A&!  B. B|"B|!  70 )"PP@A!  B)7 B|B7 B|B< Ak"$ B7A#!  B|)"PPE@A$!  3`PP@A"!  )(! PP@A !  B 1@B7hB!A!  ) |""P@ Ak"$ B7A#!    )(7  7( )h |! BT@A !   7  )07 B| 7 Ak"$ B7A#!  ) ! P@ Ak"$ B7A#!  )(! "PPE@A!   )7(  3`B|=` )(! PPE@A!  B| 7 A8j"$ Aj"$A  7(  )07 B| 7 Ak"$ B7A#! )(!A! B7 B|B7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@ 0  #(M@ Ak"$ B7A#!  A k"$BȊ))"P@ Ak"$ B7A#!   )(B !B BT! B ! )(BBBBT"BTE@A-!   B|)"B| BB|)!  7 P@ Ak"$ B7A#!  P@ Ak"$ B7A#!    B|7 Ak"$ B7A #!  B|1BR@A+!  ))(! PPE@A#!  )("P@ Ak"$ B7A#!    ))(7 ) )(7( )3`! ) B|=`B.5PE@A!!  BQE@A!!  10B"BTE@A'!  B. B|B|7 B| )7 Ak"$ B7A#!  )B7( B)7 B| )7 B|B< Ak"$ B7A#!  A j"$ Aj"$A 10B"BTE@A)! B. B|B|7 B| )7 Ak"$ B7A#! A!  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!  B7 B|B!7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A0k"$ 1@B"BTE@A!  7(B. B|!  7   7 Ak"$ B7A#! B!B!A!   1@< Ak"$ B7A#!  B|)""P@ Ak"$ B7A#!    )7 )B 1@B|!  7  7 BT@A!    ) 7 Ak"$ B7A#!  )8P@ Ak"$ B7A#!  )8B| )(B|" )7  )7 A0j"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ )HP@ Ak"$ B7A#! 1PB"BTE@A! )HB| B|!  78 )!  7 )!  7 B. B|!  70  7 Ak"$ B7A#! ) ! )!A!  "P@ Ak"$ B7A#!  )!  7  7 B| 1P< Ak"$ B7A#!  ) B 1PB}! )!  7 B T@A !   7(  )07 Ak"$ B7A#!  )8 )(7 )8 ) 7 Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A8k"$B!A! )!  7  7 B| 1< Ak"$ B7A#! )0! )!  "PP@A!  B7 B7  ) 7 Ak"$ B7A#!  1B|! BBTE@A!   < B!  7(B. B|!  7   7 Ak"$ B7A#!  )@P@ Ak"$ B7A#!  )@B| )(B|!  70 )!A!  A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ f  !"###$%%&'()*+,-./0123456 #(M@ Ak"$ B7A#! 8 Ak"$#)0"P@ Ak"$ B7A#! 8 # )R@A! 7 5p 5pB|"BPP@A! 6 B.4BPP@A! 5 5pBBTE@A/! 4 5p"!B!A ! 2 B|! B! B!B T@A! 1 )"PE@A! 0 B"BTE@A! /  <'B. B|!  7X  7 Ak"$ B7A#! .  1'< Ak"$ B7A#! - B|)!  7(  )X7 Ak"$ B7A#! , )(! ! ! B| 7 B|  5pB|7 Aj"$ Aj"$A )PP@A ! ( "P@ Ak"$ B7A#! ( )@"P@ Ak"$ B7A#! (  B"BTE@A! ' B| B|")! PPE@A-! & "P@ Ak"$ B7A#! &   )7  ) B}7 !A! $  7`  7 B| < Ak"$ B7A#! $ )`)! )`! 5p!A'! " 5pB"B ! !B!A3! ! B! B|! B T@A2!  7P  7H  78 B.7 Ak"$ B7A#!  )8BTE@A!  B. )8B|! )"PP@A!  B!  70 B.7 Ak"$ BĀ7A#!  )0PPE@A!  )0! P@ Ak"$ Bɀ7A#!  )!A!  B)7 B| )H7 B|B< Ak"$ B΀7A#!  B|)"PPE@A!   )P7hA!   7@  7 B| 7 Ak"$ BӀ7A#!  )@!A!  B.)!  5pB|B|B }B!  7P  7 B|B/7 Ak"$ Bր7A#!  B|)"PPE@A!  ! B| 7 B|  )P|7 Aj"$ Aj"$A B7 B|B 7 Ak"$ Bـ7A#!   )87 B|B7 Ak"$ Bۀ7A#!  7 B|B7 Ak"$ B݀7A#!  7 B|B7 Ak"$ B߀7A#! B7 B|B7 Ak"$ Bလ7A#!  B7 B|B7 Ak"$ B〜7A#!  B7 B|B!7 Ak"$ B倜7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ s  !"#$%%%%&'()*+,--.//01234567 #(M@ Ak"$ B7A#! 9 Ak"$ )h! )p )h}"B| PP@A! 8  )h| )pT@A! 7 B.4BPP@A! 6 BTE@A-! 5 #7P !B!A! 3 B|! B! B T@A! 2 )P)0"P@ Ak"$ B7A#! 2 )"PE@A! 1 B"BTE@A! 0  <B. B|!  7@  7 Ak"$ B7A#! /  )h7 B| 1< Ak"$ B7A#! .   )@7 Ak"$ B7A#! - Aj"$ Aj"$A )PP@A ! * "P@ Ak"$ B7A#! * )@"P@ Ak"$ B7A#! *  B"BTE@A! ) B| B|!B )X@A+! ( )h"P@ Ak"$ B7A#! (   )7  )h7   )|7A! &  78  7X  7 B| < Ak"$ B7A#! & )8! )X!A%! $ BȊ))"P@ Ak"$ B7A#! %  )hB"BTE@A! $  7H  B|)"B| )hB BB|)!  7( P@ Ak"$ B7A#! $ P@ Ak"$ B7A#! $   B|7 Ak"$ B7A #! # B|1BR@A! " B.5P@A! !  )() !B!A!  B! B|! B T@A!   70 B.7 Ak"$ B̀7A#!  )0BTE@A!  B. )0B|7 B| )(7 Ak"$ BЀ7A#!   B.7 Ak"$ BԀ7A#!  A!  B)7 B| )(7 B|B< Ak"$ B؀7A#!  A!  Aj"$ Aj"$A  )07 B|B7 Ak"$ Bۀ7A#!   )()!  7 Ak"$ B7A#!   ) 7 Ak"$ Bဠ7A#!  Ak"$ B†7A#!   )H7 Ak"$ B〠7A#!  Ak"$ B䀠7A#!  Ak"$ B倠7A#!  B7 B|B7 Ak"$ B怠7A#!  7 B|B7 Ak"$ B造7A#!  7 B|B7 Ak"$ B쀠7A#!  7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B򀠒7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ,  #(M@ Ak"$ B7A#! Ak"$ )P@ Ak"$ B7A#! )xP@ Ak"$ B7A#! ))!  70 ))!  78 ))!  7H )x4!  >$ )p ))T!  <" )! )p! )x! B! A!  )0! )`! )! )H! 1"! )8! 4$! )p! )x! )@! 1#! )!  PPE@A !  B|BTE@A !  B.4BPP@A!   XE@A!  TE@A!  E@A!  ! |!  7 B| 7 B| 7 Ak"$ B7A#!  B|1E@A!  )0! )8! )! )H! 1"! )p! )@! 1#! 4$! )x! BPPE@A!  z! B|!  <#  |B|!  7`A !  |7A!  B|! TE@A!   7@ )" B|1! A!  Aj"$ Aj"$A  7(#)0"P@ Ak"$ B7A#!  B<  7 B| )7 Ak"$ B7A#! B|)!  7P B|)!  7X Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )X7 B| )P7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )`7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )(7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ;  !" #(A0jM@ Ak"$ B7A#! $ Ak"$ )P@ Ak"$ B7A#! $ ))P@A:! # ))"P@ Ak"$ B7A#! # ))! 1(B Q@A9! "  7  7 )P@ Ak"$ B7A#! "  )7 B| )B|7 B|B< Ak"$ B7A#! ! B(|4! B|4! B |)! B0|)! B8|)!  7x B|)!  7` B| > B| 7 B| > B| 7 B|4!B S@A7! B B|4S@A5!  ))8PPE@A!  B SE@A!  )!B!A!  B|! )! )! )8!B WE@A!  )@!  |" )(TE@A!  B|" S@A!  B|B< Aj"$ Aj"$A  7X  7p P@ Ak"$ B7A#!  ) ! 1" B" BPP@A1!  B! )! B! A!  B|! )TE@A(!   B|1!  BB! B BTBBPPE@A!  |" P@ Ak"$ B7A#!   )! ) XE@A!  )TE@A!  )|7A! PPE@A!  B)7 B| 7 B|B< Ak"$ B7A#! )! )X! )p! )`! )! A!  7P  7h  )7 B| 7 Ak"$ B7A#! B|)" P@ Ak"$ B7A#! )! )! )X! )h! )P! )p! )`!A!   ))@7 B| B|7 B| )7 B|B7 B |B7 Ak"$ B7A#!  )x! )`!A !   ))8 B}7 B| B|7 B| )7 B| )7 B | )7 Ak"$ B7A#!  )x! )`!A !  B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 0  #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!  )(P@ Ak"$ B7A#!   ) )(! )() XE@A !   )()TE@A !  ) )() |7( ) )(! )(!A!  )(! PPE@A-!   )! ) XE@A!   )TE@A!   ) |7  )! ) XE@A!   )TE@A!   ) |7  ) ! ) XE@A!   )TE@A!   ) |7  )(! ) XE@A#!  )TE@A#!  ) |7(  )8! ) XE@A(!  )TE@A(!   ) |78  )0! ) XE@A !   )TE@A !   ) |70A !   ) 7 B|B7 B| 7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3  #(M@ Ak"$ B7A#!  A0k"$ )8P@ Ak"$ B7A#!  )8)"PPE@A2!  B!A!  )@! )P! ! PPE@A!  )P!  RE@A!   7 P@ Ak"$ B7A#!    B|7 Ak"$ B7A#!  ) !A!   )8)! )H!A!  )@! PPE@A!  P@ Ak"$ B7A#!   )! ) XE@A!   )TE@A!    )|7A! P@ Ak"$ B7A#!  )"PP@A0! B!  7 )8)!B!A#! )@! )P! ! PPE@A.! )P!  RE@A"!   7( P@ Ak"$ B7A#!   B|!  7 Ak"$ B7A#!  )! )(!A"!  B| 7 A0j"$ Aj"$A ) )@}" )|! !  7 B| 7  }!  7 B| 7 Ak"$ B7A#!  )!A!!  B|B7 A0j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ?  !!"#$%&'()* #(AjM@ Ak"$ B7A#! , Ak"$ )P@ Ak"$ B7A#! , ))pPP@A=! + ))! ))"P@A;! *  7  7x ))8!  7p  )> Ak"$ B7A#! ) B|)!  7h B|)!  7` B|A$#! B| )x7 B| )7 B|  )}7 ) )p}!  7X )1E@A*! (  ))! )x! )!B!A ! & )@! ! PPE@A! % )P" P@ Ak"$ B7A#! % 3 )|!  XE@A! $ TE@A! #  T@A ! " ! A ! B| 7  )7 B| 7 B| B|7 Ak"$ B7A#! )X B|)}! )` }! ) }!  7 B| 7 B| 7 Ak"$ B7A#!   ))P! B|) XE@A!   B|)TE@A!  ) B|) |7P  )7 B| B|7 Ak"$ B7A#!   )) ! B|) XE@A$!   B|)TE@A$!  ) B|) |7 B|)"PPE@A'!  B| B|) |7 ) )h7 ) )`7 ) )hB|7 ) )` )X}78 ) B|) ))|7 B7 B|B7 B|B7 B| )7 B |B7 B(|B7 B0|B7 B8|B7 B| B|7 B|B7 Ak"$ B7A#!   )x7 B| )7 Ak"$ B7A#!  Aj"$ Aj"$A ) ) )x}T@A5!   ))!A/!  )@! PPE@A!   )! B|) XE@A.!  B|)TE@A.!  B|) |7A.!  )B|7 Ak"$ B7A #! B|1PP@A9! )h! )`! )X!A+!  B7 B|B/7 Ak"$ B7A#!  Bݓ7 B|B 7 Ak"$ B7A#!  B7 B|B'7 Ak"$ B7A#!   A E ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHHIJKLMNOPQRSTUVWXYZ[\]^_`aaabbbbcdefghijklmnopqrstuvwxyz{|}~ #(AjM@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  )""P@ Ak"$ B7A#!   )BvQ@A!  #7 )!  7  R@A!  P@ Ak"$ B7A#!  1@A!  B| )7 B| B|B|A#!#P@ Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  B7#)0"P@ Ak"$ B7A#!  B70#)0"P@ Ak"$ B7A#!  B7#)0"P@ Ak"$ B7A#!  B7  B|7 Ak"$ B7A'#!  B|)BuQ!  <+ E@A(!  ))0"P@ Ak"$ B7A#!   4"BPE@A!  4BPE@A!  )PE@A!  )"P@ Ak"$ B7A#!   5BQE@A!  ))"P@A!  ))8"Bx|!  70  T@A!   ))T@A!  E@A5!  ))0"P@ Ak"$ B7A#!  ) )Q@A!  )PE@A1!  4BP@A!  )1@A!  )1@A!   )7 Ak"$ B7A#!  ))!  7 ))!  7  ))@7 Ak"$ B7A#!  ) )}"B! B|)! B|)"PPE@A!   7H  7P  7 B| 7 Ak"$ B7A#!  B|4! )H! )P!A=!  B!  } B|T@A B |B> Ak"$ B7A#!   )7 B| )P7 Ak"$ B€7A#!   )7 B|B> B |B> Ak"$ BÀ7A#!   )B8|7 Ak"$ BĀ7A#!  Aj"$ Aj"$A !A>!   )7 Ak"$ Bǀ7A#!  A3!  )B<  )7 Ak"$ Bɀ7A#!  )0!A2!   7  7 ))!  7 B|)!  7 B|)!  7 B|)!  7x ))@!  7p ))`!  7h ))P!  7 Ak"$ Bˀ7A#!  B7 B|B7 Ak"$ B̀7A#!   )07 Ak"$ B̀7A#!  B7 B|B7 Ak"$ B΀7A#! ~  )7 Ak"$ Bπ7A#! } B7 B|B7 Ak"$ BЀ7A#! |  )7 Ak"$ Bр7A#! { Bٗ7 B|B7 Ak"$ BҀ7A#! z  )7 Ak"$ BӀ7A#! y B7 B|B7 Ak"$ BԀ7A#! x  )7 Ak"$ BՀ7A#! w B7 B|B7 Ak"$ Bր7A#! v  )x7 Ak"$ B׀7A#! u Bߔ7 B|B 7 Ak"$ B؀7A#! t  )p7 Ak"$ Bـ7A#! s B7 B|B7 Ak"$ Bڀ7A#! r  )7 Ak"$ Bۀ7A#! q B7 B|B7 Ak"$ B܀7A#! p  )h7 Ak"$ B݀7A#! o B7 B|B7 Ak"$ Bހ7A#! n  )7 Ak"$ B߀7A#! m B7 B|B7 Ak"$ B7A#! l Ak"$ Bး7A#! k 1+! )0!A*! i ) ))B|7  )B8|7 Ak"$ B〸7A#! i 1+!A(! g B')TE@A! g Ak"$ B怸7A#! f B7 B|B!7 Ak"$ B瀸7A#! e B')7 Ak"$ B耸7A#! d B7 B|B 7 Ak"$ B逸7A#! c Ak"$ Bꀸ7A#! b A! ` B| )7 B| B|B|A#!  B|)7p  B|)7x  )@7 Ak"$ B쀸7A#! `  B|)! B|)! PP@A! _ B !B!B!A! ] Ak"$ B7A#! ] B7 B|B!7 Ak"$ B񀸒7A#! \ B')7 Ak"$ B򀸒7A#! [ B7 B|B 7 Ak"$ B󀸒7A#! Z Ak"$ B􀸒7A#! Y A! W  7  7 B| 7 Ak"$ B7A#! W ))@ ))}! B|)! B|)!A! U ))!  7 ))!  7 Ak"$ B7A#! U B7 B|B 7 Ak"$ B7A#! T  )07 Ak"$ B7A#! S B7 B|B7 Ak"$ B7A#! R  )7 Ak"$ B7A#! Q B7 B|B7 Ak"$ B7A#! P  )7 Ak"$ B7A#! O B7 B|B7 Ak"$ B7A#! N Ak"$ B7A#! M B7 B|B7 Ak"$ B7A#! L B7 B|B"7 Ak"$ B7A#! J B7 B|B7 Ak"$ B7A#! H   )B|7 Ak"$ B7A#! F ))!  7` B|5!  >, Ak"$ B7A#! E B7 B|B 7 Ak"$ B7A#! D  )7 Ak"$ B7A#! C Bց7 B|B7 Ak"$ B7A#! B  )`7 Ak"$ B7A#! A B7 B|B 7 Ak"$ B7A#! @  5,7 Ak"$ B7A#! ? B7 B|B7 Ak"$ B7A#! > Ak"$ B7A#! = ))!  7 Ak"$ B7A#! < B7 B|B7 Ak"$ B7A#! ;  )07 Ak"$ B7A#! : B7 B|B7 Ak"$ B7A#! 9  )7 Ak"$ B7A#! 8 Ak"$ B7A#! 7 Ak"$ B7A#! 6 B7 B|B7 Ak"$ B7A#! 5 B7 B|B7 Ak"$ B7A#! 3  7@  7  78 ))8!  7 ))!  7 ))!  7 B|)!  7 B|)!  7 B|)!  7x ))@!  7p ))`!  7h ))P!  7 Ak"$ B7A#! 1 B7 B|B7 Ak"$ B7A#! 0  )7 B| )@7 Ak"$ B7A#! / B7 B|B7 Ak"$ B7A#! .  )87 Ak"$ B7A#! - B7 B|B7 Ak"$ B7A#! ,  )7 Ak"$ B7A#! + B7 B|B7 Ak"$ B7A#! *  )7 Ak"$ B7A#! ) B7 B|B7 Ak"$ B7A#! (  )7 Ak"$ B7A#! ' Bٗ7 B|B7 Ak"$ B7A#! &  )7 Ak"$ B7A#! % B7 B|B7 Ak"$ B7A#! $  )7 Ak"$ B7A#! # B7 B|B7 Ak"$ B7A#! "  )x7 Ak"$ B7A#! ! Bߔ7 B|B 7 Ak"$ B7A#!  )p7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )h7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  B< B|)! B|)! B|)!  7 B| 7 B| 7 B| )7 B |B7 Ak"$ B7A#!  B7 B|B 7 Ak"$ Bā7A#!   7  7X )!  7 )P!  7 Ak"$ BƁ7A#!  B7 B|B 7 Ak"$ Bǁ7A#!   )X7 Ak"$ Bȁ7A#!  B7 B|B7 Ak"$ BɁ7A#!   )7 Ak"$ Bʁ7A#!  BՄ7 B|B 7 Ak"$ Bˁ7A#!   )7 Ak"$ B́7A#! B7 B|B7 Ak"$ B́7A#!  )7 Ak"$ B΁7A#! B7 B|B 7 Ak"$ Bρ7A#!  )7 Ak"$ BЁ7A#! Ak"$ Bс7A#!  Ak"$ Bҁ7A#!  )P@ Ak"$ BӁ7A#!  ))0"P@ Ak"$ BՁ7A#!  )! B| 7 B| B|B|A#! B|)! B|)! B|)! B|)! !  7 B| 7 B| 7 B| 7 B |B7 Ak"$ Bہ7A#!  B7 B|B$7 Ak"$ B݁7A#!  B7 B|B7 Ak"$ B߁7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 7  !"# #(M@ Ak"$ B7A#! % Ak"$ ) P@ Ak"$ B7A#! % ) )P@A5! $   ) B|7 Ak"$ B7A#! # B|5"B BPE@A! " #)0"P@ Ak"$ B7A#! " )! ) QE@A3! ! # RE@A3! BQE@A3!   ) )pPE@A-!  ) 1E@A*!  B! E@A1!  #)0"P@ Ak"$ B7A#!  ) ) QE@A!  ) )0"P@ Ak"$ B7A#!  )PP@A.!  BB.4S@A)!   ) )7 Ak"$ B7A#!   B|)"PPE@A"!  1(B Q@A(!  ) )" ) )}"B"BT@A'!  B  ) )8}B|XE@A%!  Aj"$ Aj"$A  ) 7 B| 7 Ak"$ B7A#!  Aj"$ Aj"$A Aj"$ Aj"$A Aj"$ Aj"$A Aj"$ Aj"$A  ) B|7 Ak"$ B7A #! B|1P!A! B!A! B7 B|B7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  Bݺ7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@ )  #(M@ Ak"$ B7A#!  Ak"$B!A!  ! PPE@A!  )! 3`PE@A!   7  70  7 B| 7 Ak"$ B7A#!  )B7( B)7 B| )7 B|B< Ak"$ B7A#!  )@! )0!A!    )X7 Ak"$ B7A#!  )(B|! BSE@A!   7(B. B|!  7X  7 Ak"$ B7A#!  )XB|!  7@ )X)!A!  B.7 Ak"$ B7A#! B!A!!  7 )!  78  7 B| 7 Ak"$ B7A#! B)7 B| ) 7 B|B< Ak"$ B7A#! )H! )P! )8! PP@A!  B|! BSE@A#!   7HB. B|!  7P )!A!   B.7 Ak"$ B7A#!  Aj"$ Aj"$A A "~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ h  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUV #(AjM@ Ak"$ BĒ7A#! X Ak"$ B|B7 B|B7 B|B7 )P@ Ak"$ BĒ7A#! X ))"P@A3! W ))!  7 P@ Ak"$ BĒ7A#! W ))!  7  )R@A1! V B!  7@ BBQ! ))8 ))(}"P!B  !  >4 P@A"! T B!B!  7  >< ))H"PPE@A!! R ))P" PPE@A! Q )! 4! B"  B B S!  >8  7x  7 B| 7 B|B< Ak"$ BĒ7A#! O B|)"PPE@A! N )! B| B|7 B| 7 B| 7 B| 4<> B| )7 B| 48> B| )x7 Aj"$ Aj"$A  7 B| 7 B|B< Ak"$ BĒ7A#! K B|)"PPE@A! J 4"BW@A! I  7p 44B B !  7h BS@A4! H  W@A4! G 4!B SE@A! F  B| 44 B|B B B~B B |! )! 4  7p 44B B !  7h BS@A! =  W@A! <  B| 44 B|B B B~B B |! )! 44! ! ! )@! )!A ! 9 B!B!A-! 8  7 B| 7 B|B> B|!  7@ B| 7 B | )7 Ak"$ BĒ7A#! 8 B(|4! )! )! )@!A! 6 B|B> B|B7 B|B> B|B7 Aj"$ Aj"$A  )7 B| )7 Ak"$ BĒ7A#! 5 B|)!  7` B|)!  7 )p4!  >< Ak"$ BĒ7A#! 4 B7 B|B7 Ak"$ BĒ7A#! 3  )h7 Ak"$ BĒ7A#! 2 B7 B|B7 Ak"$ BĒ7A#! 1  4<7 Ak"$ BĒ7A#! 0 B7 B|B7 Ak"$ BĒ7A#! /  )7 B| )`7 Ak"$ BĒ7A#! . B7 B|B 7 Ak"$ BĒ7A#! -  )@7 Ak"$ BĒ7A#! , B7 B|B7 Ak"$ BĒ7A#! + Ak"$ BĒ7A#! * B7 B|B7 Ak"$ BĒ7A#! )  )7 B| )7 Ak"$ BÀĒ7A#! ' B|)!  7` B|)!  7 ))@!  7X ))H!  7P Ak"$ BĀĒ7A#! & B7 B|B7 Ak"$ BŀĒ7A#! %  )7 B| )`7 Ak"$ BƀĒ7A#! $ B7 B|B7 Ak"$ BǀĒ7A#! #  )X7 Ak"$ BȀĒ7A#! " B7 B|B7 Ak"$ BɀĒ7A#! !  )P7 Ak"$ BʀĒ7A#! Ak"$ BˀĒ7A#!  Ak"$ B̀Ē7A#!  B7 B|B7 Ak"$ B̀Ē7A#!   )7 B| )7 Ak"$ BπĒ7A#!  B|)!  7` B|)!  7 )p4!  >< Ak"$ BЀĒ7A#!  B7 B|B7 Ak"$ BрĒ7A#!   )h7 Ak"$ BҀĒ7A#!  B7 B|B7 Ak"$ BӀĒ7A#!   4<7 Ak"$ BԀĒ7A#!  B7 B|B7 Ak"$ BՀĒ7A#!   )7 B| )`7 Ak"$ BրĒ7A#!  B7 B|B 7 Ak"$ B׀Ē7A#!   )@7 Ak"$ B؀Ē7A#!  B7 B|B7 Ak"$ BـĒ7A#!  Ak"$ BڀĒ7A#!  B7 B|B7 Ak"$ BۀĒ7A#!   )7 B| )7 Ak"$ B݀Ē7A#! B|)!  7` B|)!  7 ))8!  7X Ak"$ BހĒ7A#! B7 B|B7 Ak"$ B߀Ē7A#!  )7 B| )`7 Ak"$ BĒ7A#! B7 B|B7 Ak"$ BĒ7A#!  )X )H}7 Ak"$ BĒ7A#!  B7 B|B7 Ak"$ BĒ7A#!   )H7 Ak"$ BĒ7A#!  Ak"$ BĒ7A#!  Ak"$ BĒ7A#!  B7 B|B7 Ak"$ BĒ7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .  !"#$%&'( #(AjM@ Ak"$ BȒ7A#! * Ak"$B )S"E@A%! ) )! )! !B!B!B!B! A! ' B|! )" PE@A#! & B|" S@A! % P@A"! $ BQE@A ! # )PPE@A! " B! @A!  <?  )7 B| 7 Ak"$ BȒ7A#!  B0|)! B(|)! B |)! B|)! B|)! 1?"E@A!   7H  7h )! )!B! A!  B|! )! )!  S!  R@A!  XE@A&!  B|!  }!B }!  }!  B?|! S@A!  B| 7 B| 7 Aj"$ Aj"$A  7p  7`  7x  7P  7X  7@  7 B| 7 B| 7 Ak"$ BȒ7A#!  )X! )P! )p! )h! )H! )! )x! )`! )@! A!   TE@A(!   B|")! )! B| 7 B| 7 Aj"$ Aj"$A  TE@A*!   B|")! )! B| 7 B| 7 B|)!#)!#)!  XE@A!   T! P!A !  B!A!  B!A ! B|B7 B|B7 Aj"$ Aj"$A |" S@A,! B|! ! !A! )! )!B!B!B! A!  7 B| 7 Ak"$ BȒ7A#!  7 B| 7 Ak"$ BȒ7A#!   7 B| 7 Ak"$ BȒ7A#!  B7 B|B7 Ak"$ BȒ7A#!   A ~#!@@@@  #(M@ Ak"$ B̒7A#!  A0k"$  )87 B| B|7 B|B7 B|B7 Ak"$ B̒7A#!  ) ! )(! B| 7 B| 7 A0j"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ BВ7A#!  A0k"$  )87 B| B|7 B|B7 B|B7 Ak"$ BВ7A#!  ) ! )(! B| 7 B| 7 A0j"$ Aj"$A A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ BԒ7A#! A k"$ B|B7 B|B7 )8P@A ! )8BQ@A ! )(PPE@A! )8B WE@A! )(! B| 7 B| )87  7 B| )07 B| )87 Ak"$ BԒ7A#!  A j"$ Aj"$A  )87 B|B7 B|B< Ak"$ BԒ7A#!  B|)!A!  )0P@ Ak"$ BԒ7A#!  B|B' )01B|7 B|B7 A j"$ Aj"$A B|B7 B|B7 A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@   #(M@ Ak"$ Bؒ7A#!  A0k"$ )8PPE@A! )@B WE@A! )@B XE@A ! )@PPE@A ! B ! )@"! )8"! B| 7 B| 7 B| 7 B| 7 B| 7 A0j"$ Aj"$A  )@7 Ak"$ Bؒ7A#!  B(|)! B |)! B|)! B|)! B|)!A!  B7 B| )@7 Ak"$ Bؒ7A#!   )@7 B|B 7 Ak"$ Bؒ7A#!   A ~#!@@@@  #(M@ Ak"$ Bܒ7A#!  A k"$ B0|B7 B0|B7 B|B7 B|B7 B|B7  )(7 B|B7 B|B< Ak"$ Bܒ7A#!  )! B0| 7 B8| )(7 B| 7 B| )(7 B| )(7 A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  )P@A!  )1B-QE@A!  )B|! )B }B?B|!B! B!B!A!  B|! !  SE@A!   |1!B BP|BT@A !  B̙ T@A !  B ~!  |BP|" TE@A!  B|B7 B |B< Aj"$A B|B7 B |B< Aj"$A B|B7 B |B< Aj"$A E@A! E@A! B T@A!  B|B }  7 B |B< Aj"$A B|B7 B |B< Aj"$A B TE@A!  B|B7 B |B< Aj"$A )! )!B!A!  B|B7 B |B< Aj"$A A ~#!@@@@@@@@@@  Ak"$ )PPPE@A!  B )PB}! )P!B!A!  )0 ) |! )( ) |!B !  7(  7  70 B8|B7 B|B7 B8| 7 B| 7  B8|)7 B| 7 B|B< Ak"$ B7A-#!  B|)"BRE@A!  B|  )(|7 Aj"$ Aj"$A B|B7 Aj"$ Aj"$A A ~#!@@@@  A k"$B')!B')! B7 B| 7 B| > Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@  #)0"P@ Ak"$ B7A#!  5! 5!  > B ! B BB  !  > B|  |> Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2  !"#$%%&&'() #(M@ Ak"$ B7A#! + Ak"$ B7 Ak"$ B7A#! * )!  7@B'!A! ( )! PPE@A! ' 1@A! &  7H )! )! )! B|!  T@A! %  B|7  B|!B.5PP@A! $  7 4BPE@A! " )PP@A! ! )! )! )!  7 B|  }7 Ak"$ B7A#! B|)! )H B|4>B.5PP@A!  )H 7 )H)! )H)! )H)!  7 B|  }7 Ak"$ B7A#!  B|)! )H B|4>B.5PP@A!  )H 7 )@! )H!A!  )HB|  Ak"$ B7#!A!  )HB|  Ak"$ B7#!A!    Ak"$ B7#!A !  B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B7A#!  B(|)! B0|)! )@ B8|)7B.5PP@A!  )@ 7 ! ! )@! )H!A!  )@  Ak"$ B7#!A!  )! )!B SE@A%!  !B!A!!  B|! )"P@ Ak"$ B7A#!  1PP@A'!  B|" S@A ! B'7 B| 7 Ak"$ B7A#! Aj"$ Aj"$A B.5PP@A.!  7 )! )!  TE@A0!   B|!B.5PP@A,!  B'7A%!  B' Ak"$ B7#!A%!    Ak"$ B7#!A)!   7 B| 7 Ak"$ B7A#!   A " ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ l  !"#$%&'()*+,-./012223456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcde #(A8jM@ Ak"$ B7A#! g Ak"$ )P@ Ak"$ B7A#! g ))"P@ Ak"$ B7A#! g 5!  >$ BR@A! f 1PP@A! e 1PP@A! d 1BR@A! c 1BR@A! b ))! ))! B|!B!A ! ` !  SE@A1! _ B!  |")! B|" TE@A! ^ B!  |" ) TE@A ! ] ))p! ))h! )" TE@A! \  |! )" TE@A! [  70  7`  7X  7P  |! S@A/! Z B!B!  78  7h  7 B| )7 Ak"$ B7A#! X B|)! B|)! ))! ))! )0 TE@A! W  )X|)! )` TE@A! V  7`  7H  7  )P|)!  7@ Ak"$ B7A#! U B7 B|B57 Ak"$ B7A#! T  )H7 Ak"$ B7A#! S Ak"$ B7A#! R  )7 B| )`7 Ak"$ B7A#! Q B7 B|B7 Ak"$ B7A#! P  )@7 Ak"$ B7A#! O Ak"$ B7A#! N  )h7 B| )87 Ak"$ B7A#! M Ak"$ B7A#! L Ak"$ B7A#! K )0! )!B!A*! I  7X  7  )`|)!  7H Ak"$ B7A#! I B7 B|B7 Ak"$ B7A#! H  )H7 Ak"$ B7A#! G Ak"$ B7A#! F  )7 B| )X7 Ak"$ B7A#! E Ak"$ B7A#! D Ak"$ B7A#! C )(B|! )0! )!  WE@A! A )p! )h! )! )!  TE@A! @ B!  |)" TE@A! ?  7(  7`   |7 B| 7 Ak"$ B7A#! > B|)! B|)! ))! ))! )( T@A"! = A! <  7  7 B| )7 Ak"$ B7A#! < B|)! B|)! )!A! : ))! PPE@A! :  )R@A! 9 ))  B|)R@A! 8 ))! ))!B SE@A=! 7  7`B!A7! 5 )xB(|! ! )`! B| )7 B| B|A#! B|)"P@ Ak"$ B7A#! 5 B|)! B|)! )! )!  R@A>! 4  7X  7x  7 B| 7 B| 7 Ak"$ B7A*#! 3 B|1E@A>! 2 )XB|" )`S@A6! 1 Aj"$ Aj"$A ))!  7 ))!  7` B|)!  7p B|)!  7X Ak"$ B7A#! / B7 B|B7 Ak"$ B7A#! .  )7 B| )`7 Ak"$ B7A#! - B7 B|B7 Ak"$ B€7A#! ,  )p7 B| )X7 Ak"$ BÀ7A#! + Ak"$ BĀ7A#! * Ak"$ Bŀ7A#! ) B7 B|B 7 Ak"$ Bƀ7A#! ( B7 B|B7 Ak"$ BȀ7A#! & B7 B| 7 Ak"$ Bʀ7A#! $  )(7 B| 7 Ak"$ B̀7A#! "  7 B| 7 Ak"$ B΀7A#!  7 B| 7 Ak"$ BЀ7A#!  B7 B|B7 Ak"$ BҀ7A#!   )`7 B| 7 Ak"$ BԀ7A#!   )07 B| 7 Ak"$ Bր7A#!   7 B| 7 Ak"$ B؀7A#!   7 B| 7 Ak"$ Bڀ7A#!   7 B| 7 Ak"$ B܀7A#!  1!  <# 1!  <" 1!  <! 1!  < Ak"$ Bހ7A#!  B7 B|B'7 Ak"$ B߀7A#!   5$7 Ak"$ B7A#!  Ak"$ B7A#!  1#7 Ak"$ B7A#! Ak"$ B7A#!  1"7 Ak"$ B7A#! Ak"$ B7A#!  1!7 Ak"$ B7A#!  Ak"$ B7A#!   1 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1  !"#$%&'( #(M@ Ak"$ B7A#! * Ak"$ )!B'!A! ( )! PPE@A"! ' ) XE@A! &  )TE@A! % PPE@A!! $  )}! ) B B~|"P@ Ak"$ B7A#! $ BB! 5! BTE@A/! # B| |1 |! )"B! B! )!  XE@A! " B|! B" TE@A-!   B|)T@A!  A!  B|! B" TE@A+!    B|)TE@A!  BPP@A!  BP@A)!  B" TE@A%!   B|)"BQ@A!  )p! )h!  TE@A#!  B |  |7 B | 7 Aj"$ Aj"$A B |B7 B |B7 Aj"$ Aj"$A ! B|"B" TE@A'!   B|) X@A!  A!  B |B7 B |B7 Aj"$ Aj"$A B!A!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#! B7 B|B#7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ O  !"#$%&'()*+,-./012345678889:;<=>? #(A8jM@ Ak"$ B7A#! A Ak"$ 5B"P@A?! @ )PP"E@A>! ?  )BB! )! )!B!A! = B|! BSE@A ! <  B| B|!  5QE@A! ;  )QE@A! : 4 ! B| > B|B7 Aj"$ Aj"$A  )PPE@A;! 8 )P@ Ak"$ B7A#! 8 ))X! ))P! ))`! 5B" XE@A! 7  <S  7 ))! B| 7 B|B>  }!B }!  }!  B? |!A! 5 ! ! !  7X B|)! ))!  7 B| 7 B| 7 B| B|7 B | B|7 B(|  Q< Ak"$ B7A#! 4 B|)! B8|)! B0|)! B|1E@A ! 3 B|)! ) TE@A! 2 1S"E@A! 1 #)0"P@ Ak"$ B7A#! 1  5! 5!  > B " ! B! B  B!  > ) )BBB|"  |BBB|! 5! 4 !  )7  >  > B|4!  )7  5>  > B|4! B| > B| )X7 Aj"$ Aj"$A B.5PP@A:! . 1"E@A:! -  7  7p  7  )7 B| )7 Ak"$ B7A#! , B|)!  7 B|)!  7 B|)!  7 Ak"$ B7A#! + B7 B|B$7 Ak"$ B7A#! *  )7 B| )7 Ak"$ B7A#! ) B7 B|B7 Ak"$ B7A#! (  )7 Ak"$ B7A#! ' B7 B|B 7 Ak"$ B7A#! &  )7 Ak"$ B7A#! % B7 B|B7 Ak"$ B7A#! $  )7 B| )p7 B| )7 Ak"$ B7A#! # Ak"$ B7A#! " Ak"$ B7A#! ! ))X! ))P! ))`! ) XE@A! ))! B| 7 B|B>  )}!B }!  )}!  B? )|!A8!   7x  7h  7 B|)!  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   B|47 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )x! )h! )! B|)! ))!  7 B| 7 B| 7 B| B|7 B | B|7 B(|  Q< Ak"$ B7A#!  B|)! B8|)! B0|)! B|1@A0!  A!  B|B> B|B7 Aj"$ Aj"$A 1"E@A=!  B.5P@A!  B|B> B|B7 Aj"$ Aj"$A )! )!A !  B|B> B|B7 Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!   )7 B| 7 Ak"$ BÀ7A#!  7 B| 7 Ak"$ Bŀ7A#! )P@ Ak"$ Bǀ7A#! ))!  7 Ak"$ Bɀ7A#!  B7 B|B7 Ak"$ Bʀ7A#!   )7 Ak"$ Bˀ7A#!  Ak"$ B̀7A#!  Ak"$ B̀7A#!  B7 B|B7 Ak"$ B΀7A#!   A ~#!@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A(k"$ )0PPE@A! )04"BPE@A ! B!  7  7 Ak"$ B7A#!  B|)! B|B7 B |B7 B| )7 B | 7 B|)! B| 7 B| 7 A(j"$ Aj"$A )8P@ Ak"$ B7A#!  )8)! )8)!  TE@A!   |!A!   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 Ak"$ B7A#!  )"B|! B|)!A!  B|! B SE@A!  |1B/Q@A! A! B|!  SE@A !  TE@A!  |1B.QE@A!   XE@A !  B8| 7 B8| 7 A j"$ Aj"$A  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! A(k"$ )0PPE@A! )8P@ Ak"$ B7A#! )8)! )8)! 4@B B " TE@A!   |!  7  7 Ak"$ B7A#!  B|)! B|B7 B |B7 B| )7 B | 7 B|)! B| 7 B| 7 A(j"$ Aj"$A B!A !   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  A(k"$ )0PPE@A! )8P@ Ak"$ B7A#! )8)(! )8) ! )05$ 4@|B" TE@A!  B|5"BRE@A ! )8)@! )8)8!  TE@A!   |!  7  7 Ak"$ B7A#! B|)! B|B7 B |B7 B| )7 B | 7 B|)! B| 7 B| 7 A(j"$ Aj"$A B|B7 B|B7 A(j"$ Aj"$A B|B7 B|B7 A(j"$ Aj"$A  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ )PPPE@A ! )P5!  )P7 B| )X7 B| > B| )`7 B |B7 B(| 1h< Ak"$ B7A#! B0|4!  >@ )P5!  )P7 B| )X7 B| > B| )`7 B |B7 B(| 1h< Ak"$ B7A#! B0|4! 4@BBQE@A!  B|B7 B|B7 B|B> Aj"$ Aj"$A BBQ@A!  )XP@ Ak"$ B7A#!  )X)@ 4@W@A!   >D  )P7 B| )X7 B| 4@> Ak"$ B7A#!  B|)! B |)! B| 7 B| 7 B| 4D> Aj"$ Aj"$A B|B7 B|B7 B|B> Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B7A#!  A8k"$  )@7 B| )H7 B| )P7 B|B< Ak"$ B7A#!  ) ! 40! )(! B| 7 B| 7 B| > A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )hP@ Ak"$ B7A#!  )h5!  )h7 B| )p7 B| > B| )x7 B | )7 B(|B< Ak"$ B7A#!  40"BBPP@A!  B| > Aj"$ Aj"$A  >@  )h7 B| )p7 Ak"$ B7A#!  B|)!  7P B|)!  7X )h)!  7H )h5!  >D Ak"$ B7A#!  B؛7 B|B7 Ak"$ B7A#!   )X7 B| )P7 Ak"$ B7A#! Ak"$ B7A#!  )H7 Ak"$ B7A#! Ak"$ B7A#!  )x7 Ak"$ B7A#! Ak"$ B7A#!   5D7 Ak"$ B7A#!  Ak"$ B7A#!   4@7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  4@!A!  A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$ )pP@ Ak"$ B7A#! )hP@ Ak"$ B7A#! )p)X! )p)P! )p)`! )h5" XE@A ! )h)! B| 7 B|B>  }!B }!  }!   B?|!B!A!  4TB B ! B|4" 4T  S! ! ! !  >T B|)! )h)!  7 B| 7 B| 7 B| B|7 B | B|7 B(|  Q< Ak"$ B7A#!  B8|)! B|)! B0|)! B|1@A!  B| 4T> Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )HP@ Ak"$ B7A#!  )H5 5XBXE@A!  B|B> Aj"$ Aj"$A )HB+| 5XBB|B|"P@ Ak"$ B7A#!   5!  )H7 B| )P7 B| > B| )`7 B | )h7 B(|B< Ak"$ B7A#!  B0|4! B| > Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!  ) 1+ 10BX@A! ) B+|! ) 5 B |B|"BP! ! PE@A! ) BPP@A!  B|!  10BB|)! B8| 7 Aj"$ Aj"$A  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   ) 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )!A !  B8|B7 Aj"$ Aj"$A A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ /  #(M@ Ak"$ B7A#! " Ak"$ ) PPE@A,! ! )1"BPE@A! 1@"E@A!  BBPPE@A!   ) ! )!B!B!B!A !  B|! B" TE@A)!   |1! B|! B"B! BB!  B BT ! BBPE@A!  )8P@ Ak"$ B7A#!  )84! )8 B B} BB|> B" XE@A'!   }! )( }!B }!  B? |! PPE@A$!  1"BBPPE@A!  B!B!B!A!  B|! B" TE@A!!   |1! B|! BB! BB! B BT ! BBPE@A!  B" XE@A!  )0P@ Ak"$ B7A#!  )0 )0) B|7  }! B| B }B? |7 B|  }7 B| 7 B|B< Aj"$ Aj"$A !B!A!  )! !B! ) !A !  B|B7 B|B7 B|B7 B|B< Aj"$ Aj"$A  7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#! B7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!  B7 B| ) 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #  #(M@ Ak"$ B7A#!  Ak"$B.5P@A!  )XP@ Ak"$ B7A#!  )X)PP@A!!   )X )P7 )PP@ Ak"$ B7A#!  )P)M! )P)M! )P)M! B|!  T@A!  ! )P B|7M  B|!B.5PP@A!   )X7 )P)M! )P)M! )P)M!  7 B| 7 B| 7 B| 7 Ak"$ B7A#!  )P)M! )P)M! PPE@A!  )X )Q@A!   )PB|7 B|B> Ak"$ B7A#!  Aj"$ Aj"$A )PB,|! )X)!  7 B| 7 Ak"$ B7A##!  A!  )X Ak"$ B7#!A!  7@ B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B7A#! B(|)! B0|)! )P B8|)7MB.5PP@A! )P 7M )@! ! ! !A !  )PB|  Ak"$ B7#!A!  Ak"$ B7A#!  A!  B7 B| 7 Ak"$ B7A#!  B7 B|B"7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ k  !!!!!!"#$$$$$$%%%&''()*+,-./ #(M@ Ak"$ B7A#! 1 A8k"$ )@P@ Ak"$ B7A#! 0 )@B|!  70  7 Ak"$ B7A#! / B|5"BXE@A8! . BX@A ! - BQE@A ! , Ak"$ B7A#! + A! ) B|B< A8j"$ Aj"$A P@A7! ( #)0"P@ Ak"$ B7A#! (   4B|>#P@ Ak"$ B7A#! ( #)0!  7  )07 B| > B |B> Ak"$ B7A$#! ' B|1@A ! & ) P@ Ak"$ B7A#! &  ) 4! ) B|> BBQE@A! % #P@ Ak"$ B7A#! % #1E@A! $ #Bu7A! " )@)! !  7(  )07 B|B> B |B> Ak"$ B7A$#! " B|1E@A5! ! ) P@ Ak"$ B7A#! !  ) 4! ) B|> BBQE@A0! #P@ Ak"$ B7A#! #1E@A0!  #Bu7 )(P@ Ak"$ B7A#!   )(B|7 B|B> Ak"$ B7A#!  B|B< A8j"$ Aj"$A Ak"$ B7A#!  A&!  B|B< A8j"$ Aj"$A BXE@A!  BQE@A#P@ Ak"$ B€7A#!  #)0!  7  )07 B| > B |B> Ak"$ Bŀ7A$#!  B|1@A!  )P@ Ak"$ BȀ7A#!   )4! ) B|> BBQE@A!  #P@ Ak"$ B̀7A#!  #1E@A!  #Bu7A!  )@)!  7( P@ Ak"$ BӀ7A#!    B|7 B|B> Ak"$ Bր7A#!   )07 B|B> B |B> Ak"$ B׀7A$#!  B|1E@A! )P@ Ak"$ Bڀ7A#!  )4! ) B|> BBQE@A! #P@ Ak"$ B߀7A#! #1E@A! #Bu7  )(B|7 B|B> Ak"$ B䀰7A#! B|B< A8j"$ Aj"$A Ak"$ B怰7A#!  A!  BQ@A !  B Q@A!  Ak"$ Bꀰ7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ *  !" #(M@ Ak"$ B7A#! $ A k"$ )(P@ Ak"$ B7A#! $ )()M! )()M! )0 TE@A(! # )0B!  |)"P@ Ak"$ B7A#! # )!  )(R@A&! " B7 )()M! )()M! B|" )0R"E@A ! !  TE@A$!  B|)! )0 TE@A"!   |!B.5PP@A!   7 )()M! )()M!  TE@A !   B|!B.5PP@A!  B7 )()M!  XE@A!  )( 7M @A!  )0P@A!   )(B|7 B|B> Ak"$ B7A#!  A j"$ Aj"$A  )(7 Ak"$ B7A#!  A!  )()M! )()M!  7 B| 7 B| 7 B| )07 Ak"$ B7A#!  )()M! )()M! )()M!  7 B| 7 B| 7 B| )07 Ak"$ B7A#!  A!  B Ak"$ B7#!A!    Ak"$ B7#!A !  7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!  )07 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )07 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!  )()M! )()M! PPE@A!  )"P@ Ak"$ B7A#!  )!  )(R@A!  B7 )()M! )()MB|!B S"E@A !  B.5P!  B|)! P@A!   7 )()M! )()M!  TE@A!   B|!B.5PP@A!  B7 )()M!  XE@A!  )( 7M @A!   )(7 Ak"$ B7A#!   )(B|7 B|B> Ak"$ B7A#!  A j"$ Aj"$A )()M! )()M!  7 B| 7 B| 7 B|B7 Ak"$ B7A#! A! B Ak"$ B7#!A!   Ak"$ B7#!A !  7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!"###$$$%%%%%%%%%%%&&''''(()******+++,--./0112234444444445666666777899:::::::::;<<<<<<===>??@ABCDEFGHIJKLM #(M@ Ak"$ B7A#! O Ak"$ )pBW@A! N )xBS@A! M )hP@ Ak"$ B7A#! M )hB|!  7X  7 Ak"$ B7A#! L B|5!  > BXE@A! K BX@A! J BQE@A ! I Ak"$ B7A#! H A! F #)0"P@ Ak"$ B7A#! G   4B|>#P@ Ak"$ B7A#! G #)0!  70  )X7 B| > B |B> Ak"$ B7A$#! F B|1@A! E )0P@ Ak"$ B7A#! E  )04! )0 B|> BBQE@A! D #P@ Ak"$ B7A#! D #1E@A! C #Bu7A! A )h)"P@ Ak"$ B7A#! B   B|7 B|B> Ak"$ B7A#! A B! )0!B! )h )x7B.5PP@A! ? )h )7 )h )7 B.5PP@A! = )h )7(  78  < )h )70 @A! ; )h )p78 )p )h)S! )h)! 5BQ!BB !  > B!BB ! ! BQ!  < @A! : BPP@A! 9  )X7 B|B> B | > Ak"$ B7A$#! 8 B|1E@A! 7 )8P@ Ak"$ B7A#! 7  )84! )8 B|> BBQE@A! / Ak"$ B€7A#! / A2! - P@ Ak"$ BĀ7A#! .  B|7 B| > Ak"$ Bƀ7A#! - 5 !A0! +  7P  >$  7 B| )p7 Ak"$ Bʀ7A#! + 4$B|! 5 ! )8! 1! )P! 1!A/! ) )h )p7#)0"P@ Ak"$ B΀7A#! * )!  7P P@ Ak"$ BҀ7A#! *  B|!  7H  7 Ak"$ B؀7A#! )  )P7 B| )h7 Ak"$ Bڀ7A#! (   )H7 Ak"$ Bހ7A#! '  )X7 B|B> B |B> Ak"$ B7A$#! & B|1E@A! % )8P@ Ak"$ B〼7A#! %  )84! )8 B|> BBQE@A! $ #P@ Ak"$ B耼7A#! $ #1E@A! # #Bu7  )p7 Ak"$ B퀼7A#! ! A>!  Ak"$ B7A#!  A!  )hB(| ) Ak"$ B񀼓7#!A*!  )hB| ) Ak"$ B󀼓7#!A(!  PE@A!  #)0"P@ Ak"$ B7A#!    4B|>#P@ Ak"$ B7A#!  #)0!  7(  )X7 B| > B |B> Ak"$ B7A$#!  B|1@A!  )(P@ Ak"$ B7A#!   )(4! )( B|> BBQE@A!  #P@ Ak"$ B7A#!  #1E@A!  #Bu7A!  B! )(!B!A&!  #)0"P@ Ak"$ B7A#!    4B|>#P@ Ak"$ B7A#!  #)0!  7@  )X7 B| > B |B> Ak"$ B7A$#!  B|1@A!  )@P@ Ak"$ B7A#!   )@4! )@ B|> BBQE@A!  #P@ Ak"$ B7A#!  #1E@A!  #Bu7A!  B! )@!B!A&!  BXE@A!  BQ@A ! A! BQ@A! BX@A! B Q@A ! Ak"$ B7A#! A!  Ak"$ B7A#!  A!  B7 B|B!7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ /  !"#$%&'()* #(M@ Ak"$ B7A#! , A8k"$B )PSE@A! + )H!B!A! ) ) ! P@ Ak"$ B7A#! ) B|!  7(  7 Ak"$ B7A#! ( B|5"BXE@A! ' BXE@A! & PE@A! % Ak"$ B7A#! $ A! "  )(7 B |B > Ak"$ B7A$#! " B|1E@A! ! ) B7  )@7 B| ) 7 Ak"$ B7A#!  )(7 B|B > B |B> Ak"$ B7A$#!  B|1E@A!  )B|" )PSE@A!  )0B|! ! !  7  70 )!  7 A!  A8j"$ Aj"$A Ak"$ B7A#!  A!  BQE@A!  Ak"$ B7A#!  A!   )(7 B |B> Ak"$ B7A$#!  B|1E@A!  ) B7A!  BXE@A!!  BQ@A!  A !  BQ@A-!  BX@A&!  B Q@A! Ak"$ B7A#! A!  )(7 B |B > Ak"$ B7A$#! B|1E@A! ) ) )87 ) B7  )@7 B| ) 7 Ak"$ B7A#!   )(7 B|B > B |B> Ak"$ B7A$#!  B|1@A!  Ak"$ B7A#!  A!  Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ P  !"#$%&'()*+,-./012345677889::;<=>?@ABCDE #(A jM@ Ak"$ Bē7A#! G Ak"$ )P@ Ak"$ Bē7A#! G )B|!  7  7 Ak"$ Bē7A#! F 5P@A! E )B,|!  7  7 Ak"$ Bē7A#! D B|)"PPE@A ! C ) S@A! B  )7 B|B7 Ak"$ Bē7A##! A )!B!B!B!B!A! > B|! )! ! ! ! )M! )M!  SE@A! =  TE@A! <  B|)"P@ Ak"$ Bē7A#! < )!  R@A! ;  7X  7p  7H  7P  7x B|!  7  7 Ak"$ Bē7A#! : B|5"BXE@A'! 9 BXE@A! 8 P@A! 7 )X! )P! )H! )x!A ! 5 Ak"$ Bē7A#! 5 )X! )P! )H! )x!A ! 3 BQ@A! 3  )7 B |B> Ak"$ Bē7A$#! 2 B|1@A ! 1 )X! )P! )H! )x!A ! .  )7 B| )X7 Ak"$ Bē7A#! .  )7 B|B> B |B> Ak"$ Bē7A$#! - B|1E@A%! ,  )B|7 B|B> Ak"$ Bē7A#! + )XB|!A! ) Ak"$ Bē7A#! ) A#! ' BXE@A.! ' BX@A! & BQ@A,! % Ak"$ Bē7A#! $ )X! )P! )H! )x!A ! " Ak"$ Bē7A#! " )XB|! )P! )H! )x!A ! BX@A0! B Q@A!  A*!   >D  )7 B |B > Ak"$ Bē7A$#!  B|1@A3!  )X! )P! )H! )x!A !  )p )p)87  )7 B| )X7 Ak"$ Bē7A#!  )HB|! )P T@A!  )x! )P!  )HB|!B.5PP@A!   )p7 5DBQ@A:!  )XB|! ! ! ! !A !   7  7h  7`  )7 B|B> Ak"$ Bē7A#!  B|5"B B BW@A=!  )h! )! )`!A9!  )h! )`! )! B S@A!  Aj"$ Aj"$A  )7 B| 7 B| 7 B| 7 Ak"$ BÀē7A#!  A?!  )p Ak"$ Bŀē7#!A8! B7 B| )x7 B| )H7 B| )P7 B | 7 Ak"$ Bǀē7A#! B0|)! B8|)! B(|)! B|!A6! ! ! !A>!  Aj"$ Aj"$A  )B,|7 B|B7 Ak"$ Bˀē7A##!  Aj"$ Aj"$A B7 B|B7 Ak"$ B̀ē7A#!   7 B| 7 Ak"$ Bπē7A#!   A ~#!@@@@@@@@@@@@@   #(M@ Ak"$ Bȓ7A#! A0k"$B )HSE@A ! )@!B!A! )(B|! ! !  7  7( )!  7  )87 B| 7 Ak"$ Bȓ7A#!  ) P@ Ak"$ Bȓ7A#!   ) B|7 B|B > B |B> Ak"$ Bȓ7A$#!  B|1E@A !  )B|" )HS@A!  A0j"$ Aj"$A Ak"$ Bȓ7A#!  A !  A ~#!@@@@@@@@@@   #(M@ Ak"$ B̓7A#! Ak"$ ) P@ Ak"$ B̓7A#!  ) B,|7 Ak"$ B̓7A#!  )!  7  ) B,|7 Ak"$ B̓7A#!  )! )PE@A!  B(| 7 Aj"$ Aj"$A PPE@A !   )S@A!  )!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ =  !"#$%&'()*+,-./012345 #(M@ Ak"$ BГ7A#! 7 A0k"$ )8!A! 5 )8! P@ Ak"$ BГ7A#! 5 )M! )M! PPE@A;! 4 )"P@ Ak"$ BГ7A#! 4 )!  R@A9! 3  7 B|!  7(  7 Ak"$ BГ7A#! 2 B|5"BXE@A%! 1 BXE@A! 0 PE@A! / Ak"$ BГ7A#! . A! , ) )! )@ S@A! ,  )(7 B |B> Ak"$ BГ7A$#! + B|1E@A! *  )87 B| ) 7 B| )@7 Ak"$ BГ7A#! ) B|B7 A0j"$ Aj"$A B| 7 A0j"$ Aj"$A BQE@A! & Ak"$ BГ7A#! % A! #  )(7 B |B> Ak"$ BГ7A$#! # B|1E@A! "  )87 Ak"$ BГ7A#! !  )(7 B|B> B |B> Ak"$ BГ7A$#! B|1E@A#!   )8B|7 B|B> Ak"$ BГ7A#!  )8)MPE@A!  B|B7 A0j"$ Aj"$A Ak"$ BГ7A#!  A !  BXE@A'!  BQ@A!  A!  BQ@A7!  BX@A,!  B Q@A!  Ak"$ BГ7A#!  A!   >  )(7 B |B > Ak"$ BГ7A$#!  B|1E@A!  ) ) )87  )87 Ak"$ BГ7A#!   )87 B| ) 7 Ak"$ BГ7A#!  5BQ@A5!   )(7 B|B > B |B> Ak"$ BГ7A$#! B|1@A! Ak"$ BГ7A#! A!  )8B|7 B|B> Ak"$ BГ7A#! A1!  Ak"$ BГ7A#!  A!  B7 B|B7 Ak"$ BГ7A#!  B7 B| 7 Ak"$ BГ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@ %  #(M@ Ak"$ Bԓ7A#!  Ak"$ )XP@ Ak"$ Bԓ7A#!  )X)!  78 )X) !  7( )X)(!  7@ )X)0!  7 )X)!B SE@A !  )X)!  )` } #!B|~ |! )X 7 BSE@A!  )XB7 )PP@ Ak"$ Bԓ7A#!  )P)M! )P)M! )P)M!  7 B| 7 B| 7 B|B7 Ak"$ Bԓ7A#!   )XB|7 B|B> B |B> Ak"$ Bԓ7A$#!  B|1E@A!   )P7 Ak"$ Bԓ7A#!  )PP@ Ak"$ Bԓ7A#!  )PB|!  70  7 Ak"$ Bԓ7A#! )8P@ Ak"$ Bԓ7A#! )8)!  )(7 B| )@7 B| ) 7 )8$  Ak"$ Bԓ7Av!A #!   )07 Ak"$ Bԓ7A#! Aj"$ Aj"$A Ak"$ Bԓ7A#!  A !   )P7 Ak"$ Bԓ7A#!   )XB|7 B|B> B |B> Ak"$ Bԓ7A$#!  B|1@A !  Ak"$ Bԓ7A#!  A !  A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Y  !"#$%&&'()*+,-./0123456789:;<=>?@ABCCDEFGHIJKLMN #(M@ Ak"$ Bؓ7A#! P Ak"$ )P@ Ak"$ Bؓ7A#! P )B,|!  7 B|B7 Ak"$ Bؓ7A##! O ))M! ))M!  7X ))M!  7HB S@A! N B!B!B! !A! K B|!  SE@A! J  TE@A! I  B|! B.5PP@A ! H B7A! F B Ak"$ Bؓ7#!A! E  7P  >,  )B|7B }!  >4 B| > Ak"$ Bؓ7A#! E  )B|7 B| 44> Ak"$ Bؓ7A#! D  )B|7 B|B 4,}> Ak"$ Bؓ7A#! C )P )HXE@A! B ) )P7M ) )H7MB.5PP@A! A ) )X7M  )7 Ak"$ Bؓ7A#! ? Aj"$ Aj"$A )B| )X Ak"$ Bؓ7#!A! <  7@ !B!B!B!B! B! A(! ; )`! P@ Ak"$ Bؓ7A#! ; B|!  7h  7 Ak"$ Bؓ7A#! : B|5"BXE@A;! 9 BXE@A1! 8 PE@A$! 7 Ak"$ Bؓ7A#! 6 A! 4 1'"@A*! 4 )8B|! 40! 4,! )PB|" )@SE@A)! 2 )pB|! ! ! ! ! )@! )H! )X! !  7P  7p  >,  >0  78  <' )!  7`A! / )@! ! ! ! )X! )H!A! . )8 )@TE@A! . )X )8B|!B.5PP@A/! -  )`7  )X7 B| )@7 B| )H7 B| )87 Ak"$ Bؓ7A#! + 1'!A%! )  )` Ak"$ Bؓ7#!A-! ( BQE@A4! ( Ak"$ Bؓ7A#! ' A! %  )h7 B |B> Ak"$ Bؓ7A$#! % B|1E@A! $ )`B7  )h7 B|B> B |B> Ak"$ Bؓ7A$#! # B|1E@A9! " 40B|!B! )8! 4,!A&! Ak"$ Bؓ7A#! A8!  BXE@A=!  BQ@A2!  A"!  BQ@A!  BX@A!  B Q@A2!  Ak"$ Bؓ7A#!  A!   >(  )h7 B |B > Ak"$ BÀؓ7A$#!  B|1E@A!  )` )`)87 )8 )@TE@A!  )X )8B|!B.5PP@A!   )`7  )X7 B| )@7 B| )H7 B| )87 Ak"$ BȀؓ7A#!   )h7 B|B > B |B> Ak"$ Bɀؓ7A$#!  B|1E@A!  )8B|! 4,B| 4, 5(BQ!B! 40!A&!  Ak"$ B̀ؓ7A#!  A!  )` Ak"$ B΀ؓ7#!A! Ak"$ BЀؓ7A#! A!  )P7 B| )H7 Ak"$ BҀؓ7A#!  7 B| 7 Ak"$ BԀؓ7A#!   )87 B| )@7 Ak"$ Bրؓ7A#!   )87 B| )@7 Ak"$ B؀ؓ7A#!   A ~#!@@@@@@@@@   #(M@ Ak"$ Bܓ7A#!  Ak"$ )P@ Ak"$ Bܓ7A#!  ))M! ))MPE@A!   )B,|7 B|B7 Ak"$ Bܓ7A##!  Aj"$ Aj"$A )"P@ Ak"$ Bܓ7A#!  )B,|! )!  7 B| 7 Ak"$ Bܓ7A##!  A!  A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! A(k"$ )0P@ Ak"$ B7A#! )0B,|!  7  7 Ak"$ B7A#!  B|)"PPE@A!   )8S@A !   ) 7 B| )87 Ak"$ B7A!#!  B|1E@A!  A(j"$ Aj"$A A(j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#!  B')!B')!B SE@A!   7(B!B!B!A !  B|! )"PP@A!  B|" S@A!   70  7 B.7 Ak"$ B7A#!  B| )7 B| )07 Aj"$ Aj"$A  7  7H  78  70  7  B,|7 Ak"$ B7A#! B|)"PPE@A!  )SE@A! )8!  7@  7  )8B,|7 Ak"$ B7A#! B|)"PPE@A!   )SE@A!  )8! ) ! ! ! )(! )H!A !  )! )@!A!  )! )0!A!  B!B!A !  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ '  #(M@ Ak"$ B7A#! A k"$ )0 )@W@A!  )@ )0TE@A%!  )( )@B|")"P@ Ak"$ B7A#!  )"BW@A!  )! )(! )0! )@!A!  ! B SE@A!  B|B" TE@A#!   B|)"P@ Ak"$ B7A#!  ) W@A!   TE@A!!   B|!B.5PP@A!   7A!    Ak"$ B7#!A!   TE@A!   B|!  )RE@A!  B.5PP@A!   7 A j"$ Aj"$A   Ak"$ B7#!A!  7  7 Ak"$ B7A#! )! )!A! Ak"$ B7A#! A!  7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   )@7 B| )07 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ C  !"#$%&'()*+,-./01234 #(M@ Ak"$ B7A#! 6 A k"$ )0 )@W@A1! 5 )@ )0TE@A! 4 )( )@B|")"P@ Ak"$ B7A#! 4 )"BW@A/! 3 )! )(! )0! )@!A! 1 ! B"B|!  W@A"! 0  TE@A?! /  B|)" P@ Ak"$ B7A#! / )! B|" SE@A.! . TE@A=! -  B|)" P@ Ak"$ B7A#! - )" SE@A.! , B|" SE@A-! +  TE@A;! *  B|)" P@ Ak"$ B7A#! * )! B|" SE@A,! )  TE@A9! (  B|)" P@ Ak"$ B7A#! ( )" SE@A,! ' SE@A+! &  W@A"! %  TE@A7! $  B|)!  TE@A5! #  B|!B.5PP@A ! "  7A!   Ak"$ B7#!A!   TE@A3!   B|!  )RE@A&!  B.5PP@A(!   7 A j"$ Aj"$A   Ak"$ B7#!A&!  ! !A!  ! !A!  ! !A!  ! ! A!   7  7 Ak"$ B7A#!  )! )!A!  Ak"$ B7A#!  A!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!  7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   )@7 B| )07 Ak"$ B€7A#!   A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 B|B7 Ak"$ B7A#!   A ~#!@@@@  A k"$  )(7 B| )07 B| 48> Ak"$ B7A#!  4! B| > A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$B*)PE@A! B|B7 Aj"$ Aj"$A B*)PE@A! B*1E@A!  B*7 Ak"$ B7A#! B*)"PE@A!   B*7 Ak"$ B7A#!  B|B7 Aj"$ Aj"$A B*)PE@A!  B*1E@A !   !  7B*B7 B*7 Ak"$ B7A#!  B| )7 Aj"$ Aj"$A A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ )P@ Ak"$ B7A#! )),! )B7, P@A!  7 B*7 Ak"$ B7A#!  )"P@ Ak"$ B7A#!   B7B*)PE@A!  B* )7 B* )7 B*7 Ak"$ B7A#!  Aj"$ Aj"$A B*)"P@ Ak"$ B7A#!    )7A!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ Ak"$ B7A#! )! 4! )!B*1E@A ! B )`SE@A ! P@ Ak"$ B7A#! )`B| )`# )Q!  >L B7 B| 7 B| > B| 7 B | 1X< B(| 7 B0| )h7 B8| )p7 B| )x7 Ak"$ B7A#!   4L> Ak"$ B7A#!  Aj"$ Aj"$A )`!A!  P@ Ak"$ B7A#!  1@A!   > Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ y  !""#$%%&&'''((()*+,-...//012234566789::;<=>>?@ABBCDEFFGH #(M@ Ak"$ B7A#! J Ak"$ )xP@ Ak"$ B7A#! J )x)! )`B4|!  78 "P"PE@A! I B )} SE@A! H  7 B| 4p> Ak"$ B7A#! G B|)! ! )x 7  7@ Ak"$ B7A#! E )@P@ Ak"$ B7A#! E )B! )@)! )@ 7  }!B )W! )B|" ) "B!B T! )@)!B  "B 1! BTE@A! D )@B|" | < )@)B|! )@ 7 BBQE@A! C B!A! A  | B< B! B|! B XE@A!! @ BT@A! ? A! > BTE@A! >  |" < )@ B|7  )@)!A(! ;  | B< B! B|! B XE@A*! : BT@A'! 9 A! 8 BTE@A! 8  | < )@ B|7B )SE@A9! 7 )!B!A6! 5  | B< B! B|! B XE@A2! 4 BT@A.! 3 A! 2 BTE@A! 2  | < )@ B|7 B|" )SE@A9! 1 B|! )! )@)!A0! . )PE@A! .  )@)!B!A?! ,  | B< B! B|! B XE@A! + BT@A>! * A! ) BTE@A! )  | < )@ B|7 )@) }! )8 S@A! ' PPE@A! &  B~|< Aj"$ Aj"$A  70  7HB )SE@A! #  7P  )h7 B| )@B|7 B|B7 B|B7 B | )7 Ak"$ B΀7A#! "  B(|)! )@)! )P!A!  | B< B! B|! B XE@A!  BT@A!  A!  BTE@A!   | < )@ B|7 )0! )H!A!  B!A$!  B7 B|B7 Ak"$ B܀7A#!   7 B|B7 Ak"$ B߀7A#!   7 B|B7 Ak"$ Bင7A#!   7 B|B7 Ak"$ B䀄7A#!   7 B|B7 Ak"$ B怄7A#!   7 B|B7 Ak"$ B逄7A#!   7 B|B7 Ak"$ B뀄7A#!  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B󀄔7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A8k"$ )@P@ Ak"$ B7A#!  )@)!  70 #Q@A!  PP@A!  B! B| B S!B SE@A ! P@ Ak"$ B7A#! )BQE@A ! B|!  )XXE@A! B*7 B| )H7 B| 7 B| )X7 Ak"$ B7A#! B |5! B| 7 A8j"$ Aj"$A  7 B| )`7 B| )H7 B| )P7 B | )X7 Ak"$ B7A#!  B(|)! )0!A!   )`B|7 B| )H7 B| )P7 B| )X7 Ak"$ B7A#!  B |)! )0!A!   7 B| )X7 Ak"$ B7A#!   A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!    4B|>#)0"P@ Ak"$ B7A#!  #P@ Ak"$ B7A#!  )! "PPE@A!  4! B| 7 B | > B(| B,|7 Aj"$ Aj"$A  7 B.7 Ak"$ B7A#!  B| )7 B |B> B(|B.7 Aj"$ Aj"$A A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ 4BBQ@A!  #)0"P@ Ak"$ B7A#!   4!  B|> BBQE@A !  #P@ Ak"$ B7A#!  #1E@A !  #Bu7 Aj"$ Aj"$A  B.7 Ak"$ B7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ c  !"#$$%&'(()**+, #(M@ Ak"$ B7A#! . A0k"$B*)"PPE@A! - #)0"P@ Ak"$ B7A#! -  )R!  < @A! + )8PPE@A! * )8"P@ Ak"$ B7A#! *  B7B*)PE@A! ) B* )87 B* )87 B*)"PPE@A?! & "P@ Ak"$ B7A#! & B* )7  7 !  7( P@ Ak"$ B7A#! %  B7 B7 Ak"$ B7A#! $ )B! )( 7 )()"BTE@A! # )(B|" |B< )()B|! )( 7 4@B B !A,! !  | B< B! B|! B XE@A.! BT@A+!  A!  BTE@A!   | < B|! )( 7A3!   | B< B! B|! B XE@A5!  BT@A2!  A!  BTE@A!   | < )( B|7 1"@A9!  B| ) 7 A0j"$ Aj"$A  B*7 Ak"$ B7A#!  A8!  B7 B|B/7 Ak"$ B7A#!  B|)"P@A!  !A!  B*)"P@ Ak"$ Bƀ7A#!    )87A!   B*7 Ak"$ B΀7A#!  1!A!  B!A!  7 B|B7 Ak"$ BՀ7A#!  7 B|B7 Ak"$ B׀7A#!  7 B|B7 Ak"$ Bڀ7A#!  7 B|B7 Ak"$ B܀7A#!   7 B|B7 Ak"$ B߀7A#!  B7 B|B7 Ak"$ B—7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@ *  #(M@ Ak"$ B7A#!  Ak"$ )`P@A&!   )X7 B|B7 B| )`B7 Ak"$ B7A#!  B|)!  78  )P7 B| )X7 B| )`7 B| )h7 B | 7 Ak"$ B7A#!  B(|5"PP@A%!  )PP@ Ak"$ B7A#!    )P7 Ak"$ B7A#!   )P7 B| )X7 B| )`7 B| )h7 B | )87 Ak"$ B7A#!  B(|5"PP@A!  )P )P5B|>  )P7 B| )`7 Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!   )87  )P5>  )`7 )`BXE@A'!  B |! )`! )X!B!A! B|!  B| )7 B|" S@A!  7@ )PB | )8B?B|!  )7  7 B| 7 Ak"$ B7A#!   )P7 Ak"$ B7A#! )@5! B| > Aj"$ Aj"$A  >4  )P7 Ak"$ B7A#!  B| 54> Aj"$ Aj"$A B| > Aj"$ Aj"$A B|B> Aj"$ Aj"$A  )`7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  )8B?! )B |" B|)! ! )8! ) ! )(!A!  B|! )  B|)R@A !  B|" S@A! 5! B| > Aj"$ Aj"$A )! ! PPE@A!  )QE@A ! )" QE@A !   BXE@A!  B SE@A !  B |!B!A!  B|B> Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!   ) B|7 B| )(BBBBTB(|7 Ak"$ B7A#!  )! B0| 7 Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!   )()! )0B|Bx!  7 PE@A!  B T@A!  B7 B|B/7 Ak"$ B7A#! B|)"PPE@A!   )()7 )( 7 )(B7 )()"P@ Ak"$ B7A#!  )()"BTE@A! )( ) |7 B8| B| |7 A j"$ Aj"$A B )() |T@A!  A!   7 B|B7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  B߱7 B|B7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  A0k"$ B(|B7#)0"P@ Ak"$ B7A#!  B(| )7 B< B|B7 B| B(|7 B|B7 B |B7 Ak"$ B7A#!  A0j"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A8k"$#)0"P@ Ak"$ B7A#!    4B|>#)0!  70 P@ Ak"$ B7A#!  #P@ Ak"$ B7A#!  )!  7(  )@7 B< B|B7 B|B7 B|B7 B |B7 Ak"$ B7A#!  )0 )(7 )04! )0 B|> BBQE@A!  #P@ Ak"$ B7A#!  #1E@A!  #Bu7 A8j"$ Aj"$A A ~#!@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   1,@A !  B<, B7, B7, Aj"$ Aj"$A B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A0k"$#)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   1,E@A !  ),P@A !   ), )8|7, A0j"$ Aj"$A  7( B < B|B7 B|B7 B|B7 B |B7 Ak"$ B7A#!  )(!A !  A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$#)0"P@ Ak"$ B7A#! )"P@ Ak"$ B7A#!  1,E@A!  ),PP@A !  B<, Aj"$ Aj"$A  78 B(|B7 B(|B7 B(| ),7 B0| ),7 B < B|B7 B| B(|7 B|B7 B |B7 Ak"$ B7A#!  )8!A !  Bڻ7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@   #(M@ Ak"$ B7A#!  A8k"$ )@P@ Ak"$ B7A#!  )@B7#)0"P@ Ak"$ B7A#!  )@ )7 B7 Ak"$ B7A#!  )" )HB|7 B*7 B| 7 B|B7 B|B7 Ak"$ B7A#!  5 ! B(|B7 B(|B7 B(| )@)7 B0| 7 B < B|B7 B| B(|7 B|B7 B |B7 Ak"$ B7A#!  A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  )0"P@ Ak"$ B7A#!  )!  )B|7 "P@ Ak"$ B7A#!   )-PPE@A! B|B7 B|B7 B|B7 B| )7 B| )7 )-"BTE@A! B|B. B|)7 B)< B|B7 B| B|7 B|B7 B |B7 Ak"$ B7A#! Aj"$ Aj"$A ) QE@A!  B(|B7 B(| )7 B&< B|B7 B| B(|7 B|B7 B |B7 Ak"$ B7A#!  A!   7 B0|B7 B0|B7 B0| )7 B8| )7 B< B|B7 B| B0|7 B|B7 B |B7 Ak"$ B7A#!  A!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ BĔ7A#!  A(k"$#)0"P@ Ak"$ BĔ7A#!  # )7 B< B|B7 B|B7 B|B7 B |B7 Ak"$ BĔ7A#!  A(j"$ Aj"$A A #!@@@@@@@@  #(M@ Ak"$ BȔ7A#!  A(k"$ 10BBPP@A!   10B< B| )87 B|B7 B|B7 B |B7 Ak"$ BȔ7A#!  A(j"$ Aj"$A B$< B|B7 B|B7 B|B7 B |B7 Ak"$ BȔ7A#!  A!  A ~#!@@@@@@@@@   #(M@ Ak"$ B̔7A#!  Ak"$#)0"P@ Ak"$ B̔7A#!  )HP@ Ak"$ B̔7A#!  )! )H )H)B|7 )H) QE@A !  B(|B7 B(| )H)7 B'< B| )P7 B| B(|7 B|B7 B |B7 Ak"$ B̔7A#!  Aj"$ Aj"$A )H 7 B0|B7 B0|B7 B0| )H)7 B8| )H)7 B< B| )P7 B| B0|7 B|B7 B |B7 Ak"$ B̔7A#!  A!  A #!@@@@  #(M@ Ak"$ BД7A#!  A(k"$ B< B|B7 B|B7 B|B7 B |B7 Ak"$ BД7A#!  A(j"$ Aj"$A A ~#!@@@@@@@@@   #(M@ Ak"$ BԔ7A#!  Ak"$ )HPPE@A !  )HB*)SE@A !  B! #)0"P@ Ak"$ BԔ7A#!  )"P@ Ak"$ BԔ7A#!   )B|7 )0"P@ Ak"$ BԔ7A#!   )7 B(|B7 B(|B7 B(|B7 B(| )7 B0| )7 B8| B7 B< B|B7 B| B(|7 B|B7 B |B7 Ak"$ BԔ7A#!  Aj"$ Aj"$A )H!A!  A ~#!@@@@@@@@  #(M@ Ak"$ Bؔ7A#!  A8k"$#)0"P@ Ak"$ Bؔ7A#!    4B|>#)0!  70 P@ Ak"$ Bؔ7A#!  #P@ Ak"$ Bؔ7A#!  )!  7(  )@7 B< B|B7 B|B7 B|B7 B |B7 Ak"$ Bؔ7A#!  )0 )(7 )04! )0 B|> BBQE@A!  #P@ Ak"$ Bؔ7A#!  #1E@A!  #Bu7 A8j"$ Aj"$A A ~#!@@@@@@@@@@   #(M@ Ak"$ Bܔ7A#! A0k"$ B/7 Ak"$ Bܔ7A#!  )"BQE@A!  B(|B7 B(|B7 B"< B|B7 B| B(|7 B|B7 B |B7 Ak"$ Bܔ7A#!  A0j"$ Aj"$A B(|B7 B(| 7 B"< B|B7 B| B(|7 B|B7 B |B7 Ak"$ Bܔ7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@ (  #(A(jM@ Ak"$ B7A#!  Ak"$ B|A #! )P@ Ak"$ B7A#!  ))(!A!  )H)(! PPE@A !   7H )"PP@A !  B|B7 B|B7 B|B7 B|B7 B|B7 B|B7 B| B|)7 )P@ Ak"$ B7A#!  ))!  B|7 B| )7 )$  Ak"$ B7Av!A #!  B|1@A!  Aj"$ Aj"$A  7@ )! B| 7  7 Ak"$ B7A#!   B|)! B|)! PPE@A!!  B| 7 B| 7 )H4"BPE@A!  B! B| 7 4 ! B| 7 B|B7 BBQE@A!  B|7 B| 7 B| 7 B|B< B | )@7 Ak"$ B7A#! B(|)! B0|)! B| 7 B| 7A!  )HB|!A! Aj"$ Aj"$A B|)!  78 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )87 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A j~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !"#$%&&'()*+,,,-./012345666789:;<=>?@@@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvvvwxyz{|}~ #(AjM@ Ak"$ B7A#!  Ak"$B )SE@A!  )PP@A!  # )QE@A!  #)0"P@ Ak"$ B7A#!  # )Q@A!  Ak"$ B7A#!  4! )BQE@A!  )BQE@A!  )P@ Ak"$ B7A#!  ))p"PPE@A!  ))x!B! B|A #! B| 7 B| 7 )P@ Ak"$ B7A#!  ))! ))! )PP"E@A!  B!  >L  7  <?  7  <C  7 PE@A!  "P@ Ak"$ B7A#!  B| )7 B| B|7  B|)7 Ak"$ B7A#!   B|)! B|)! PPE@A!  B| 7 B| 7 B|A #! )! )! )! )! )! )!B!B! B! B! A!  B| 7 B| 7 B| B|)7 B|B7 B| B|)7 B|B7 B|B7 B Q! B!  SE@A!  B|)" P@ Ak"$ B7A#!  B|)! 5P@A!   7  7h  7  <=  7  << B|)P@A!  )0"PPE@A!  ) Q!  7  7  7  7 P@ Ak"$ B7A#!   1("BQE@A!  B|B7B!B!  7  7 B|)! B| 7 B| Bx|7 )PP!  <B E@A!  B| B|)7 E@A!  4 BBQ! B| 4 7 B|B7 @A!  B| B|)7 E@A6!  B|)"P@ Ak"$ B7A#!  5"PPE@A!  B|  )|B|7 @A!  1C"@A!   7 1?"@A!  B|! 1(BQE@A=!  B SE@A=!  B|!  B|)! PE@A=!  E@A!  1(!  PP@A!  )P!  ! PE@A!   SE@A!  B|)! )!  R@A!  B| 7 Aj"$ Aj"$A  7  7  <A  )7 B| < B| 7 B| 7 B | 7 Ak"$ BĀ7A#!  B(|)! )! )! )! 1C! )! )! 1A! )! )! )! )! 1?!A=!   <@  7 B|)!B SE@A!  ) TE@A!  E@A!   7X  7 B| 7 B|B< Ak"$ Bˀ7A#!  B|)"PP@A!  1@! )X! )h!  7  7X )BPPE@A!  B! @A!  )1(! )! )! 1C! )! )! )! )! )! )! 1B! 1?! ! )!A9!   )7 B| )7 Ak"$ BԀ7A#!  B|)!  7p B|)!  7  )7 B| )7 B| )X7 Ak"$ BՀ7A#!  B(|4!  >D B |)!  7 B|)!  7 )pBQ@A!  )p! )!  7x  7 Ak"$ B؀7A#!   )7 B| )x7 Ak"$ Bـ7A#!  Bޔ 7 B|B7 Ak"$ Bڀ7A#!  Ak"$ Bۀ7A#!  B|)!  7B!A!  P@ Ak"$ B݀7A#!   B|)!  7 Ak"$ B߀7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  )B|! )! B|)!  BTE@A!  B X@A!   7 PPE@A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  )! )!A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 B| )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   4D7 Ak"$ B7A#!  Ak"$ B7A#!  B|)! ))" T@A!  ))0"PPE@A!  B 4SE@A!  ) )QE@A!  B|)!  7 B|)!  7 B|)!  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )B|!A!  B 4LW@A!  A!   7  7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   ) )}7 Ak"$ B7A#!  Ak"$ B7A#!  A!   )7 B|B7 B| )p7 Ak"$ B7A*#!  B|1E@A!  B!B!A!  )p! )!A!  )1(!  )7 B| )7 B| )7 B| P< B| < B| < Ak"$ B7A#!  B |1! )X! )!A!   7 B|A#! )X! 1@! )h!A!  B|)"P@ Ak"$ B7A#!  )1! )4 )|!  7  <=  7X  )7 B| )7 B|B> B| 7 B |B7 Ak"$ B7A#!  B(|4"BS@A!  BTE@A!  ) B~|!  7 B| 4 > 1! B| < )BPPE@A!   B|7 B| )7 Ak"$ B7A#!  B|)!  7 B|)!  7  )7 B| )7 B| )X7 Ak"$ B7A#!  B(|4!  >H B |)!  7 B|)!  7 Ak"$ B7A#!   )7 B| )7 Ak"$ B7A#!  BԀ7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 B| )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   4H7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )B|!A!   B|7 B| )7 B| )7 B| )P< B| < B| 1=< Ak"$ B7A#!  B |1@A!  )!A!  1=! )X! )!A!  B|!A!  )BP@A!  A!  B|)! PE@A!  )BPPE@A!  B|!  7`  7P  7 B| 7 B|B< Ak"$ B7A#!  B|)"PP@A!  1=! )! )`! )! )1(BQE@A!   B"BQE@A!  B SE@A!  B|! )1(! B|! )! )! )! )! 1  )7 B| )7 B|B> B| 7 B | B|7 Ak"$ Bˁ7A#! } B(|4"BS@A! | BTE@A! { ) B~|"1BQE@A! z  1>B"BQE@A! y B )SE@A! x )B|! )!A! v ) )SE@A! v )BTE@A! u ) )B| )`7 )B|! ! )!A! r )!A! q B Q@A! q BQ@A! p )! )!A! n 1>! )! )`! )!A! m @A! m  )Q@A! l B|! ! !A! j ))!  B|7 B| )7 )$  Ak"$ B7Av!A #! j B|1E@A! i )! )! )! )! )! )! 1 B QE@A! = B| )87 )0"P@ Ak"$ B7A#! = )"P@ Ak"$ B7A#! = )! )!  7  7  7  7 B|)!  7 B| 7 B| 7 B| B|7 Ak"$ B7A#! ; B |4 B|)|! B| 7 B| B|7 )! )! )! )! )! 1$  )H7 B| )P7 Ak"$ B7A#!  B|)! B|)! BQ@A!  BQ@A !  4$!B!A!   7 B|B7 B| 7 Ak"$ B7A*#!  B|1E@A !  )`PPE@A!  )`!B! P@ Ak"$ B7A#!  ) )H)R@A !  )"P@ Ak"$ B7A#!  4BB B ! E@A!  ! !A! )Bx!A! )@P@ Ak"$ B7A#! )@)(""P@ Ak"$ B7A#! B|"P@ Ak"$ B7A#! )! 1!A!  7 B|B7 B| 7 Ak"$ B7A*#! B|1@A ! A !  )H7 B| )P7 Ak"$ B7A#! B|)!  7( B|)!  70 Ak"$ B7A#!  BӬ7 B|B7 Ak"$ B7A#!   )07 B| )(7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  BȜ7 B|B7 Ak"$ B7A#!   A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   #(AjM@ Ak"$ B7A#!  Ak"$ B|A #!  )7 B| B|7 B|B 7 B|B 7 Ak"$ B7A#!  B|A#! B| B|A #! )! 1! )! )! B|!B!B!A!  B|! )" P@A !   W@A !  PPE@A !  BTE@A!   B| 7 @A!  B|! B|! B S@A!  @A !  B| 7 Aj"$ Aj"$A  7 B|B7  B|7 Ak"$ B7A#!  )!A !  78  7  70  <&B')PP!  <' E@A!  7 B|  }7 B| B|7 Ak"$ B7A#! B|) )0|B|! 1' 1&! )! 1! )! )! )8!A !  7( Ak"$ B7A#! Bׯ7 B|B7 Ak"$ B7A#!   )(7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )0!A!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ )HP@ Ak"$ B7A#! )H)!  7(  7 Ak"$ B7A#!  )! )! PP@A! B! E@A !  )H)BR@A !  Aj"$ Aj"$A  7 B| 7 B| )(7 Ak"$ B7A#!  A !   78  70  7 B| 7 B| )H7 B|B< B|B< B|B< Ak"$ B7A#!  B |1! )8! )0!A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$  )p7 B| )x7 Ak"$ B7A#!  )!  7` )!  7P Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )`7 B| )P7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )pP@ Ak"$ B7A#!  )p)!  )p7 B| )x7 B| )B| )  )T7 B|B< Ak"$ B7A#!  B(|)!  7@ B |)!  7X B0|4!  >< Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )X7 B| )@7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   4<7 Ak"$ B7A#! Ak"$ B7A#! )p)" )T@A! Ak"$ B7A#! Ak"$ B7A#! Ak"$ B7A#!  Aj"$ Aj"$A  7H Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   ) )H}7 Ak"$ B7A#!  Ak"$ B7A#!  A!  A #!@@@@  #(M@ Ak"$ B7A#!  A(k"$  )07 B| )87 B| )@7 B| )H7 B |B7 Ak"$ B7A#!  A(j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 4  ! #(AjM@ Ak"$ B7A#! # Ak"$B.1E@A ! " )P@ Ak"$ B7A#! " ))0"PPE@A ! ! B 4SE@A ! ))pPPE@A !  )"PPE@A !  )PP@A&!  )P@ Ak"$ B7A#!    )B|7 Ak"$ B7A#!  B|5B_BBQE@A%!  ))x! ))p! )B}!  7  7  7  7 B| 7 B| )7 B| )7 B |B7 B(|B7 B0|B7 B8|B7 B|B7 B| 7 Ak"$ B7A#!  B|)"PE@A!  )BP@A#!  BQ@A!   )7 Ak"$ B7A#!  ))"PPE@A!  )! )!B SE@A!   7`B!A!  )B(|! ! !  7X  7 B| )7 B|! B| A#!  B|)7 B| B|A#! Ak"$ B7A#!  )XB|" )`S@A!  Aj"$ Aj"$A Aj"$ Aj"$A Ak"$ B7A#! B7 B|B7 Ak"$ B7A#! Ak"$ B7A#! A!   )7 B| )7 B| )7 B| )7 B |B7 B(|B7 B0|B7 B8|B7 B|B7 B| )B7 Ak"$ B7A#!  B|)!A!  )! )! )!A!   B|7 B|B> Ak"$ B7A"#!  ))0")"P@ Ak"$ B7A#!  P@ Ak"$ B7A#!  B| A #! ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  B7 ))0"P@ Ak"$ B7A#!   B|7 B|B> Ak"$ B7A"#!   B|7 Ak"$ B7A#!  A !  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ B7A#!  Ak"$ B|)!  78 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )87 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B|)! B|)!B SE@A !   70B!A !  )PB|! !  7(  7P )!  7  7 Ak"$ B7A#!  B|)!  7H B|)!  7@  7 B| 7 B| )(P< B|B< B|B< Ak"$ B7A#!  B|1@A!  )(B|" )0S@A!  B|)BQ@A!   B|)7 Ak"$ B7A#!   B|)! B|)! PP@A!  B! E@A!  B|)BR@A! Aj"$ Aj"$A B|)!  7 B| 7 B| 7 Ak"$ B7A#! A!  7H  7@  7 B| 7 B|B< B|B< B|B< Ak"$ B7A#! B|1! )H! )@!A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A!   )H7 B| )@7 B| ) 7 Ak"$ B7A#!  A !  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2  !"#$$%%&'( #(M@ Ak"$ B7A#! * Ak"$  )7 B| )7 Ak"$ B7A#! ) )!  7h )!  7H  )7 B| )7 B|B< Ak"$ B7A#! ( )"PP@A'! ' )H! )h!  7`  7@  )7 B| )7 B| )7 B|B< Ak"$ B7A#! % B(|)!  7P B |)!  7p B0|4!  >< )@BQ@A#! $ )@! )`!  7@  7` Ak"$ B7A#! "  )`7 B| )@7 Ak"$ B7A#! ! BԀ7 B|B7 Ak"$ B7A#! Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )p7 B| )P7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   4<7 Ak"$ B7A#!  Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))" )T@A!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Aj"$ Aj"$A  7X Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   ) )X}7 Ak"$ B7A#!  Ak"$ B7A#!  A!   )`7 B|B7 B| )@7 Ak"$ B7A*#!  B|1E@A&! B!B!A ! )@! )`!A !  7x  )7 B| )7 B|B> B| )7 B |B7 Ak"$ B7A#! B(|4!B WE@A.! BTE@A0!  )x B~|4 !  )7 B| )7 B| > Ak"$ B7A#!  B |)! B|)! ! ! !A!  )H! )h!A,!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B|B7 B|A #! B|B7 B| )X7 B | B|7 B(|#7 B0| )`7 B8| )h7 B| )p7 B| )x7 B| B|7  B|7 Ak"$ B7A#!  B|)! B| 7 Aj"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )xPPE@A!  B7 B|B7 B|B7 B| )`7 B | )h7 B(| )p7 B0| )x7 B8|B7 B|B7 B|B7 Ak"$ B7A#!  B|)! B| 7 Aj"$ Aj"$A B7 B| )x7 Ak"$ B7A#!   A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! A k"$#)0"P@ Ak"$ B7A#! B 4SE@A !  )8PPE@A !  ) )8QE@A!  B|B< A j"$ Aj"$A )!  )8Q@A!   )(7 B| )07 B| 1@< B| 1A< B| 1B< Ak"$ B7A#!  B|1! B| < A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 9  !!"#$%%&' #(M@ Ak"$ B7A#! ) A0k"$ Ak"$ B7A#! ( B 4S@A5! '  )8PPE@A4! & 1IBBQE@A ! %  1JB"BQE@A/! $  )87 B| )@7 Ak"$ B7A#! # B|)!  7 B|)!  7( BQ@A-! " B! E@A! 1H"E@A,!   7 B| 7 B|B.< Ak"$ B7A-#!  B B|)WE@A*!  B ) W@A'!  B! E@A%!  B ) S@A#!  B! E@A!!  B ) TE@A6!  )(1!B XE@A!!  BX! B| < A0j"$ Aj"$A B!A!   )(7 B|B7 B|B7 Ak"$ B7A*#!  B|1!A!  B!A!   )(7 B|B7 B|B7 Ak"$ B7A*#!  B|1!A! B!A! B|B< A0j"$ Aj"$A  7 B|B7 B| 7 Ak"$ B7A*#! B|1! ) ! )(!A !  B Q@A !  BQ@A !  B|B< A0j"$ Aj"$A B|B< A0j"$ Aj"$A B|B< A0j"$ Aj"$A B7 B| ) 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ;  !"#$%%%&&'((()* #(M@ Ak"$ B7A#! , A8k"$ )@P@ Ak"$ B7A#! ,   )@B|7 Ak"$ B7A#! + 5"B_"B"B TE@A:! * B' BB|")! )! BQ"E@A! ( )@1"PPE@A! ' B XE@A7! & B!B!  >  7  70 E@A6! $ )@)PP@A1! # B!  7 )@)!  7( Ak"$ B7A#! ! B7 B|B 7 Ak"$ B7A#!  )(7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )07 B| ) 7 Ak"$ B7A#!  Ak"$ B7A#!  5B BPP@A-!  B )W@A'!  )@)PP@A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A8j"$ Aj"$A Ak"$ B7A#!  Bǡ7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#! Ak"$ B7A#! A! Ak"$ B7A#! B7 B|B7 Ak"$ B7A#! Ak"$ B7A#!  A!   Ak"$ B7A#!  ) )@)}B#!!A!  BQ@A!  A!  B' B|")! )!A!  B!B!A !  A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ K  !""""""####$%&'()))**+ #(M@ Ak"$ B7A#! - Ak"$ Ak"$ B7A#! , #)0"P@ Ak"$ B7A#! , 4!  >, )!  7@ PPE@A! + )` R@A! * Ak"$ B7A#! ) B|)!  70 )!  7P )`! )@!B!A ! ' B|!  TE@A! &  78  B|)!  7H  QE@A5! % B! E@A.! # B! @A! ! Ak"$ B7A#! Ak"$ B7A#!  Ak"$ B7A#!   )H7 Ak"$ B7A#!  )HP@ Ak"$ B7A#!  )H)0!#)0 R@A#!   B7 B|B7 B|B7 B| )H7 B |B7 Ak"$ B7A#!  )0! )P! )`! )@! )8!A!    )HB|7 Ak"$ B7A#!  B|5B_BBQE@A!  Ak"$ B7A#!  B7 B|B67 Ak"$ B7A#!  Ak"$ B7A#!   )H7 Ak"$ B7A#!  A!   7 B|B< Ak"$ B7A#!  B|1E@A4!  4,BS! )P! )`! )@! )8! )H! ! )0!A! B!A1!  QE@A7! B!A! P@ Ak"$ B7A#!   B|7 Ak"$ B7A#! B|5BQ! )0! )P! )`! )@! )8! )H!A!  Aj"$ Aj"$A Ak"$ BÀ7A#!  Ak"$ BĀ7A#!  Ak"$ Bŀ7A#!   )@7 Ak"$ Bƀ7A#!   B7 B|B7 B|B7 B| )@7 B |B7 Ak"$ Bɀ7A#!  A!  A ~#!@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$ )hP@ Ak"$ B7A#!  )h)(! )h)0"PP"E@A!   TE@A!  !  7  70  7( E@A!   TE@A!   7 Ak"$ B7A#!  BΠ7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  )07 Ak"$ B7A#! B7 B|B 7 Ak"$ B7A#!  )X7 Ak"$ B7A#! Bߔ 7 B|B7 Ak"$ B7A#!  )`7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B8|B7 B|B7 B|B7 B8|B7 B| )h7 B| )p7 )(B~|! )Bp|"   T!  )X   )XT7 ) B|! )B|"   T! B| )`  )` T7 B| B8|7 Ak"$ B7A#!  Aj"$ Aj"$A !A!  ! !A!  A ~#!@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A k"$ )(P@ Ak"$ B7A#!   )()7 Ak"$ B7A#!   )! )! PPE@A!  1("BQE@A!  B8|B< A j"$ Aj"$A BQ@A! B QE@A ! 10"E@A ! B8|B< A j"$ Aj"$A B.1! B8| P< A j"$ Aj"$A  7 B| 7 Ak"$ B7A#!   B|)! B|)!B W@A!  B! B8| < A j"$ Aj"$A  7 B|B7 B|B7 Ak"$ B7A*#!  B|1!A!  B8|B< A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$B')PPE@A !  B8|A#! )xP@ Ak"$ B7A#!  )x!B!A!  )0B|! ! ! )"P@A !   7(  70  7 B|B7 B| B8|7 Ak"$ B7A#!  )(B|"B S@A!  B8|B7  B8|7 Ak"$ B7A#! Aj"$ Aj"$A )xP@ Ak"$ B7A#! )x!B!A! )0B|! ! ! )"P@A!  7(  70  7 Ak"$ B7A#!  Bׯ7 B|B7 Ak"$ B7A#!   ) 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )(B|"B S@A!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ,  ! #(M@ Ak"$ B7A#! # Ak"$ )xP@ Ak"$ B7A#! # )x )h7B!  )pWE@A! !  7  )x7 Ak"$ B7A#! )x)"PPE@A(!   78  7 Ak"$ B7A#!  B|)!  7 B|B7 B|B7 B| )87 B| 7 B|)!  7( Ak"$ B7A#!   )(7 B| ) 7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  )x)"PP@A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )h7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  )B|! )x)(PE@A!  B| 7 Aj"$ Aj"$A  78  7 Ak"$ B7A#! B|)!  7 B|B7 B|B7 B| )87 B| 7 )x)!  7 B|)!  70 Ak"$ B7A#!  )07 B| ) 7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )7 Ak"$ B7A#! Ak"$ B7A#!  Ak"$ B7A#!  A!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  A!  A ~#!@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$B.5PPE@A!  B! )! B')7 B| ) 7 $  Ak"$ B7Av!A #!  Aj"$ Aj"$A #)0"P@ Ak"$ B7A#!  )#R@A!  Bȉ!A!  A ~#!@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A8k"$B')PPE@A ! B.5PPE@A! B! )PPPE@A ! B|B7 B |B7 B(|B7 B0|B7 B| )@7 B(| )H7 B0| )P7 )! B')7 B| B|7 $  Ak"$ B7Av!A #!  A8j"$ Aj"$A #)0"P@ Ak"$ B7A#!  )#R@A!  Bȉ!A!  A8j"$ Aj"$A B7 B| )P7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ B7A#! Ak"$ ) P@ Ak"$ B7A#!  ) 4(!  ) 7 B| > Ak"$ B7A#!  )7 Ak"$ B7A#!  ) 1BBP! )! )! PE@A !  PPE@A !  B|! B(| B }B?B|7 B(| 7 Aj"$ Aj"$A B(| 7 B0| 7 Aj"$ Aj"$A B7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )1B"BP@A!  )1BB"BXE@A !  BXE@A !  BQ@A !  BQ@A!  B| )B0|7 Aj"$A B| )B|7 Aj"$A B| )B|7 Aj"$A BQE@A ! B| )B8|7 Aj"$A B| )B|7 Aj"$A BXE@A! BQE@A! B| )B|7 Aj"$A B| )B8|7 Aj"$A BQ@A!  BQE@A!  B| )B|7 Aj"$A B| )B8|7 Aj"$A B|B7 Aj"$A A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$  ) 7 Ak"$ B7A#! )"PP@A ! ) P@ Ak"$ B7A#! ) 1BB"BQ@A ! BQE@A !  ) )07 Ak"$ B7A#! B|)! B|)! B(| 7 B0| 7 Aj"$ Aj"$A B(|B7 B(|B7 Aj"$ Aj"$A  ) )07 Ak"$ B7A#!  B|)! B|)! B(| 7 B0| 7 Aj"$ Aj"$A  4!  ) 7 B| > Ak"$ B7A#!   B|)7 Ak"$ B7A#!  B|)! B|)! B(| 7 B0| 7 Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 8  !"#$%&'()*+, #(M@ Ak"$ Bĕ7A#! . Ak"$ 4pBP@A*! - )h!B'!A! + )! PPE@A ! * )" XE@A! ) )!  TE@A! ( 4pB !  B |!  T@A-! ' ! B| 7 Aj"$ Aj"$A  70 B'7 Ak"$ Bĕ7A#! % B')! B7 B| 7 B| 4p> Ak"$ Bĕ7A#! $ B|))!  7P B |1!  </ B'7 Ak"$ Bĕ7A#! # 1/"@A)! " Ak"$ Bĕ7A#! ! BƟ7 B|B7 Ak"$ Bĕ7A#!  4pB B 7 Ak"$ Bĕ7A#!  B7 B|B7 Ak"$ Bĕ7A#!   )07 Ak"$ Bĕ7A#!  B7 B|B7 Ak"$ Bĕ7A#!  Ak"$ Bĕ7A#!  B'!A(!   7X )!  7H )!  7@ Ak"$ Bĕ7A#!  B7 B|B7 Ak"$ Bĕ7A#!   )H7 Ak"$ Bĕ7A#!  B΂7 B|B7 Ak"$ Bĕ7A#!   )@7 Ak"$ Bĕ7A#!  Ak"$ Bĕ7A#!  Ak"$ Bĕ7A#!  )X)! PP@A !  A+!  B| )P7 Aj"$ Aj"$A B|B7 Aj"$ Aj"$A B7 B|B.7 Ak"$ Bĕ7A#!   7H  7@  78 Ak"$ Bĕ7A#! BƟ7 B|B7 Ak"$ Bĕ7A#!  )8B 7 Ak"$ Bĕ7A#! B7 B|B7 Ak"$ Bĕ7A#!  )H7 Ak"$ Bĕ7A#!  B7 B|B7 Ak"$ Bĕ7A#!   )@7 Ak"$ Bĕ7A#!  Ak"$ Bĕ7A#!  Ak"$ Bĕ7A#!  B7 B|B!7 Ak"$ Bĕ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ =  !"#$%&'()*+,-./012 #(M@ Ak"$ Bȕ7A#! 4 Ak"$ 4hB"P@A/! 3 BQ@A/! 2 )`!B'!A! 0 )! PPE@A.! / ) XE@A! .  )TE@A! - PPE@A! ,  7P )! B7 B| 7 B| 4h> Ak"$ Bȕ7A#! + B|))"PP@A ! * )P)! 4hB !  B |! )P)" T@A0! ) ! B| 7 Aj"$ Aj"$A B| 7 Aj"$ Aj"$A  7  B'7 Ak"$ Bȕ7A#! & B')! B7 B| 7 B| 4h> Ak"$ Bȕ7A#! % B|))!  7@ B'7 Ak"$ Bȕ7A#! $ )@PP@A-! # Ak"$ Bȕ7A#! " B7 B|B7 Ak"$ Bȕ7A#! !  4hB B 7 Ak"$ Bȕ7A#! B7 B|B7 Ak"$ Bȕ7A#!   ) 7 Ak"$ Bȕ7A#!  B7 B|B7 Ak"$ Bȕ7A#!  Ak"$ Bȕ7A#!  B'!A,!   7H )!  78 )!  70 Ak"$ Bȕ7A#!  B7 B|B7 Ak"$ Bȕ7A#!   )87 Ak"$ Bȕ7A#!  B΂7 B|B7 Ak"$ Bȕ7A#!   )07 Ak"$ Bȕ7A#!  Ak"$ Bȕ7A#!  Ak"$ Bȕ7A#!  )H)! PP@A$!  A;!  B| )@7 Aj"$ Aj"$A B!A!  B|B7 Aj"$ Aj"$A  7(  78  70 Ak"$ Bȕ7A#!  B7 B|B7 Ak"$ Bȕ7A#!  )(B 7 Ak"$ Bȕ7A#! B7 B|B7 Ak"$ Bȕ7A#!  )87 Ak"$ Bȕ7A#! B7 B|B7 Ak"$ Bȕ7A#!  )07 Ak"$ Bȕ7A#!  Ak"$ Bȕ7A#!  Ak"$ Bȕ7A#!  B7 B|B!7 Ak"$ Bȕ7A#!  B7 B|B.7 Ak"$ Bȕ7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 6  !"#$%&'()*+ #(M@ Ak"$ B̕7A#! - Ak"$ 4XBBQ@A3! , )P!B'!A! * )! PPE@A2! ) ) XE@A! (  )TE@A! ' PPE@A! & )! )!B SE@A! % 4X!B!A ! # B|!  SE@A! "  B~|")! )! B B !  XE@A ! !  |TE@A ! ) | }! ! B| 7 Aj"$ Aj"$A B!A!  )" 4XB B |!A!   7  B'7 Ak"$ B̕7A#!  B')! B7 B| 7 B| 4X> Ak"$ B̕7A#!  B|))!  78 B'7 Ak"$ B̕7A#!  )8PP@A1!  Ak"$ B̕7A#!  B7 B|B7 Ak"$ B̕7A#!   4XB B 7 Ak"$ B̕7A#!  B7 B|B7 Ak"$ B̕7A#!   ) 7 Ak"$ B̕7A#!  B7 B|B7 Ak"$ B̕7A#!  Ak"$ B̕7A#!  B'!A0!   7@ )!  70 )!  7( Ak"$ B̕7A#!  B7 B|B7 Ak"$ B̕7A#!   )07 Ak"$ B̕7A#! B΂7 B|B7 Ak"$ B̕7A#!  )(7 Ak"$ B̕7A#! Ak"$ B̕7A#! Ak"$ B̕7A#! )@)! PP@A(!  A4!  B| )87 Aj"$ Aj"$A B!A!  B! B| 7 Aj"$ Aj"$A B7 B|B.7 Ak"$ B̕7A#!   A ~#!@@@@@@  #(M@ Ak"$ BЕ7A#!  )P@ Ak"$ BЕ7A#!  )1! BBPE@A!  B|B7 Aj"$A )1 )1BB )|"B|"P@ Ak"$ BЕ7A#!  B|"P@ Ak"$ BЕ7A#!   1! 1! B|  BB7 Aj"$A A ~#!@@@@@@@@   #(M@ Ak"$ Bԕ7A#!  B|B7 B|B7 )PPE@A !   )1B )1B"PE@A!  B|B7 B|B7 Aj"$A  B| )B|7 B| 7 Aj"$A B|B7 B|B7 Aj"$A A ~#!@@@@@@@   #(M@ Ak"$ Bؕ7A#!  Ak"$ B |B7 B |B7  )7 Ak"$ Bؕ7A#!  )"PE@A!  B |B7 B |B7 Aj"$ Aj"$A  )1B )1B! B |  )|B|7 B(| 7 Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ Bܕ7A#! A k"$ )(PPE@A! )(1BBPE@A! B0|B7 B0|B7 A j"$ Aj"$A  )(1!  < )(1!  <  )(7 Ak"$ Bܕ7A#!  B|)! B|B> 1B 1B"B|!B S!  |B|!   ! )( |! B| R@A!  B|4!  )(7 B| > Ak"$ Bܕ7A#!   B|)7 Ak"$ Bܕ7A#!  B|)! B|)! B0| 7 B8| 7 A j"$ Aj"$A  B|7 B| 7 B|B7 Ak"$ Bܕ7A#!  A!  A  ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ `  !!!"#$$%&'''()*+,,-./012345556789:;;;<=>??@@ABCDEFGHIJ #(AjM@ Ak"$ B7A#! L Ak"$B')PPE@A! K B')! B|A#! BWE@A! J B|A#! B| B|7 B7 B| 7 B| B|7 Ak"$ B7A#! H B|)!  7 B'7 Ak"$ B7A#! G B|)"PPE@A! F )! )! )! PPE@A! D )! B B|}B?B|! B|!B SE@A! C  7xB!A! A B|! !  7p  7 P@ Ak"$ B7A#! A )!  7 )! )!B S@A=! @ P@ Ak"$ B7A#! @ )PPE@A! ? B|" S@A! > Aj"$ Aj"$A )! B7 B| 7 B|B7 Ak"$ B7A#! < B|)!B')"B|!B')!B')" T@A7! ; B' B|7  B|!B.5PP@A4! :  7 ) 7 ))! ))!B S@A! 8 )! )x! )p! )!A! 6  7hB!A+! 5 )B|! ! !  7P  7 )!  7 B|A#! B|A#! B| B|7 Ak"$ B7A#! 4 B| 5>  )7 B| )7 B| B|7 Ak"$ B7A#! 3 B|1@A2! 2 )PB|" )XS@A! 1 )!  7 ))! B7 B| 7 B| 4D> Ak"$ B7A#! / B|)"P@ Ak"$ B7A#! / )`B|" )hS!B.5PP@A0! .  )7 E@A! , )B|! ! !  7`  7 4!  >D  ))|!  7 P@ Ak"$ B7A#! + 5! B7 B| )7 B| > Ak"$ B7A#! * B|)")! )!B SE@A3! )  7XB!A! '  ) Ak"$ B7#!A)! & )!A$! % )!A$! $   Ak"$ B7#! )B|  Ak"$ B7#!A! #  7 B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B7A#! # B(|)! B0|)!B' B8|)7B.5PP@A;! " B' 7 ! )!A!  B'  Ak"$ B7#!A:!   7h  7B! A!  B|! ) Q@A!  B|" S@A>!  B|!  T@A!   7  7X  7P  B|!B.5PP@A!   7 5! B7 B| )7 B| > Ak"$ Bŀ7A#!  B|)"P@ Ak"$ Bƀ7A#!   )X7  )P7B.5PP@A!   )7 )`B|" )hSE@A!  )B|! )x! )p! )! )! )h! ! )!  7`  7 4! )" PP@A!  )|!  7 P@ Ak"$ B΀7A#!  5! B7 B| )7 B| > Ak"$ BЀ7A#!  B|)")! )! )!B SE@A!  )! !B!A?! )! )x! )p! )!A!  ) Ak"$ BԀ7#!A!   Ak"$ Bր7#!A!  7H B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B؀7A#! B0|)! B8|)! B(|)! B|! )! ! )H!A!  )! !A!  B7 B| 7 B| > Ak"$ Bۀ7A#!  B|))! A!  B!B!B!A !  Aj"$ Aj"$A B7 B| 7 Ak"$ B߀7A#!   A B~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   !!!""#####$%%%%&&'''''())))*+,-./011223344556789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ #(AjM@ Ak"$ B7A#!  Ak"$ B| )7 B| )7 B7 B| )7 B| B|7 Ak"$ B7A#!  1 "@A!  B| )7 B| )7 B7 B| )7 B| B|7 Ak"$ B7A#!  B|)P@ Ak"$ B7A#!  ) )Q@A!  )P@ Ak"$ B7A#!  )P@ Ak"$ B7A#!  )1B"B" )1BBR@A!   <+  7x  )7 Ak"$ B7A#!  B|)!  7p B|)!  7  )7 Ak"$ B7A#!  B|) )pRE@A!  B|B< Aj"$ Aj"$A  )7 B| )p7 Ak"$ B7A*#!  B|1E@A!   )7 Ak"$ B7A#!  B|)!  7  )7 Ak"$ B7A#!  B|)! )PP"@A!  PP@A!  1+B|BBX@A!  )xBXE@A!  )xBX@A!  )xBQE@A!  )32 )32R@A!  )30! )30" R@A!   )1BBP"PE@A!  B!  ) |! BXE@A!   )1BBPPE@A!  B!  =.  =,  7 ) |!  7 )!B!A1!  )PB|! 3.! )! 3,! )! )!  SE@A6!  B!  |)!  TE@A!   7P  |)!  7 B| 7 B| 7 Ak"$ B7A#!  B|1@A0!  B|B< Aj"$ Aj"$A  )1BBPPE@A!  B! )32B! )30!  |B! ) |! BXE@A!   XE@A!  B }!B }!  }! B!  B? |! )1BBPPE@A!  B! )32B! )30!  |B! ) |! BXE@A!   XE@A!   78  7  }!  70 BB }}B? B|!  7B!A!  )`B|! )8! )! )0! )! )!  SE@A!  B!  |)!  TE@A!   7`  |)!  7 B| 7 B| 7 Ak"$ BՀ7A#!  B|1@A!  B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B8!A!  B8!A Ak"$ B7A#! } ) )h|!  7 4! B|)!  7  7 B| > Ak"$ B7A#! | B|)!  7  )7 Ak"$ B7A#! { B|)!  7h B|)!  7  )7 Ak"$ B7A#! z B|) )hR@A! y  )7 B| )h7 Ak"$ B7A*#! x B|1E@A! w  )7 Ak"$ B7A#! v B|)!  7h B|)!  7  )7 Ak"$ B7A#! u B|) )hR@A! t  )7 B| )h7 Ak"$ B7A*#! s B|1E@A! r )4!  )7 B| > Ak"$ B7A#! q B|)!  7 )4!  )7 B| > Ak"$ B7A#! p B|)!  )7 B| 7 B| )7 Ak"$ B7A#! o B|1@A! n B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A ))0! ))0!  7 B| 7 B| )7 Ak"$ B7A#! h B|1@A! g B! B| < Aj"$ Aj"$A ))8! ))8!  7 B| 7 B| )7 Ak"$ B7A#! d B|1!A! b )xBQ@A! b )xBQE@A! a ))8 ))8Q@A! ` B! B| < Aj"$ Aj"$A ))0! ))0!  7 B| 7 B| )7 Ak"$ B7A#! ] B|1!A! [ ))0! ))0!  7 B| 7 B| )7 Ak"$ B7A#! [ B|1E@A! Z ))@ ))@Q! B| < Aj"$ Aj"$A B!A! V )xBXE@A! V )xBQE@A! U ))0! ))0!  7 B| 7 B| )7 Ak"$ B7A#! T B|1! B| < Aj"$ Aj"$A ))0! ))0!  7 B| 7 B| )7 Ak"$ B7A#! R B|1! B| < Aj"$ Aj"$A )xBQE@A! P B|B< Aj"$ Aj"$A )xBQE@A! N ))@ ))@R@A! M  ))07 Ak"$ B7A#! L B|)!  7p B|)!  7  ))07 Ak"$ B7A#! K B|) )pRE@A! J B|B< Aj"$ Aj"$A  )7 B| )p7 Ak"$ B7A*#! H B|1E@A! G ))@!  7p )! )!B!A! E )HB|! )p! )! )!  SE@A! D )@! )8!  TE@A! C B~!  |! )@! )8!  TE@A! B  7  7H  7  7h  )7 Ak"$ B7A#! A ) )h|!  7 B|)!  7h B|)!  7  )7 Ak"$ B7A#! @ B|) )hR@A! ?  )7 B| )h7 Ak"$ B7A*#! > B|1E@A! = ))! ))!  7 B| 7 B| )7 Ak"$ B7A#! < B|1E@A! ;  ))7 Ak"$ B7A#! : B|)!  7h B|)!  7  ))7 Ak"$ B7A#! 9 B|) )hR@A! 8  )7 B| )h7 Ak"$ B7A*#! 7 B|1E@A! 6 )) ))RE@A! 5 B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A )xBQ@A! . A! - B|B< Aj"$ Aj"$A E@A! , PP@A! + B|B< Aj"$ Aj"$A  7 )4!  )7 B| > Ak"$ BƁ7A#! )  B|)7 Ak"$ Bȁ7A#! (  )4! B|)!  7 B|)!  7@  )7 B| > Ak"$ Bˁ7A#! '  B|)7 Ak"$ B́7A#! & B|) )@R@A! %  )7 B| )@7 Ak"$ Bρ7A*#! $ B|1@A! # B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A B|B< Aj"$ Aj"$A  7 B| 7 Ak"$ BՁ7A#!   7 B| 7 Ak"$ Bׁ7A#!   7 B| 7 Ak"$ Bف7A#!   7 B| 7 Ak"$ Bہ7A#!   7 B| 7 Ak"$ B݁7A#!   7 B| 7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#! Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )x7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ )0P"PE@A!  B7 B| )(7 B| )0> Ak"$ B7A#!  A j"$ Aj"$A B7 B| )07 Ak"$ B7A#!   A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! A k"$#")! )!  7 !  7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B$7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  A k"$#")! )! ) ! )!  7 B| 7 B| 7 B| 7 Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@   #(M@ Ak"$ B7A#!  Ak"$#")"P@ Ak"$ B7A#!   B|!  7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@   #(M@ Ak"$ B7A#!  Ak"$#")"P@ Ak"$ B7A#!   B|!  7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B7A#!  A(k"$#")! )! ) !  7 )!  7 B| 7 B| 7 Ak"$ B7A#!  ) )7 A(j"$ Aj"$A A ~#!@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#")!  7 B)7 Ak"$ B7A#!  B*7 Ak"$ B7A#!  ) )7 ))"P@ Ak"$ B7A#!  B؉)5>  B)7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#")!  7  7 Ak"$ B7A#!   )7 Ak"$ B7A#!   B)7 Ak"$ B7A#!  B*B*)B*)}7 )P@ Ak"$ B7A#!  )B*)7B* )7 B)7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#")!  7 )!  7 B)7 Ak"$ B7A#!  )B.4> )4BSE@A!  )B> )4!B. >Bؠ' BB7 B/+9 Ak"$ B7A#!   B)7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$  )(7 Ak"$ B7A#!  B|)"BW@A!   )(7 B| B|7 Ak"$ B7A!#!  B|1E@A!  B0|B< A j"$ Aj"$A B0|B< A j"$ Aj"$A A t#!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7A#!  Aj"$A A ~#!@@@@@  #(M@ Ak"$ B7A#!  Ak"$#")!  7B*1!  < Ak"$ B7A#!  )! ) 7B.)!B.  B.)}|7B. ))7 ))!B.)! B07 B|  }7 Ak"$ B7A#!  Aj"$ Aj"$A A #!@@@@@@@@@   #(M@ Ak"$ B7A#!  Ak"$  ) 7 Ak"$ B7A#!  ) P@ Ak"$ B7A#!   ) B-|7 Ak"$ B7A#!  ) 1-@A!  Aj"$ Aj"$A B.7 B|B> Ak"$ B7A#!  ) B<-A!  A ~#!@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  )!  7  7 B|B> B |B> Ak"$ B7A#!  B7 Ak"$ B7A#!   )7 B|B> B |B> Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A0k"$#")!B')!B')!B SE@A!  7(  7B!A! ) B|! !  7  7 )!  7  7 Ak"$ B7A#!  )P@ Ak"$ B7A#!   ))-"PPE@A!  )PE@A!  ))-"P@ Ak"$ B7A#!  )PE@A!  )B|" )S@A!  A0j"$ Aj"$A )(B<A!  A ~#!@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B< Ak"$ B7A#!  )!B.)!B.  B.)}|7B.)! B07 B|  }7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$#")!  7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@ !  #(M@ Ak"$ B7A#!  Ak"$B.B.)7B.4!B S@A!   B.7 B|B> Ak"$ B7A"#!  B.5"BQE@A!  B! B. < E@A !  B! B. < B.)7 Ak"$ B7A#! Aj"$ Aj"$A B.1!A ! BQ!A!  Ak"$ B7A#!  Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!   B-|!  7  7 B|B7 Ak"$ B7A#!  #)0"P@ Ak"$ B7A#!  )!  7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  A!  A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B< Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$ )P@ Ak"$ B7A#!  ))@!  7 Ak"$ B7A#!  Aj"$ Aj"$A A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ ) P@ Ak"$ B7A#!  ) )! "PPE@A!   4!  B|> BBQE@A!  #P@ Ak"$ B7A#!  #1E@A!  #Bu7 B.7 B| ) 7 Ak"$ B7A#!  B(|B< Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ;  #(M@ Ak"$ B7A#!  A0k"$#")!  7 )!  7  7 B|B> B |B> Ak"$ B7A#!  )P@ Ak"$ B7A#!  ))-"BQE@A3!  )B-|!  7(  7 B|B7 Ak"$ B7A#!  ) P@ Ak"$ B7A#!  ) 1E@A.!   B'7 Ak"$ B7A#!  A!  B' 7hB'B'4B|>  )7 Ak"$ B7A#!  B|)"PPE@A(!   B7B')"PPE@A$!  "P@ Ak"$ B7A#!    7A!  B' 7A!   B'7 Ak"$ B7A#!   )(7 B|B7 Ak"$ B7A#!  ) 7 B|B> B |B> Ak"$ B7A#! A0j"$ Aj"$A BQ@A7! BQE@A9!   )B-|7 B|B7 Ak"$ B7A#!  A1!   )B-|7 B|B 7 Ak"$ B7A#!  A1!  B7 B|B+7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@ $  #(M@ Ak"$ BĖ7A#!  Ak"$#!#)0"P@ Ak"$ BĖ7A#!  )!  70 )!  7 )!  7(  Q@A!  B!  < @A!   7 Ak"$ BĖ7A#!  B|1! B|)! B|1"@A!  ) P@ Ak"$ BĖ7A#!  ) 1@A"!   <  <  78  ) 7 B| )07 Ak"$ BĖ7A#!  ) B<  )87 B| 1< B | 1< Ak"$ BĖ7A#! 1"@A! Aj"$ Aj"$A  )(7 B|B> B |B> Ak"$ BĖ7A#! A!  ) P@ Ak"$ BĖ7A#! ) B< Aj"$ Aj"$A  7 B|B> B |B> Ak"$ BĖ7A#!  )(P@ Ak"$ BĖ7A#!  )(B< ) !A!  P@ Ak"$ BĖ7A#!    B|7 Ak"$ BĖ7A#!  B|5BQ! ) ! )(! )0!A!  BϞ7 B|B7 Ak"$ BĖ7A#!   A ~#!@@@@  #(M@ Ak"$ BȖ7A#!  Ak"$#")! )!  7 B| 7 Ak"$ BȖ7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B̖7A#!  Ak"$#")! )!  ) 7 B| 7 B| 7 Ak"$ B̖7A#!  B0|B< Aj"$ Aj"$A A t#!@@@@  #(M@ Ak"$ BЖ7A#!  Ak"$ BЖ7A#!  Aj"$A A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ BԖ7A#! A8k"$#")!  70 )!  7( B)7 Ak"$ BԖ7A#! Ak"$ BԖ7A#! )B))XE@A!  B)7 Ak"$ BԖ7A#!  A8j"$ Aj"$A  Ak"$ BԖ7A#!  B.)! )!  7 Bȇ)7 B| 7 B|B< Ak"$ BԖ7A#!  B|)! )( 7B)B)) |7 Ak"$ BԖ7A#!  )0 ) ) }9 B)7 Ak"$ BԖ7A#!  A8j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@ &  #(M@ Ak"$ Bؖ7A#!  A0k"$#")!  7 )!  7( )@B|"BBBBT! )8!A !  B|!  B XE@A!!  P@ Ak"$ Bؖ7A#!  )P! )H!  TE@A$!   B|)! BPPE@A!  B!  T@A!   7  B|7 Ak"$ Bؖ7A#!  B|)"PP@A! )(! )! ) ! )8!A! ) BTE@A"!   ) B|7 B| )7 Ak"$ Bؖ7A#! B|1E@A! B| ) 7 B|B< A0j"$ Aj"$A BB!A!  B|B7 B|B< A0j"$ Aj"$A  ) 7 B|B7 Ak"$ Bؖ7A#!   7 B| 7 Ak"$ Bؖ7A#!   A #!@@@@@@   #(M@ Ak"$ Bܖ7A#!  Ak"$ B)7 Ak"$ Bܖ7A#!  Bȇ)7 Ak"$ Bܖ7A#!   B)7 Ak"$ Bܖ7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B7A#!  A(k"$#")!  7 B)7 B|B7 B|B< Ak"$ B7A#!  ) )7 A(j"$ Aj"$A A ~#!@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A8k"$#!#)0"P@ Ak"$ B7A#! 1!  < )!  70B!A!  7(  7 B.7 B| 7 Ak"$ B7A#! B)7 B| ) 7 B|B< Ak"$ B7A#!  )(B|! )0! 1"! )0! BSE@A !  E@A !  P@ Ak"$ B7A#!  1@A !  B.)"PP@A!  A8j"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#")"P@ Ak"$ B7A#!  )! 1! ) !  78 5P@A!   7 B| 7 B|B< B| < Ak"$ B7A#!  )8 B|)7 Aj"$ Aj"$A  70  7(  <'  7 B| 7 Ak"$ B7A#!  )0! )(! 1'!A!  A ~#!@@@@@@  #(M@ Ak"$ B7A#!  A(k"$#")!  7 P@ Ak"$ B7A#!  )!  7  7 Ak"$ B7A#!   ) 7 B| )7 B|B< Ak"$ B7A#!    ) 7 Ak"$ B7A#!  A(j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@    #(M@ Ak"$ B7A#!  A(k"$#")")!  )0XE@A !   )! )8 )0|B|" XE@A !   )07  7 A(j"$ Aj"$A  )8 )0|B| T@A !  )!  )0T@A !   7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   )07 Ak"$ B7A#!  BӅ7 B|B 7 Ak"$ B7A#!   )87 Ak"$ B7A#! Ak"$ B7A#! Ak"$ B7A#! ) )!  7 ) )!  7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!  )7 Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  Bи7 B|B7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$#")! )!  7 B| 7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B7A#!  A(k"$#")! )! )!  7 B| 7 B|B7 B| 7 B |B7 Ak"$ B7A#!  A(j"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B7A#!  A(k"$#")! )! )!  7 B| 7 B|B7 B| 7 B |B7 Ak"$ B7A#!  A(j"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$#)0"P@ Ak"$ B7A#!  )!  7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@ &  #(M@ Ak"$ B7A#!  Ak"$#")!  7@ )!  7H B'7 Ak"$ B7A#!  )@! )H! P@ Ak"$ B7A#!  BTE@A#!  B| B~|") )BSE@A!  B' B|")"PPE@A!  B.5P! )(! P@A!   7 B7( )! )! )! B|!  T@A!   B|7  B|!B.5PP@A!   7A!   Ak"$ B7#!A!  7X  7P B7 B| 7 B| 7 B| 7 B | 7 Ak"$ B7A#! B(|)! B0|)! )X B8|)7B.5PP@A! )X 7 )X! )P! ! ! )@! )H!A!  )X  Ak"$ B7#!A!    Ak"$ B7#! B(|B Ak"$ B7#!A!   B'7 Ak"$ B7A#!  Aj"$ Aj"$A  7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 0  #(M@ Ak"$ B7A#!  A(k"$#")! 4! BXE@A$!  B!  7  BTE@A!  BXE@A!   B|B"BTE@A.!  B' |1"BTE@A,!  B' B|3! B')!  7 B| 7 B|B< Ak"$ B7A#!  B.5P! B|)! P@A!  ) 7 A(j"$ Aj"$A )  Ak"$ B7#!A!   Bx|B"BTE@A*!  B' |1"BTE@A'!  B' B|3!A! B| T@A!  B?|B@!A! B|!A!  7 B|B7 Ak"$ B7A#!  7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ .  #(M@ Ak"$ B7A#!  A(k"$#")! )!B!B!A!  ! P@ Ak"$ B7A#!  BTE@A,!  B| B~|")! )! )B SE@A!  B|" TE@A*!   B|")!B.5PP@A!  B7 )! XE@A(!   7 PPE@A!  P@ Ak"$ B7A#!  B.5PP@A!   7(A!  B(|  Ak"$ B7#!A!  !A!  B Ak"$ B7#!A !  7  7  7 B'7 Ak"$ B7A#! )P@ Ak"$ B7A#! B.5P!B' )B|")! P@A$! ) 7(  ) 7  B'7 Ak"$ B7A#! A(j"$ Aj"$A )B(|  Ak"$ B7#!  ) Ak"$ B7#!A!   7 B| 7 Ak"$ B7A#!   7 B| 7 Ak"$ B7A#!   7 B|B7 Ak"$ B7A#!   A #!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$  B |7 Ak"$ B7A#!  )PP@A!  Aj"$ Aj"$A B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ L  !!""##$%&&'()*+,,-./0 #(M@ Ak"$ B7A#! 2 Ak"$#")!  7( )"PPE@A! 1 )HP@ Ak"$ B7A#! 1 )H)( )Q@A?! 0 )HP@ Ak"$ B7A#! 0 )H)! )H)!  7 B| 7 B|B< Ak"$ B7A#! / B|)"PPE@A>! . )(P@ Ak"$ B7A#! . )()(! )H!B!A ! , )(! ! ! PPE@A! + )! )(" T@A! *  QE@A ! ) 1E@A! ( B|B< Aj"$ Aj"$A )"P@ Ak"$ B7A#! ' 5P@A! &  !B!B!A! $ B|! P@ Ak"$ B7A#! $ 1!  BT@A!! # B WE@A! "  B~ B BT|! B|!B S@A! ! A! B WE@A!  78  7  70   B BT|> Ak"$ B7A#!  B|)"P@ Ak"$ B7A#!  B<B.5PP@A B> Ak"$ B7A#!  A8j"$ Aj"$A A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$#")!  78 )!  7( ) !  7 )(!  7@ )!  70 Ak"$ B7A#! 1E@A!  )0PP@A!   )87 B| )(7 B| ) 7 Ak"$ B7A#!  )@ B|1< Aj"$ Aj"$A B.7 B|B> Ak"$ B7A#!   )07 Ak"$ B7A#!  A!  A #!@@@@  #(M@ Ak"$ B7A#!  Ak"$ B> Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! A(k"$ B|B7 B|B7 )0!B!A!  B|! B! B WE@A!  B"B TE@A!  B| | B0|<A!  B| | B|<A!   B|7 B|B7 B|B7 Ak"$ B7A#!  A(j"$ Aj"$A A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ )1E@A !  #7#)0"P@ Ak"$ B7A#!   5P@A!  )P@ Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!   5B|> Aj"$ Aj"$A B7 Ak"$ B7A#!  A!  A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$#")! )!  7 B| 7 B|B< Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$#"5 !  > 5!  > Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!   57 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   57 Ak"$ B7A#!  Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!   A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$#"))"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!  )! )!  7 B| 7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ B7A#! A(k"$#")"P@ Ak"$ B7A#! )p!  7 )!  7 )!  7 Ak"$ B7A#! Bֽ7 B|B7 Ak"$ B7A#!  ) 7 Ak"$ B7A#! B7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  Bߔ 7 B|B7 Ak"$ B7A#!   )7 Ak"$ B7A#!  B7 B|B7 Ak"$ B7A#!  Ak"$ B7A#!  B7 B|B 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ Bė7A#! A k"$#")!  7 )!  7 )!  7 Ak"$ Bė7A#! 1! ) < E@A! B*1E@A! )PP@A!  A !  Ak"$ Bė7A#!  )P@ Ak"$ Bė7A#!  ))0"P@ Ak"$ Bė7A#!  )5 5Q@A!  B7 Ak"$ Bė7A#!  A j"$ Aj"$A A ~#!@@@@@   #(M@ Ak"$ Bȗ7A#!  Ak"$#")"P@ Ak"$ Bȗ7A#!  )0"P@ Ak"$ Bȗ7A#!  )!  7 Ak"$ Bȗ7A#!  B7 Ak"$ Bȗ7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ B̗7A#!  A k"$#")!  7 4!  > Ak"$ B̗7A#!  )P@ Ak"$ B̗7A#!  )! )! ) 7 ) 7 A j"$ Aj"$A A ~#!@@@@@@@@@   #(M@ Ak"$ BЗ7A#!  A0k"$#")! 4! ) ! )(! ))!  7 B| 7 B| > B| 7 B | 7 Ak"$ BЗ7A#!  #)0"P@ Ak"$ BЗ7A#!  )(! )!  7 B| 7 B|B< Ak"$ BЗ7A#!  B.1@A !  A0j"$ Aj"$A Ak"$ BЗ7A#!  A!  A ~#!@@@@  #(M@ Ak"$ Bԗ7A#!  A k"$#")!  7 B> Ak"$ Bԗ7A#!  )P@ Ak"$ Bԗ7A#!  )! )! ) 7 ) 7 A j"$ Aj"$A A ~#!@@@@@@@@@@@@@  #(M@ Ak"$ Bؗ7A#! Ak"$#")!B!A!  B$| B|)!B*B*)B*)}7 P@ Ak"$ Bؗ7A#! B*)7B* 7 B|! P@ Ak"$ Bؗ7A#!  )$SE@A ! BT@A!  A!   7 B7$ B)7 Ak"$ Bؗ7A#!   )B|7 B|Bȇ)7 Ak"$ Bؗ7A#!   B)7 Ak"$ Bؗ7A#!  Aj"$ Aj"$A  7 B|B7 Ak"$ Bؗ7A#!   A ~#!@@@@@@@@@@@  #(M@ Ak"$ Bܗ7A#! Ak"$#")!  7 P@ Ak"$ Bܗ7A#!   7 Ak"$ Bܗ7A#! )5"PPE@A!  ) B|>  )7 Ak"$ Bܗ7A#!  Aj"$ Aj"$A #)0!  7 P@ Ak"$ Bܗ7A#!   ))7 ) 7  )7 Ak"$ Bܗ7A#!   )B|7 Ak"$ Bܗ7A#!   )B7A!  A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#")! )! ) ! )@! )! )0"P! )(! PE@A!   7X  7 B| 7 B|B7 B| 7 B | 7 B(| 7 B0| 7 B8|B7 B|B7 B|B7 Ak"$ B7A#!  )X B|)7 Aj"$ Aj"$A B7 B| 7 Ak"$ B7A#!   A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! #")"P@ Ak"$ B7A#! )! )0 )Q@A!  )( )Q@A!   )QE@A!  B|B!< Aj"$A B|B< Aj"$A B|B<< Aj"$A B|B>< Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 4  #(M@ Ak"$ B7A#!  A(k"$B.D9B.5P!B')! P@A2!  B' 7 B.5P!B')! P@A0!  B' 7 B.5P!B')! P@A.!  B' 7 B.5P!B')! P@A,!  B' 7 B.5P!B')! P@A)!  B' 7 Ak"$ B7A#!  B.5P! )! P@A'!  B' 7 Ak"$ B7A#!  B.5P! )! P@A$!  B' 7 B.5P!B')! P@A!! B' 7 B|B7 B |B7 B |)"P@ Ak"$ B7A#! B. )7 B|B7 B|B؉7 B|)"P@ Ak"$ B7A#! B. )7 A(j"$ Aj"$A B'  Ak"$ B7#!A!  B'  Ak"$ B7#!A!  B'  Ak"$ B7#!A!  B'  Ak"$ B7#!A !  B'  Ak"$ B7#!A !  B'  Ak"$ B7#!A !  B'  Ak"$ B7#!A!  B'  Ak"$ B7#!A!  A #!@@@@@@@@@  #(M@ Ak"$ B7A#!  A(k"$ B8|B> B|B7 B|B7 B |B7 B|B7 B| B8|7 B | B0|7  B|7 Ak"$ B7A#!  B0|4BS@A!  A(j"$ Aj"$A B.7 Ak"$ B7A#!   B|5> Ak"$ B7A#!  A!  A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$ B.7 Ak"$ B7A#!  ) BS! B|)!B ) !B SE@A !  #)0"P@ Ak"$ B7A#!   5! 5!  > B ! B BB  !  >  |B P@A !  Aj"$ Aj"$A  7 B| )(B|7 B|B7 Ak"$ B7A#!  A !  A v#!@@@@  Ak"$  )7 B| B|7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ J  A(k"$#)0"P@ Ak"$ B7A#!  4B|>#P@ Ak"$ B7A#! #)p" B0|T@A!  #7#B7#)0"P@ Ak"$ B7A#!  )! !  7 B7  7 Ak"$ B7A#!  B|1@A&!  )B7B*1E@A!  )! ) !A!  Ak"$ B7A#!  )! ) ! PPE@A!  )0"P@ Ak"$ B7A#!  5 5Q@A!   Ak"$ B7A#!  ) )7 ))0"P@ Ak"$ B7A#!   4B|> B7 Ak"$ B7A#!  )B7p ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!    5B|> )B< A(j"$ Aj"$A B*1E@A/!  ))0"P@ Ak"$ B7A#!  )! ! ) RE@A!  B7 Ak"$ B7A#!  ))0"P@ Ak"$ B7A#!  )"P@ Ak"$ B7A#!    5B|>  )7 B|B> B |B> Ak"$ B7A#!  )B7p ))0"P@ Ak"$ B7A#!   4B|> )1E@A! )Bu7 )B<B'1@A=! A(j"$ Aj"$A   )7 B|B< Ak"$ B7A#! B|1@A Ak"$ B7A"#! ) A j"$ Aj"$A B.1@A! ' A! & )0BQE@A! & )(1BQE@A! % )(1"BQE@A! $ )(1BQE@A! # B!A! !  )(7 B| )07 Ak"$ B7A#! ! B|)! B|1E@A! B QE@A!  BB!A!  B!A!  )0BQE@A!  )(1BQE@A!  )(1BQE@A!  )(1BQE@A!  )(1BQE@A!  B!A!  )0BQE@A !  )(1BQE@A!  )(1BQE@A!  )(1BQE@A!  )(1BQE@A!  )(1BQE@A!  B !A!  )0BQE@A!  )(1BQ"E@A'!  )(1BQE@A'! )(1BQE@A'! )(1BQE@A'! )(1BQE@A'! )(1BQ@A! E@A!  )(1BQE@A!  )(1BQE@A!  )(1BQE@A!  )(1BQE@A!  )(1BQE@A!  B !A!  A ~#!@@@@@@@@@@@@   #(M@ Ak"$ B7A#! Ak"$  )P7 Ak"$ B7A#! )"P@A!  78  7 Ak"$ B7A#!  B|)! B |)"P! B|)! B|)! PE@A!   7@  70  7 B| )P7 B| )87 Ak"$ B7A#!  B| )@7 B| )07 Aj"$ Aj"$A B|B7 B|B7 Aj"$ Aj"$A B7 B| 7 Ak"$ B7A#!   A ~#!@@@@@   #(M@ Ak"$ B7A#!  A k"$ Ak"$ B7A#!   )!  7 4!  > Ak"$ B7A#!  )! B(| )7 B0| 4> B8| 7 A j"$ Aj"$A A t#!@@@  A k"$ #7 #7 #7 #7 Ak"$ B7A#!  A j"$ Aj"$A A #!@@@  A(B'7A'B(70B'$ Ak"$ B7A#!@ Ak"$ B7A#!@ Ak"$ B7A#!@ Ak"$ B7A#!@ B7 B 7 Ak"$ B7A#!@ Ak"$ B7A#!  A #! B< Aj"$A \~#! )")$ )"$ Ak )7 ) $ )$ B7 B7 B7A ~#!@@@  )$#)0")!# )7@# B|78##7H# Q@A#! )8B}"$ #7 $#) Ak"$ B7Av!A #!  Aj"$A#! A ~#!@@@  )!#)0")!# Q@ $#)Av!A #! # )R@ Ak"$ B7A#!@ #B7@# 78##7H $ )8B}"B7 "$ $#) Ak"$ B7Av!A #!  #)0")"$ )8"$ B78 Aj"$A A #! Aj"$A #!A#! #!A#! #!A#! l#!@ )$#P@ Ak"$ B7A#!  )B}"$  /Ak;#)Av!A #! A #! Aj"$A #! Aj"$A #! Aj"$A ~#!@@@  #)0")!# Q@ Ak"$ B7A#!@ # )PQ@ Ak"$ B7A#!@  )7  B|7 #7# )7@##7H# B|78##7P $ )8"$ Ak"$ B7A#!  A #!B$A#! #! ~#!@ )P@ Ak"$ B̘7A#!  5 "BX@A#! B X@A#! BX@A#! BX@A#! BX@A#! BX@A#! BX@A#! BX@A#! B X@A#! BX@A#! BX@A#! BX@A#! BX@A#! BX@A#! BX@A#! B X@A#! BX@A#! BX@A#! BX@A#! BX@A#! BX@A#! BX@A#! B X@A#! BX@A#! BX@A#! BX@A#! BX@A#! A#! A ~#!@@@@  #(M@ Ak"$ BИ7A#!  Ak"$#) "PE@ B| )Q@  7 50"PE@  )( )0B#! ) $#) Ak"$ BИ7Av!A #!  54! )$ )( |$  |$ 50 }$ Ak"$ BИ7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ BԘ7A#!  A k"$#) "PE@ B(| )Q@  7 5@"PE@  )8 )@B#! )0$#) Ak"$ BԘ7Av!A #!  5D! )($ )8 |$  |$ 5@ }$ Ak"$ BԘ7A#!  A j"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ Bؘ7A#!  Ak"$#) "PE@ B| )Q@  7 5`"PE@  )X )`B#! )P$#) Ak"$ Bؘ7Av!A #!  5d! )H$ )X |$  |$ 5` }$ Ak"$ Bؘ7A#!  Aj"$ Aj"$A A ~#!@@@@  #(M@ Ak"$ Bܘ7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ Bܘ7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ Bܘ7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  A k"$#) "PE@ B | )Q@  7 5 "PE@  ) ) B#! ) $#) Ak"$ B7Av!A #!  5 ! ) $ ) |$  |$ 5 }$ Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@  #(A?jM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5@"PE@  )@ )@B#! )@$#) Ak"$ B7Av!A #!  5@! )@$ )@ |$  |$ 5@ }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  A k"$#) "PE@ B | )Q@  7 5 "PE@  ) ) B#! ) $#) Ak"$ B7Av!A #!  5 ! ) $ ) |$  |$ 5 }$ Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@  #(A?jM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5@"PE@  )@ )@B#! )@$#) Ak"$ B7Av!A #!  5@! )@$ )@ |$  |$ 5@ }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  A k"$#) "PE@ B | )Q@  7 5 "PE@  ) ) B#! ) $#) Ak"$ B7Av!A #!  5 ! ) $ ) |$  |$ 5 }$ Ak"$ B7A#!  A j"$ Aj"$A A ~#!@@@@  #(A?jM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5@"PE@  )@ )@B#! )@$#) Ak"$ B7Av!A #!  5@! )@$ )@ |$  |$ 5@ }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@  #(AjM@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 5"PE@  ) )B#! )$#) Ak"$ B7Av!A #!  5! )$ ) |$  |$ 5 }$ Ak"$ B7A#!  Aj"$ Aj"$A A ;#!@@@  Ak"$ B7A#!  A ~#Ak$#)0")")-" 7  )7 B|!  7-  )-Q@# 7# 7#Ak$#B7A@ 7#Aj$#Aj$A G~#! )! )!@ P@ Aj"$A B< B|! B}! ~#! )! )! )!  T@@@ BT   )7 B|! B|! B}! @ P@ Aj"$A  1< B|! B|! B}!   |!  |!@@ BT  B}! B}! B}!  )7 @ P@ Aj"$A B}! B}! B}!  1< #! AԙAؙA $Bн/$# 7# 7A A *#E@@#Ak/#Ak/#E A$ # ##! )"$A$ Aj"$A ##!A#!A$ Aj"$A -@ )7 Ak"E@ Aj! Aj! #@ B7 Ak"E@ Aj! . BQ@ BQ@B  Q b@B DCd@B Dc@B  Q b@B DCd@B Dc@B  #! #! #! Aj"$A #! ?6 Aj"$A !#!  (@6 Aj"$A #! #! Aj"$A #! #! Aj"$A #! #! Aj"$A #! #! Aj"$A #! #! Aj"$A #! #! Aj"$A #! #! Aj"$A #! #! Aj"$A ~#!@@@@  #(M@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 #")!  7 B| )7 Ak"$ B7A#!  Aj"$ Aj"$A A ~#!@@@@@@@@   #(M@ Ak"$ B7A#!  A8k"$#) "PE@ B| )Q@  7 B|)"PPE@A!  )! )! B7 B|Bј7 B|B7 B| 7 B | 7 Ak"$ B7A#!  B(|)! B0|)! B| 7 B| 7 A8j"$ Aj"$A Ak"$ B7A#!   A ~#!@@@@@@@@@@@   #(M@ Ak"$ B7A#! A k"$ )() )0)QE@A! )()! )0)! )0)! )()!  Q@A!  B! B8| < A j"$ Aj"$A  7 B| 7 B| 7 Ak"$ B7A<#!  B|1E@A!   )(B|7 B| )0B|7 B|B7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 B|B7 Ak"$ B7A*#!  1@A!  B! B8| < A j"$ Aj"$A  )(B|7 B| )0B|7 B|B7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@  #(M@ Ak"$ BĚ7A#!  )5 )5QE@A!  )) ))QE@A!  )5 )5Q@A!  B|B< Aj"$A ))! ))! B|  Q< Aj"$A A ~#!@@@@  #(M@ Ak"$ BȚ7A#!  A k"$  )(7 B| )07 B|B 7 Ak"$ BȚ7A*#!  1! B8| < A j"$ Aj"$A A ~#!@@@@@@@@@  #(M@ Ak"$ B̚7A#!  A k"$  )(7 B| )07 B|B7 Ak"$ B̚7A*#!  1@A!  B! B8| < A j"$ Aj"$A  )(B|7 B| )0B|7 B|B 7 Ak"$ B̚7A*#!  B|1!A!  A ~#!@@@@@@@  #(M@ Ak"$ BК7A#!  )) ))QE@A!  )1 )1Q@A!  B|B< Aj"$A ))! ))! B|  Q< Aj"$A A ~#!@@@@  #(M@ Ak"$ BԚ7A#!  A k"$  )(7 B| )07 B|B 7 Ak"$ BԚ7A*#!  1! B8| < A j"$ Aj"$A A ~#!@@@@@@  #(M@ Ak"$ Bؚ7A#!  )) ))QE@A!  )4! )4! B| B BQ< Aj"$A B|B< Aj"$A A ~#!@@@@  #(M@ Ak"$ Bܚ7A#!  A k"$  )(7 B| )07 B|B!7 Ak"$ Bܚ7A*#!  1! B8| < A j"$ Aj"$A A ~#!@@@@@@@@@@@@@@@  #(M@ Ak"$ B7A#!  Ak"$#) "PE@ B| )Q@  7 B|)"PPE@A! )! PE@A! B!B! B | 7 B | 7 Aj"$ Aj"$A BQE@A ! B!B!A!  B')!B')!  TE@A!   B|")! )!A!   7 B| 7 Ak"$ B7A#!  Ak"$ B7A#!   A ~#!@@@@@@@@@@   #(M@ Ak"$ B7A#! #) "PE@ B| )Q@  7 B|)"PPE@A !  1!B XE@A!  B!B! B| 7 B| 7 Aj"$A B' B|")! )!A!  Ak"$ B7A#!   A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 B|B67 Ak"$ B7A*#!  1@A!  B! B8| < A j"$ Aj"$A  )(B8|7 B| )0B8|7 B|B 7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 B|B7 Ak"$ B7A*#!  1@A!  B! B8| < A j"$ Aj"$A  )(B|7 B| )0B|7 B|B7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ )0! )8!B!A! B|! BSE@A! B!  |)  |)Q@A! B! B| < A(j"$ Aj"$A B!A !  ) B|! )0! )8! BSE@A !   7 B!  |")!  |)! )!  7 B| 7 B| 7 Ak"$ B7A*#!  B|1@A!  A!  B!A!  A ~#!@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ )0! )8!B!A! B|! B SE@A! B!  |)  |)Q@A! B! B| < A(j"$ Aj"$A B!A !  ) B|! )0! )8! B SE@A !   7 B!  |")!  |)! )!  7 B| 7 B| 7 Ak"$ B7A*#!  B|1@A!  A!  B!A!  A ~#!@@@@@@  #(M@ Ak"$ B7A#!  )4! )4B BQE@A!  ))! ))! B|  Q< Aj"$A B|B< Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ )() )0)Q@A!  B! B8| < A j"$ Aj"$A  )(7 B| )07 B|B7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ )(1+ )01+Q@A!  B! B8| < A j"$ Aj"$A  )(7 B| )07 B|B)7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@@@@@   #(M@ Ak"$ B7A#! A k"$ )0)! )()! )0)! )()!  QE@A! )() )0)QE@A! )() )0) Q@A!  B! B8| < A j"$ Aj"$A  7 B| 7 B| 7 Ak"$ B7A*#!  B|1E@A!  )0)! )()! )()!  7 B| 7 B| 7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 B|B7 Ak"$ B7A*#!  1@A!  B! B8| < A j"$ Aj"$A  )(B|7 B| )0B|7 B|B87 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  )) ))QE@A!  )1 )1Q@A!  B|B< Aj"$A ))! ))! B|  Q< Aj"$A A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$ )(1 )01Q@A!  B8|B< A j"$ Aj"$A  )(B|7 B| )0B|7 B|B7 Ak"$ B7A*#!  B|1E@A!   )(B|7 B| )0B|7 B|B7 Ak"$ B7A*#!  B|1! B8| < A j"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 Ak"$ B7A#!  1E@A!   )(B|7 B| )0B|7 B|B7 Ak"$ B7A*#!  B|1! B8| < A j"$ Aj"$A B8|B< A j"$ Aj"$A A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$B!A!  )B|! BSE@A!   7 B~!  )( |7 B| )0 |7 Ak"$ B7A#!  B|1@A!  B8|B< A j"$ Aj"$A B8|B< A j"$ Aj"$A A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$ )() )0)QE@A!  )(3 )03QE@A!  )(1 )01 Q@A!  B! B8| < A j"$ Aj"$A  )(B|7 B| )0B|7 B|B 7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@@  #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 B|B7 Ak"$ B7A*#!  1@A!  B! B8| < A j"$ Aj"$A  )(B|7 B| )0B|7 B|B7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@@@@@@@@@@   #(M@ Ak"$ B7A#! A(k"$ )8) )0)QE@A! )8) )0)QE@A! B!A!  ) B|! BSE@A !   7 B! )0 |")! )8 |)! )!  7 B| 7 B| 7 Ak"$ B7A*#!  B|1@A!  B! B| < A(j"$ Aj"$A B!A !  A ~#!@@@@@@@@@@@@@@@   #(M@ Ak"$ B7A#!  A(k"$ )0! )8!B!A! B|! BSE@A! B!  |)  |)Q@A! B! B| < A(j"$ Aj"$A B!A !  ) B|! )0! )8! BSE@A !   7 B!  |")!  |)! )!  7 B| 7 B| 7 Ak"$ B7A*#!  B|1@A!  A!  B!A!  A ~#!@@@@@@@@@@  #(M@ Ak"$ B7A#! A k"$ )() )0) Q@A!  B! B8| < A j"$ Aj"$A  )(7 B| )07 B|B7 Ak"$ B7A*#!  B|1E@A!  )0)! )()! )() !  7 B| 7 B| 7 Ak"$ B7A*#!  B|1!A!  A ~#!@@@@  #(M@ Ak"$ B7A#!  A k"$  )(7 B| )07 B|B7 Ak"$ B7A*#!  1! B8| < A j"$ Aj"$A A ~#!@@@@@@@@  #(M@ Ak"$ B7A#!  A(k"$#) "PE@ B0| )Q@  7 B0|)"PPE@A!  )! )! 1! 1!  7 B| 7 B| < B| < Ak"$ B7A#!  B|)! B |)! B8| 7 B| 7 A(j"$ Aj"$A Ak"$ B7A#!   A ~#!@@@@@@  #(M@ Ak"$ B7A#!  )) ))QE@A!  )1! )1! B|  Q< Aj"$A B|B< Aj"$A A ~#!@@@@@@@  #(M@ Ak"$ B7A#!  #) "PE@ B| )Q@  7 B|)"PPE@A!  )! )! B| 7 B| 7 Aj"$A Ak"$ B7A#!   A Q#!@@@  #(M@ Ak"$ B7A#!  Aj"$A A A BFR_abcdefghikmnpstxy.Fbpfdfnfpg0gphiidinl1l2lolrmpokotpcppr1r2rdrgrtrwscspt1t2wdwgwt_g_addargbadbssbufcasdstenderrfungcwgengetkeylenmOSmaxnowobjoffoutpadpcspopptrputretseqsetsigsizsrcstkstrsubtagtlstypval*intaddrargpargsbasebitpbptrcbufcodecritctxtcurgdatadiffdownebsselemfilefindfintfreeftabfullgoidgopcgrowhashheadheapinititypkeyskindlastleftlinklistloadlockmaskmhdrmoremsgsnamencgonewgnextnobjnodenretnstkoldppad1pad2parkpclnpcspprevptabpushrankrootrseqrunqscavselfsizeskipstattailtesttexttinyuservarpwhenwseqzeroA /fij\]kP`Oc^lnW[_TUdaShRgbZQVe*bool*int8*uintError_Func_func_typealignallocarenaboundbytepbytescachechunkclearcountcutabdatapdyingedataelemsemptyentryequaletexteverrextrafind1firstflagsflushframefree1freemgFreegListgcbsshash0inUseincgoindexinterinuselimitlocksmagicmaxpcminLCminpcnextpnfuncnobjspagesparampcbufpctabrLockrecvqrecvxresetrightrlockschedsendqsendxsigpcspansspinesplitstackstartstatesweeptflagtimertypesvaddrvalidwLockwbBufwbuf1wbuf2*error*int16*int32*int64*uint8String_defer_panicallocNarenasarglenargmapbitmapbucketchunkscloseddivMuldivmodetypesfuncIDgcdatagetPtrgoexitinListinsertisFreelabelslayoutlengthlinenomFixupmappedmcachenchunknelemsnfilesnoscannpagesoffsetpallocparentpcachepcfileperiodprocidptrbitputPtrqcountrangesrefillremovesetAllsignedstatusstringtagLentickettimerstryGetunlockunpackunusedupdatevdsoPCvdsoSPwriterruntime*func()*string*uint16*uint32*uint64abortedadvancealllinkallnextbalanceblock64blockedbucketscentralcgoCtxtchunkOfclosingcontextdecHeaddequeuedestroydiscarddisposedocrashenqueueentriesfiletabframepcfreeAllfreeBuffreeHWMfuncoffgrowinggsignalhasmainincHeadincTailisBlankisEmptylibcalllockedglockedmmakeArgmorebufnameLennameOffnameoffnpcdatapages64partialpkgPathpkgpathpreemptptrSizeptrdatapushAllpushcntputFastracectxreadersreclaimrestartrunlockrunnextpop scavLWMsetTypesigmasksortkeyspecialstartedstartpcsuccesssummarysysGrowsysInitsysStatsyscalltakeAlltextOfftopbitstophashtypeOfftypemapwaitingwrapped*[]int32*[]uint8*float32*float64*uintptrallocAllallspansassertedbaseMaskbaseaddrbytedataclearAllconcretecontainscontinpccuOffsetcurArenadataqsizdeadlinedivShiftdlogPerMdoesParkelemsizeelemtypefastrandfindSuccflushGenfreeSpanfreeWaitfreelinkfuncNameisMarkedisSelectlessThanlibcallglockAddrmcentralminPagesmstartfnncgocallneedzeronextFreenextwhennoptrbssobjIndexoverflowpcHeaderputBatchraceaddrreleasedreturnedrunqheadrunqtailpush scanWorkscavengesetRangesetSpanssigcode0sigcode1specialsspineCapspineLenspinningstatsSeqstktopspsubtractsudogbufsweeperssweepgensysAlloctargetpcthrowingtracebuftracesequncommonwaitlinkwaitlockwaittailwritebuf *[2]uint8 *[4]uint8 *[8]int32 *[8]uint8 *[]string *[]uint32 *[]uint64 *chan int addObject allArenas allocBits allocSpan ancestors cacheSpan caughtsig cloneInto deferpool divShift2 enoptrbss firstFree freeindex fullSwept gcbssmask goidcache itablinks lessEqual libcallpc libcallsp lockedExt lockedInt locksHeld mallocing mheapLock nevacuate nextwaitm nfuncdata noptrdata noverflow numTimers openDefer pageInUse pageMarks pclntable pkghashes prevDefer printlock profilehz ptrToThis recoveredempty scanAlloc scavenged schedlink schedtick schedwhen setMarked sizeclass spanalloc spanclass spineLock stackLock stacksize startAddr summarize sweepdone syscallpc syscallsp traceback traceskip typelinks waitsince *[24]uint8 *[2]string *[2]uint32 *[4]string *[64]uint8 *[6]string *[8]uint32 *[9]string *[]uintptr *chan bool *complex64 *runtime.g *runtime.m *runtime.p *struct {} _interface allocCache allocCount allocLarge allocRange arenaHints buildIndex cachealloc cgoCallers checkempty checkmarks clearRange countAlloc enoptrdata fieldAlign findLargeN findObject findSmallN freeManual gcdatamask gcmarkBits gcscandone goSigStack insertBack isExported markArenas modulename mspancache needextram nextSample oldbuckets pagesInUse pagesSwept pallocBits pclnOffset pluginpath preemptGen preemptoff raceignore readerPass readerWait releaseAll removeLast searchAddr selectDone sigContext stackcache sudogcache sysmontick throwsplit timer0When timersLock tinyAllocs tinyoffset totalBytes traceSweep traceSwept tracelastp tryChunkOf tryGetFast waitreason workbufhdr zeroedBase **runtime.m *[1]uintptr *[6]uintptr *complex128 acquiretime allocManual bytesMarked checkBucket clearMarked createstack deferreturn ensureSwept findfunctab flushedWork fullUnswept funcnametab newSigstack newoverflow noldbuckets oldoverflow pctabOffset popcntRange preemptStop raceprocctx readerCount releasetimepushAll runtimehash scavengeAll scavengeOne setoverflow speciallock stackguard0 stackguard1 startBucket sweepArenas syscalltick syscallwhen textsectmap uncacheSpan waittraceev waitunlockf internal/cpu *[32]uintptr *[]struct {} *runtime.mOS *sys.Uintreg RuntimeError adjustTimers allocToCache atomicstatus conservative deferpoolbuf dequeueSudoG gcAssistTime goidcacheend linktimehash locksHeldLen modulehashes nextOverflow pageSpecials paniconfault partialSwept reclaimChunk reclaimIndex sameSizeGrow scavengeGoal sysexitticks timerRaceCtx"A "A "@A¦ "AҦ "A '"""A "A "A "A§ "Aҧ "A " A "(A "A "A "0A "8A¨ "@AҨ ""PA " A "XA  *[252]uintptr *[253]uintptr *[512]uintptr *[8]struct {} *[]*runtime.g *interface {} *runtime.bmap *runtime.hmap *runtime.itab *runtime.name *runtime.note cgoCallersUse checknonempty deletedTimers filetabOffset gcAssistBytes incrnoverflow missingMethod nextFreeIndex oldbucketmask parkingOnChan preemptShrink reclaimCredit reportZombies signalPending startingtrace tryAllocMSpan waittraceskip**runtime.note*[131072]uint8*[8]*runtime.g*func() string*runtime._func*runtime._type*runtime.event*runtime.gList*runtime.gobuf*runtime.hchan*runtime.mheap*runtime.mlink*runtime.mspan*runtime.mutex*runtime.stack*runtime.sudog*runtime.tflag*runtime.timer*runtime.waitq*runtime.wbBufallocNeedsZeroarenaHintAllocasyncSafePointcreateOverflowfindMappedAddrfreeSpanLockedfuncnameOffsetheapArenaAlloclockRankStructmanualFreeListpartialUnsweptrunSafePointFnsysblocktracedtraceReclaimed**runtime.mspan*runtime._defer*runtime._panic*runtime.bucket*runtime.funcID*runtime.gcBits*runtime.gcWork*runtime.lfnode*runtime.mcache*runtime.sigset*unsafe.PointerfreeMSpanLockedmarkBitsForBasepagesSweptBasisprepareForSweepscavengeReservestackWorkBufHdr**runtime._defer**runtime.mcache*[]*runtime.bmap*[]*runtime.itab*[]*runtime.note*func(bool) bool*runtime.evacDst*runtime.functab*runtime.funcval*runtime.imethod*runtime.libcall*runtime.nameOff*runtime.offAddr*runtime.ptrtype*runtime.rwmutex*runtime.spanSet*runtime.special*runtime.typeOff*runtime.workbufactiveStackChansallocMSpanLockedgcMarkWorkerModemarkBitsForIndexnextSpanForSweeprefillAllocCachereservationBytesscavengeStartGen**runtime.funcval*[8]*runtime.note*[]*runtime._type*[]*runtime.mspan*[]*runtime.sudog*[]*runtime.timer*[]unsafe.Pointer*cpu.CacheLinePad*runtime.arenaIdx*runtime.chunkIdx*runtime.dlogPerM*runtime.fixalloc*runtime.funcInfo*runtime.guintptr*runtime.lockRank*runtime.mapextra*runtime.markBits*runtime.mcentral*runtime.muintptr*runtime.pageBits*runtime.pcHeader*runtime.pollDesc*runtime.puintptr*runtime.stkframe*runtime.stringer*runtime.textsectallocBitsForIndexscavengeUnreservestackObjectBufHdrsweepPagesPerByte*[8]*runtime._type*[8]unsafe.Pointer*[]*runtime._defer*[]runtime.evacDst*[]runtime.functab*[]runtime.imethod*[]runtime.spanSet*[]runtime.typeOff*runtime._typePair*runtime.addrRange*runtime.arenaHint*runtime.bitvector*runtime.gclinkptr*runtime.heapArena*runtime.mSpanList*runtime.notInHeap*runtime.pageAlloc*runtime.pageCache*runtime.pallocSum*runtime.ptabEntry*runtime.spanClassremoveGreaterEqualsetMarkedNonAtomicsweepHeapLiveBasis**runtime.notInHeap*[2]runtime.evacDst*[2]runtime.spanSet*[512]*runtime.itab*[8]runtime.typeOff*[][]*runtime._type*[]runtime.arenaIdx*[]runtime.guintptr*[]runtime.textsect*runtime.addrRanges*runtime.bucketType*runtime.cgoCallers*runtime.mSpanState*runtime.moduledata*runtime.modulehash*runtime.pallocBits*runtime.pallocData*runtime.plainError*runtime.sysMemStat*runtime.sysmontick*runtime.waitReason*runtime.workbufhdrscavengeRangeLockedspecialprofileallocA runtime/internal/sys*[128]*runtime.mspan*[128]*runtime.sudog*[136]*runtime.mspan*[32]*runtime._defer*[512]*runtime.mspan*[8][]*runtime._type*[][]*runtime._defer*[]runtime._typePair*[]runtime.addrRange*[]runtime.pallocSum*[]runtime.ptabEntry*runtime.boundsError*runtime.errorString*runtime.linearAlloc*runtime.stackObject*runtime.traceBufPtrfindAddrGreaterEqualgcFractionalMarkTimehasScavengeCandidate*[5][]*runtime._defer*[8]runtime._typePair*[]*runtime.heapArena*[]runtime.modulehash*[]runtime.pallocData*runtime.ancestorInfo*runtime.gsignalStack*runtime.heldLockInfo*runtime.pcvalueCache*runtime.stackWorkBuf*runtime.stringStructfindScavengeCandidategcMarkWorkerStartTimespecialfinalizeralloctimerModifiedEarliest**runtime.stackWorkBuf*[256]runtime.guintptr*[]*runtime.moduledata*[][32]*runtime._defer*[][]runtime.pallocSum*[]runtime.stackObject*runtime.checkmarksMap*runtime.headTailIndex*runtime.interfacetype*runtime.itabTableType*runtime.mSpanStateBox*runtime.stackfreelist*[4][]runtime.pallocSum*[5][32]*runtime._defer*[]runtime.ancestorInfo*[]runtime.heldLockInfo*runtime.cgothreadstart*runtime.lockRankStruct*runtime.stackObjectBuf*runtime.stackScanState*[63]runtime.stackObject*[]runtime.stackfreelist*runtime.boundsErrorCode*runtime.cgoTracebackArg*runtime.noteWithTimeout*runtime.pcvalueCacheEnt*runtime.persistentAlloc*runtime.stackWorkBufHdr*[1024]*runtime.heapArena*[1024]runtime.pallocData*[10]runtime.heldLockInfo*[4]runtime.stackfreelist*[]**runtime.stackWorkBuf*map[int32]unsafe.Pointer*runtime.cgoSymbolizerArg*runtime.gcMarkWorkerMode*runtime.specialfinalizer*[2]**runtime.stackWorkBuf*[]runtime.noteWithTimeout*[]runtime.pcvalueCacheEnt*runtime.sliceInterfacePtr*runtime.stackObjectBufHdr*runtime.stackObjectRecord*[8]runtime.noteWithTimeout*[8]runtime.pcvalueCacheEnt*runtime.TypeAssertionError*runtime.gcBgMarkWorkerNode*runtime.stringInterfacePtr*runtime.uint16InterfacePtr*runtime.uint32InterfacePtr*runtime.uint64InterfacePtr*[]*[1024]*runtime.heapArena*[]*[1024]runtime.pallocData*[]runtime.stackObjectRecord*func(interface {}, uintptr)*map[uint32][]*runtime._typeA *[1]*[1024]*runtime.heapArena*[1]*[1024]runtime.pallocData*[][8]runtime.pcvalueCacheEnt*map[*runtime.note]*runtime.g*[2][8]runtime.pcvalueCacheEnt*runtime.neverCallThisFunction*struct { F uintptr; s string } *map.bucket[int32]unsafe.Pointer *map.hdr[uint32][]*runtime._type *map[runtime._typePair]struct {}!*struct { F uintptr; now *int64 }"*struct { runtime.gList; n int32 }#*map.bucket[uint32][]*runtime._type#*map[runtime.typeOff]*runtime._type$*func(runtime.neverCallThisFunction)$*map.bucket[*runtime.note]*runtime.g$*map.hdr[runtime._typePair]struct {}$*struct { F uintptr; gp *runtime.g }$*struct { F uintptr; pp *runtime.p }$*struct { F uintptr; restart *bool }%*func(unsafe.Pointer, unsafe.Pointer)%*struct { F uintptr; _g_ *runtime.g }%*struct { base uintptr; end uintptr }&*func(*runtime.g, unsafe.Pointer) bool'*map.bucket[runtime._typePair]struct {}'*struct { F uintptr; c *runtime.hchan }(*struct { F uintptr; freem **runtime.m }(*struct { F uintptr; s **runtime.mspan })*struct { F uintptr; c **runtime.mcache }**func(unsafe.Pointer, unsafe.Pointer) bool**map.bucket[runtime.typeOff]*runtime._type**map[*runtime.note]runtime.noteWithTimeout**struct { F uintptr; rw *runtime.rwmutex },*struct { F uintptr; out *int32; in *int32 },*struct { len int; buf [128]*runtime.mspan }.*map.hdr[*runtime.note]runtime.noteWithTimeout/*map.iter[*runtime.note]runtime.noteWithTimeout/*struct { F uintptr; R *runtime.itabTableType }0*struct { F uintptr; pp *runtime.p; sc uintptr }0*struct { F uintptr; src uintptr; dst *uintptr }1*map.bucket[*runtime.note]runtime.noteWithTimeout2*struct { lock runtime.mutex; fn func(bool) bool }3*struct { F uintptr; gp *runtime.g; pp *runtime.p }3*struct { F uintptr; gp *runtime.g; traceskip int }4*struct { F uintptr; gp *runtime.g; scanWork int64 }4*struct { F uintptr; siz int32; d **runtime._defer }4*struct { mcentral runtime.mcentral; pad [24]uint8 }A 2}'r6(" {A 2H_6($A 2cBF6( A 3>T6(" A 36(r!LA 2r,B6(( A 2|6(0`A 2~rw6(H.`A 2Yu6(1A 2A616(81A 26("`A 2p6(/A 2 n6(A 2+#6(4A 2jz6(A 2ܮX6( A 2Q6(f A 2V6(q @A 2"%6(A 2f6(@A 2&Xk6(A 2&6(A 2_6(A 2Z.B6(4%A 3k6(e6A 3v6(:@A 39A6(<5A 3ȓQ6(7A 36Y6(8A 3A6(5@A 3tR6(9HA 3ä6(!@<`ZA 3vu6(t[A 36(!`EA 2ˠ6('@A 3m6(X-@A A #@[T'A 2nS6(z0@A 2jr6()A 2I6()A 3,6(1?A 3=36(0%A 3 /6(/A A ")LWT.@A 2oM6(p%A 3L(6(#%A 2K6(A 26(!A 2kR6()# A 20(66(<#<A 3w6(sA 3]b6( 3A 2_46(1@A 3~mp6(@A 2g6(p-`A A #T(BA 2 W6(-A 2oc6(O#`A 3xY6(/@A 26(!A 36S6(b#;A 26(/A 3M76(<wA 2`6( A 2qV6(*A 3k6(%A A " WT`A 3L\n6(* jA 3M0 6(,*A 2آq6( @A 2: .6(u#A 26(4A 2 %6(A 2/6(0A 3im6(8&`]A 2W)6(-`A 3jh6(0A 2KD]6(0 A 2$]Y6((A A ! 06(#`A 2M_6("A 2G6(o2A A #3q6T#@A A #!.T13A 2iR6( A A " T0A A "KVT2@A 2\k=6(2A A "T->A A "#CYT1 A 26([/A 38(QM6(t&SA 2R6(>3A 2(ά6(-`A 2-6(&A 3?6(/ ^A 3Ď66(*&A 3S \6(&A 2z36(@ A 3{o6(Q GA 2 6(#A 2h6(\3A 2Z6(z3@A 256(3A 36(*@A 26g6(A 2ZZ6( A 2D6(&fA 2x6(@A 2.6( A 2l6( A 2t/6( A 2&ٚ6(@A 2^ŋ6(| A 2A26(&"A W-A (1 uA  iA (3`uA b^A (3uA /2|A (*' {A ?OA ($"A "A (">A B̰A (A HA (- A +GpA ("`A cA (z. A $LZA ($LA pA ("`A nXA ($ A ݜA ( % A TA (.A ZsA (4A [j @A (+A A (f) A (eA (. A *MsA (P A U+hA (+@A #*A (+`-A |pGA (/?@A i|A (|)A oA (?'%A ^A (T' A UُA ()A m$A (/A X])A (i'`A ObJA ((-A |'A (52A \A (@-A 5A (+`A 4A (R2A 4%N/A ( ,A mڄA (~'2A A >pA T.`4A Z4UA (3A {@A (_0A A ()A c4A ('A Ө A (A A 6D7A Txe`A ̥A (v@A ԑA (A ?i A (A ~.8A ([ @A 3]A (|A F<A ( %A 3A ( A 6*[]struct { mcentral runtime.mcentral; pad [24]uint8 }7*struct { F uintptr; released *uintptr; crit *float64 }7*struct { F uintptr; stacksize int32; newg *runtime.g }7*struct { base runtime.offAddr; bound runtime.offAddr }8*func(*runtime.hchan, unsafe.Pointer, bool) (bool, bool)9*[136]struct { mcentral runtime.mcentral; pad [24]uint8 }9*func(*runtime.hchan, unsafe.Pointer, bool, uintptr) bool9*struct { F uintptr; gp *runtime.g; gcw *runtime.gcWork }:*struct { F uintptr; p unsafe.Pointer; b *runtime.bucket };*struct { F uintptr; frame *runtime.stkframe; bad uintptr }<*struct { F uintptr; gp *runtime.g; pc uintptr; sp uintptr }<*struct { F uintptr; pc uintptr; sp uintptr; gp *runtime.g }A >=*struct { F uintptr; p *runtime.pageAlloc; minPages uintptr }A Aп T@?A H 2(Uq 2(A mTT4?A A %yT?A A >° Tf @@A A .Tq @A m3A (cA &ĒI3A !(>6A qST@A A ΀TAA A AT@AA A ?TAA A 'TAA A 1ܑTz0E A 18ucT)E A 1ǵ*RT!H A 1ZsT! A 1ЦT1@I A 1T*@L A q{mTu#M n(>3@S A 1S T* A 1mT@ T A 1E^6T#@U A 1 T\3U A 1WJk Tz3U A 1ػ T3V A ^RE TV+\( VA A wT@WA A  T WA A =J T WA A .1 T XA A _B>fT@XA A J T| XA 6\:(&"XA A ?*struct { F uintptr; prevDefer *runtime._defer; gp *runtime.g }@*struct { F uintptr; ok *bool; oldp *runtime.p; _g_ *runtime.g }A  A 2l#j pTT1@> A A 2B= `ST81>A A 1I" STT1 A A 1', ST'+LA @*1 SЊ>+ A A *hĮ `STA@A A @A 1vΥ, 0STU+LA @fe%]"0;`f`A Au 1(X4`u`A Aj 1(x4uA A 1a TA A 1Њ} ST@A A 1p͙= STpA A 1h:9 STA A 20"a STa.A At2R @2 u A A 1Cp3 ST4A A@@. @Th)% A AP8z `Tb$)2A A PX8;0!`A A 1c} T. A A 1F T@A @Mg Sl+ { A A 17`K STg A `P>*A !nt/ A @A 1@R @TTp1A A@8УJ@;c;`A A 1B T@A Al>8 T8:)` A A 2hQ@ TT+LA A 2h( TTA @Oۅ PS/A xhȨA !p, A ACH SD0`4 ?A @A 1& @TTH@@A A`X:~H;rU`A 0A 1^  TT&A A 1b @ST2`A A 1>0 T@A A|l P;o` A  n3A "(#A 3A "(4@A SΥ3A "((7A @@4A !i'" A @@3HA !iRA @@}A !i$`A r?A !+ `A  A 5jA !TA vPzA !|,@ A xf A !t2A  A D^A !TP) A >y A !T@A  A .A !Tb A @@%@ A !i' A -A !(o2P@ A JI*struct { F uintptr; state *runtime.stackScanState; gcw *runtime.gcWork }A A3A *(7A rsu3A *(8A *W08A A A @e T' A A @1x T%@ A A @}&T% `jXrA AЁ @|- T& A A @t[ T`& A A @ T, A A @@)oT* `jY3AЄ ' TA A X 6(' - A XH` 6(' A Xt y 6(1 `j]]A X/G\ 6(%` `jXXA A Hn T;(= A A H*AL* TL&@b  A A Hz Ti,`4  A XC4] 6(* `jYYA }L5A 5(4`A EW5A 5(8`@A .R5A 5(1pA v5A 5(5@@A 85A 5(6"pA !5A 5(24 @A A AUDST)F A AА *W@|PTT.G@A  A @A A˞@TT$&` A @A AFW@TTB*` Aڒ X*struct { F uintptr; firstFree *struct { base runtime.offAddr; bound runtime.offAddr } }X*struct { F uintptr; typ *runtime._type; src unsafe.Pointer; off uintptr; size uintptr }A h3A X(`f s3A A(f A Q TH% AЖ P T\% A r!Tp%@GAٗ A p _ L(;, `jNO@eA <~Tp-IA A AЙ [zT/KA  A A JTLAؚ A A eFuT4NA AЛ A _NT;(A @A p D(n* `joP@eA T"@PAٝ A  A M ?T( `^*struct { F uintptr; pc uintptr; sp unsafe.Pointer; gp *runtime.g; prevDefer *runtime._defer }_*struct { F uintptr; pc uintptr; sp uintptr; gp *runtime.g; skip int; pcbuf []uintptr; n *int }a*struct { F uintptr; msgs *runtime._panic; gp *runtime.g; pc uintptr; sp uintptr; docrash *bool }A h 6(3@$ `jNN@eA hcy 6(H% A hNs 6($,7 `jmm@eA hԎ* 6(\%@ A hDZp 6(;, `jNN@eA h=yB 6(%@!  pA A Xx T!  XA hg> 6(`W ` A hod 6()/ \\fZZA h 6(R, @A hRW 6(B/ FA h96 6(& A hLE 6(@n  @eA h 6(n*@ `joo@eA h"~ 6(`& A h撚 6(( `A hB! 6(*@ vvA hNQ 6(, A A X;( T# L@e0@eA .^8@Aش A `jA ӳy8&S A `jf*struct { F uintptr; fn **runtime.funcval; argp unsafe.Pointer; siz int32; gp *runtime.g; pc uintptr }f*struct { F uintptr; size uintptr; align uintptr; sysStat *runtime.sysMemStat; p **runtime.notInHeap }g*struct { F uintptr; h *runtime.mheap; npages uintptr; spanclass runtime.spanClass; s **runtime.mspan }A A `P{ T. (A x΄ 6(` ?A A h] T!: ]WVA x VDt 6(%M @tA xҢ# 6(% A x(N 6(#2 oonn@eppA *y ^ 6(b A F  LL A @A Zr'X;T4%BxQ@X(07`A A Z}JT A (>DA A Z}T<#H A@ (7A A Zk+5T` A (9A A ZS TB/ A@ (h7A A Z=apTL A (J:A A Z!T@N A@ (l8A A ZsST-O A (BA յA 8:A "0(7A 0,7z^iWA :8P7 Ax7A 08A :8t8 A0x7A 0<7 t;ZA :87 Ax7A 0<7 ǖA :88 A0x7A 0H DXA :88 Ax7A 00=sFA :86 A0x7A 07A :85 Ax7A 08xs}A :86 A0x7A 07J >A :87 Ax7A 0vC`v\A :8'9 A0x7A 07`\A :8I8 Ax7A 0h7`to`A :85 A0x7A h7A q36 Tz( (^8 A KE T&fW A0$99A p<9A J]_ Tx7 A$9A H8A rԚ<T5 A0@=``7A J0  ST9 Ar8A 068|A :8: A0:@A s7<; AE@A 8PT; 6('`-  (&A ; 6(`  A |Q 6(e( @FFGGHHA ' 6(z(` (^8 A W% 6(# C @e@e44` @e55 @eA   6(s   @e. @eyyA 2wOs 6(5A L t vA k՜D@'D A @7"A 8"A jy;8' A@ @`7A D`A j&'W;(G A @7A FA A ZST)#@H A @<A JBA A ZT-@J A  @EA :`A A Z)TO#J A @5:A 9A A ZI:T!K A` @J:A DCA A [w:NT&( A @=LA 9LA A rtZlT M A @,Xl8A j>!(0N A@ @7A 6EA A ylY^@STX*  A @MDJA A ZAT0O A @$9 A 8A A ZT(O A  @5:A 8A 88$ dA Kd#P A @8UA x9"`A A ZBT @Q A` @7A 7A uMS0Q A @]@8@A  A yYnST2Q A @O@J:0A j9$U82R A@ @8A 8"A A yc ST-@R A @Y 8 @A  A y.ST1R A @O@J:`0A A Z&|rT[/R A  @:A :A j-2W(-S A @8A r8A j@b @ A` @= A 9 A A yD6ST# A @O@80A AЂ ZpST*@V A @X:A Q: A҃ *struct { inUse runtime.addrRanges; gen uint32; reservationBytes uintptr; released uintptr; scavLWM runtime.offAddr; freeHWM runtime.offAddr }A  6(%8 0 @ez@eT@e(@eA A  T"=F yn"@eQ @eA ] 6(*0Q  %@eU6 A yA :8g Ax7Aȉ ) = *8 A  A :Xg APx7A )7Z8@. A 4}sA :X; Ax7A )77 A (GA :8; Ax7A )7F A KCA :8P; A0x7AȎ )7K A \o{A :XT9 Ax7A )8`x7`x A *@A :8Ph Apx7A )d7XF A =/A :8Pg A x7A )d787` A hBA :8H: A x7Aȓ )77 A dA :Xq AP x7A )I {7 A YA :Xe A x7A )FG9w A KdA :h; A x7A )8@7`s A KTA :h{: A0 x7AȘ )8B8 A o A :he A x7A )JC: A ZĮ A :X Ap x7A )`>Z8@. A AЛ lNK T&( @ep0p Z / A ((uA JX) A Xh@`A )N0 D@A (6RɘA J(X-E A X8A )909@A A Z6;T'@E A` X9A )j9J:`| A jSX/F A  XL7A )8 7 A  A ZwIST 3I A XX:A )7 \70A P@@ĆA Kg/J A X8UA )C>` : pA jVT)/ A` X:A )<B A AЧ ZSTR, A  XJ:A )?H8 A jS`%L A X_FuA )4PuwR A jr;% A XJA )B`9`A j((M;Y,*@M A` XIMA )JR P@A AЭ ZASTe( A  X$9A )<: A A ZEDT0@O A X}GA )89A Aа ZY=;T#P A XJ:A )@9@A A Zt}FST&T A` X>A )?D A Aг Z̖TTs A  XJ:A )H868 A AA :e6@B 74DA B9< _F`yA TA ::B 74DA B9<_FyA ppR&MA :z<5B 7P4DA A9@<P_FyA  7YA :7C 74DA B9<_F zA ppo/QA :z8@C 74DA A9`<P_F`zA =A :5C 74DA B9 <P_FzA  )y A :8g APx7A A77 70A  qϚA :P"r A x7A A7`vm: 80A  <A :T h A x7A A77 70A $ @% A! @H<`{<  pA (h 6(U `j `j~ `j ( A i 6(O  @e4 p@eA ^ 6(#9 CA k( `;V3 A$ pK"A D"D" U0 XSh0F A`% pAA @N68 ~80 uSU#G A & p87A IT7X7 D70 A Z" (<T*@T A& p\JA hJ"Q 0Q0c* 6() jjtmm ,iichhgg(llkkA A ;5 T%@ \.!,A g5 6(> <f `j `j`j A ((̓LfA :W A0*x7A YP7`T@ J0h7`t@A ((A :\@ A+x7A Y77 70I {@A ((A :[Q A+x7A Y:X< D0d7t@A (dA :X A,x7A Y8"8 80:@A A [+( ;T' A- @$99A X=9 (f&A  M 6(!0 N @e @e@eU. A F 6(.`  R \ ss@e(A Z;^ 6($& X x @eMMfLLF0NNc @eKKJJA (A Z9;T! A@1 >A Y>~O F0O@A j |;@@ A 2 p=A YP<"9 07(T8`0A j(K;5S8# A3 J@A Y@>G G0h=`@A k00xA;a1Q A3 C@A Y7 _:09"@7 PA j FɈ7S8i, A4 8A Y:8"9 >0A 0(yA :Z A5x7A 7s9 807@7P00hqA :] Ap6x7A y.:{7 707@A`vPA Z;T$, A`7 pp7A t7@ @9@"`jmQ@eP(RKA ;X A08X=A `8P[`FpC9@B9Oc@;(%` A 9 Xx<`A p:@h= z@eT@e(@eA A ZDOT#` A : (47A pCA 0A Z TT!@ A ; J:`A qdA`8 90:@f:PA 0A Z7CL TTb#@K A < 7A q`79 707@N8PA ]$ 6(B*  @e fPPRRQQt @e OOA HH_A :l A>x7A 77 70:@=P`7wA jb9( A ? (<`A <f `j `j`j A j8 iDu0TJ~1@F A @ 7A 9`? E`0<@':P\9`A j8 Ȓ0TTI A@A 7A؂ 7L7 N908@7P}7`A A Z ":pST(J A`B $?A 8>A yI` I` R`!yL ~!O!A 0A Z;T#@ AC =@A bCM >@0A@zPPMXA Q 6(X*  @eTT SSf-t @e, A j808j*x;`!D AE 9A؋ $8@9{07@7P J`FAb~?dA Z;T" AF pMA 8JA ; N0N@`<PMNEP H0 yA J^Q U AH 7 A ;;@ H70$8@@8`>Fp@00A :_9C 7I <A =@$7@IP=Ay }My0lI@<PA 003A :_6 7PJ <A =@$7@IP=A z }M z0lI@<PA 00$A :__5 7K <A =@$7@IP=Az }Mz0lI@<PA A tq T@= d &s@e g.yJ"F$4l$@e H A H@o,A Jf% A N :A =`$8 :0<@?Px=`:p#;A j00wך T_ A`O <A =@$7@IP=A }M0lI@<PA RA JP(! AP CA 6?&N9X>@H8@X=:7P`I D:A PA Zf(;T*0 AR <`A P>Q 68`06B`@99`P9`:p=:A kXXh_Tjt&S A`S 7@!A 7 E07@7P7`;p9??A A 5 Tf @$ $(P% 'D)8",y+ -*@e!Y$L "#@e&RvA 0(BNA J[ A@V :Aج 0C9 p>(X<@*L@,9@.<0l?`@8PJXA j`XgAV;x AW _@@Aد +E@68 bE0??8mE"@(>@P=@`=A p >A :@A RbKL 6(P(@P @AA >>II f@@ ??tBB<<;;*"$@e& EE @eDD ==A jH8R,/h;e!D AZ 08Aص C9 I 77 70>{@9 {P7`;p"B0A ZCp;Tt@D A@\ 0<Aظ B9O :(2@0:8B@EHc?P07RI@V@A Zq~~@TT8&N A] 0=Aػ t:@{:@ =@ &C@=?@ X0E@pUPBP`MpXX" <v/ T A@_ HL7Aؾ J: : x90bO@PP@`EhCj @ pG G <7 A `@ƒ]A :i9 7`l8tA x9l7 P7z0Ay@29yP_F`4PpQ@@fD$7@T7@-?OA /A J~L&` Ab x9A :@7 A0<2;8:@7P7G`7;88G  8:A A [^h?};T` Ad PJ:LA :L:  J0@@=XPH`?pLXHLgGH?DLJ`>(F@AE@H@bE=P@GiiDA I@ A g :@A =@PgG0J8\G@DP07MM|Y` ^@RRU ?X 8L`|Ph<`LHQ0Z?@>QPKI :MI`0IM`hR`hB@LHRD *8J: A X'&%8A Jċ An.. `7A @9 \J "Q(gN&0\7p? )@lPHR@H ;R F F : C8=ZN QG PVM`P$G`,Np,N,X,@ ,07,N,Pe,,R,c-S[@- e -Z800->-_XM0GMN@M@`MIMQM`UNRNC N8"NA xhهA JH A`s// xP>A P P0>{@> {P\7`>@pJJ;G=Q J(90PJ@(KPO`Cb^PdUfWhRjMltNn-[pU@rMtpXvRGN B8GFF0> 90pH v@CPXC`\D pA?x> 3NUA xA JK A@x@@ 7A B@Q?D@^BM`C8 F`U9 H0d7 @= Pm: `7pTIGM=<J%GA PI`=ExE cMK@F<:@U_L } WE0:@ZAPPJ`BpO@0I: > ")" ][] i)s } G M P *( - < > m= n=%: ???NaN]: endfingc gp nilobjpc= <== at fp= is lr: of pc= sp: sp=) = ) m=+Inf-Inf: p=GOGCLEAF m=] = ] n=allgallpbasecas1cas2cas3cas4cas5cas6deadidleitabprofrootsbrktrue ... H_T= H_a= H_g= MB, W_a= and cnt= h_a= h_g= h_t= max= ms, ptr siz= tab= top= u_a= u_g=, ..., fp:] = (deferfalsegFreegcinggscanhchaninit mheappanicscav schedsleepsudogsweeptrace addr= alloc base code= ctxt: curg= free goid jobs= list= m->p= next= p->m= prev= span=% util(...) , i = , not SCHED efenceobjectselectsweep sysmontimers (scan (scan) MB in allocs dying= locks= m->g0= nmsys= s=nil zombie, goid=, j0 = GODEBUGIO waitUNKNOWN types value=cpuprofforcegcgctracehead = panic: runningsyscallunknownwaiting bytes, etypes goalΔ= is not mcount= minutes nalloc= newval= nfreed= packed= pointer stack=[ status [signal stack=[cgocheckdeadlockgo1.16.3pollDescrunnableruntime.rwmutexRrwmutexWscavengetraceBufunknown( (forced) -> node= blocked= defersc= in use) lockedg= lockedm= m->curg= marked ms cpu, not in [ s.limit= s.state= threads= u_a/u_g= unmarked wbuf1.n= wbuf2.n=(unknown), newval=, oldval=, size = , tail = : status=atomicor8chan sendcopystackctxt != 0debugLockhchanLeafinittraceinterfacemSpanDeadmSpanFreepanicwaitpreemptedrecover: scavtracestackpoolwbufSpans} stack=[ MB goal, actualΔ= flushGen gfreecnt= pages at runqsize= runqueue= s.base()= spinning= stopwait= sweepgen sweepgen= targetpc= throwing= until pc=, bound = , limit = Bad varintGC forced GOMAXPROCSatomicand8debug callfloat32nanfloat64nangoroutine invalidptrlock countmSpanInUsenotifyListruntime: gs.state = schedtracesemacquirestackLargeticks.locktracefree(tracegc() unknown pc of size (targetpc= KiB work, freeindex= gcwaiting= heap_live= idleprocs= in status mallocing= ms clock, nBSSRoots= p->status= s.nelems= schedtick= span.list= timerslen=, elemsize=, npages = : frame.sp=GOTRACEBACKassistQueuebad m valuebad timedivcgocall nilclobberfreecreated by float32nan2float64nan1float64nan2float64nan3gccheckmarkmSpanManualnetpollInitreflectOffsruntime: P runtime: p scheddetailtracealloc(unreachable KiB total, [recovered] allocCount found at *( gcscandone m->gsignal= minTrigger= nDataRoots= nSpanRoots= pages/byte preemptoff= s.elemsize= s.sweepgen= span.limit= span.state= sysmonwait= wbuf1= wbuf2=) p->status=-byte limit abi mismatchbad flushGenbad g statusbad recoverycan't happencas64 failedchan receivedumping heapend tracegc entersyscallgcBitsArenasgcpacertracelfstack.pushmadvdontneedmheapSpecialmspanSpecialraceFiniLockreleasep: m=runtime: gp=runtime: sp=spanSetSpinesweepWaiterstraceStringswirep: p->m=worker mode != sweepgen MB) workers= called from flushedWork heap_marked= idlethreads= is nil, not nStackRoots= s.spanclass= span.base()= syscalltick= work.nproc= work.nwait= , gp->status=-byte block (GC sweep waitbad flushGen bad map statefatal error: load64 failedmin too largenil stackbaseout of memoryruntime: seq=runtime: val=self deadlocktraceStackTabtriggerRatio=value method xadd64 failedxchg64 failed} sched={pc: but progSize nmidlelocked= out of range untyped args -thread limit GC assist waitGC worker initMB; allocated allocfreetracebad allocCountbad span statebad stack sizefinalizer waitgcstoptheworldmemprofilerateno module datapollCache.lockruntime: full=s.allocCount= semaRoot queuestack overflowstopm spinningstore64 failedsync.Cond.Waitwork.full != 0 with GC prog ] morebuf={pc:asyncpreemptoffforce gc (idle)malloc deadlockmisaligned maskmissing mcache?ms: gomaxprocs=recovery failedruntime error: runtime.gopanicruntime: frame runtime: max = runtime: min = runtimer: bad pscan missed a gstartm: m has pstopm holding p mheap.sweepgen= not in ranges: untyped locals 0123456789abcdefGC scavenge waitGC worker (idle)GODEBUG: value " runtime stack: bad g transitionbad special kindbad summary databad symbol tablecastogscanstatusgc: unswept spangcshrinkstackoffinvalid g statusinvalid spdelta mSpanList.insertmSpanList.removemissing stackmapnewmHandoff.locknon-Go function pacer: H_m_prev=reflect mismatchruntime: g: g=runtime: addr = runtime: base = runtime: gp: gp=runtime: head = runtime: nelems=schedule: in cgoworkbuf is empty initialHeapLive= spinningthreads=, p.searchAddr = : missing method GC assist markingbad TinySizeClassdebugPtrmask.lockg already scannedglobalAlloc.mutexlocked m0 woke upmark - bad statusmarkBits overflownotetsleepg on g0runtime: level = runtime: nameOff runtime: next_gc=runtime: pointer runtime: summary[runtime: textOff runtime: typeOff scanobject n == 0select (no cases)stack: frame={sp:swept cached spanthread exhaustionunknown caller pcwait for GC cycle but memory size to non-Go memory , locked to threadbad lfnode addressbad manualFreeListfaketimeState.lockforEachP: not donegarbage collectionindex out of rangeruntime: npages = runtime: range = {system page size (tracebackancestors called using nil *, g->atomicstatus=, gp->atomicstatus=GC work not flushed_cgo_setenv missingadjusttimers: bad pbad runtime·mstartbad sequence numbercgocall unavailabledodeltimer: wrong Pm not found in allmmarking free objectmarkroot: bad indexmissing deferreturnmspan.sweep: state=nwait > work.nprocspanic during mallocpanic during panic panic holding lockspanicwrap: no ( in panicwrap: no ) in runtime: pcdata is runtime: preempt g0semaRoot rotateLeftstopm holding lockssysMemStat overflowunexpected g statusunknown wait reason markroot jobs done to unallocated spanbad defer size classbad system page sizebad use of bucket.bpbad use of bucket.mpchan send (nil chan)close of nil channeldodeltimer0: wrong Pforcegc: phase errorgc_trigger underflowgo of nil func valuegopark: bad g statusmalloc during signalp mcache not flushedpacer: assist ratio=preempt off reason: reflect.makeFuncStubruntime: unknown pc semaRoot rotateRighttrace: out of memorywirep: already in goworkbuf is not emptywrite of Go pointer previous allocCount=, levelBits[level] = _cgo_unsetenv missingasync stack too largecheckdead: runnable gconcurrent map writesfindrunnable: wrong pnegative shift amountpanic on system stackreleasep: invalid argruntime: confused by runtime: newstack at runtime: newstack sp=runtime: searchIdx = runtime: work.nwait= startlockedm: m has pstartm: m is spinningtimer data corruptionassembly checks failedbad g->status in readybad sweepgen in refillduplicated defer entryfreeIndex is not validgetenv before env initheadTailIndex overflowinteger divide by zerointerface conversion: minpc or maxpc invalidnon-Go function at pc=oldoverflow is not nilruntime.main not on m0runtime: work.nwait = runtime:scanstack: gp=s.freeindex > s.nelemsscanstack - bad statussend on closed channelspan has no free spacestack not a power of 2timer goroutine (idle)trace reader (blocked)trace: alloc too largewirep: invalid p state) must be a power of 2 MB during sweep; swept ", missing CPU support chan receive (nil chan)close of closed channelfatal: morestack on g0 garbage collection scangcDrain phase incorrectindex out of range [%x]invalid m->lockedInt = left over markroot jobsmakechan: bad alignmentnanotime returning zeropanic during preemptoffprocresize: invalid argreflect.methodValueCallruntime: internal errorruntime: invalid type runtime: s.allocCount= s.allocCount > s.nelemsschedule: holding locksshrinkstack at bad timespan has no free stacksstack growth after forksystem huge page size (unlock of unlocked lockwork.nwait > work.nproc", required CPU feature bad defer entry in panicbad defer size class: i=bypassed recovery failedcan't scan our own stackdouble traceGCSweepStartgcDrainN phase incorrectinitSpan: unaligned basequeuefinalizer during GCrange partially overlapsrunqsteal: runq overflowruntime: found obj at *(runtime: p.searchAddr = span has no free objectsstack trace unavailable to unused region of spanGODEBUG: can not enable "_cgo_thread_start missingallgadd: bad status Gidlearena already initializedbad status in shrinkstackbad system huge page sizechansend: spurious wakeupcheckdead: no m for timermissing stack in newstackmissing traceGCSweepStartreleasep: invalid p stateremaining pointer buffersruntime: misaligned func runtime: program exceeds slice bounds out of rangestartm: p has runnable gsstoplockedm: not runnableGODEBUG: can not disable "corrupted semaphore ticketentersyscall inconsistent forEachP: P did not run fnfreedefer with d.fn != nilinitSpan: unaligned lengthnewosproc: not implementednotewakeup - double wakeupout of memory (stackalloc)persistentalloc: size == 0runtime: bad span s.state=shrinking stack in libcallstartlockedm: locked to meG waiting list is corruptedaddress not a stack addressgcstopm: not waiting for gcgrowslice: cap out of rangeinternal lockOSThread errorinvalid profile bucket typemakechan: size out of rangemakeslice: cap out of rangemakeslice: len out of rangemspan.sweep: bad span stateprogToPointerMask: overflowrunlock of unlocked rwmutexruntime: asyncPreemptStack=runtime: checkdead: find g runtime: checkdead: nmidle=runtime: thread ID overflowstack size not a power of 2startm: negative nmspinningstopTheWorld: holding lockstimer when must be positivework.nwait was > work.nproc args stack map entries for FixedStack is not power-of-2[originating from goroutine comparing uncomparable type fatal: morestack on gsignal findrunnable: netpoll with pfound pointer to free objectgcBgMarkWorker: mode not setgcstopm: negative nmspinninginvalid runtime symbol tablemheap.freeSpanLocked - span missing stack in shrinkstackmspan.sweep: m is not lockednewproc1: new g is not Gdeadnewproc1: newg missing stackregion exceeds uintptr rangeruntime: bad lfnode address runtime: casgstatus: oldval=runtime: no module data for unreserving unaligned regionaddspecial on invalid pointergc done but gcphase != _GCoffgfput: bad status (not Gdead)invalid length of trace eventnotesleep not supported by jsruntime: impossible type kindruntime: levelShift[level] = runtime: marking free object runtime: p.gcMarkWorkerMode= runtime: split stack overflowruntime: sudog with non-nil cruntime: summary max pages = runtime: unknown pc in defer semacquire not on the G stackstring concatenation too long (types from different scopes) in prepareForSweep; sweepgen locals stack map entries for GODEBUG: unknown cpu feature "abi mismatch detected between assignment to entry in nil mapcheckdead: inconsistent countsfailed to get system page sizefreedefer with d._panic != nilinvalid function symbol table invalid pointer found on stacknotetsleep not supported by jsrunqputslow: queue is not fullruntime: bad pointer in frame runtime: found in object at *(runtime: impossible type kind ) not in usable address space: ...additional frames elided... bad write barrier buffer boundscasgstatus: bad incoming valuescheckmark found unmarked objectinternal error - misuse of itabnon in-use span in unswept listpacer: sweep done at heap size resetspinning: not a spinning mruntime: cannot allocate memoryruntime: split stack overflow: slice bounds out of range [%x:]slice bounds out of range [:%x] (types from different packages)" not supported for cpu option "end outside usable address spacepanic while printing panic valueruntime: mcall function returnedruntime: newstack called from g=runtime: root level max pages = runtime: stack split at bad timeruntime: sudog with non-nil elemruntime: sudog with non-nil nextruntime: sudog with non-nil prevscanstack: goroutine not stoppedslice bounds out of range [%x::]slice bounds out of range [:%x:]slice bounds out of range [::%x]sweep increased allocation countGODEBUG: no value specified for "base outside usable address spaceconcurrent map read and map writefindrunnable: negative nmspinningfreeing stack not in a stack spanheapBitsSetType: unexpected shiftmin must be a non-zero power of 2misrounded allocation in sysAllocruntime: castogscanstatus oldval=runtime: failed mSpanList.insert runtime: goroutine stack exceeds runtime: memory allocated by OS [runtime: name offset out of rangeruntime: type offset out of rangeslice bounds out of range [%x:%y]stackalloc not on scheduler stackstoplockedm: inconsistent lockingtimer period must be non-negativedoaddtimer: P already set in timerforEachP: sched.safePointWait != 0mspan.ensureSwept: m is not lockedout of memory allocating allArenasruntime: g is running but p is notruntime: unexpected return pc for schedule: spinning with local workslice bounds out of range [%x:%y:]slice bounds out of range [:%x:%y]attempt to clear non-empty span setfindfunc: bad findfunctab entry idxfindrunnable: netpoll with spinninggreyobject: obj not pointer-alignedmheap.freeSpanLocked - invalid freepersistentalloc: align is too largepidleput: P has non-empty run queuetraceback did not unwind completely) is larger than maximum page size () is not Grunnable or Gscanrunnable Go pointer stored into non-Go memoryruntime: invalid pc-encoded table f=runtime: invalid typeBitsBulkBarrierruntime: marked free object in span runtime: mcall called on m->g0 stackruntime: sudog with non-nil waitlinkruntime: wrong goroutine in newstackuncaching span but s.allocCount == 0) is smaller than minimum page size (_cgo_notify_runtime_init_done missingall goroutines are asleep - deadlock!failed to reserve page summary memoryruntime: allocation size out of rangesetprofilebucket: profile already setstartTheWorld: inconsistent mp->nextparg size to reflect.call more than 1GBconcurrent map iteration and map writegcBgMarkWorker: blackening not enabledindex out of range [%x] with length %ymakechan: invalid channel element typeruntime.write to fd > 2 is unsupportedruntime: sudog with non-false isSelectheapBitsSetTypeGCProg: small allocationmismatched count during itab table copymspan.sweep: bad span state after sweepout of memory allocating heap arena mapruntime: casfrom_Gscanstatus failed gp=runtime: function symbol table header: stack growth not allowed in system callsuspendG from non-preemptible goroutinebulkBarrierPreWrite: unaligned argumentscannot free workbufs when work.full != 0refill of span with free space remainingruntime: out of memory: cannot allocate runtime: typeBitsBulkBarrier with type attempted to add zero-sized address rangegcSweep being done but phase is not GCoffmheap.freeSpanLocked - invalid span statemheap.freeSpanLocked - invalid stack freeobjects added out of order or overlappingruntime: typeBitsBulkBarrier without typestopTheWorld: not stopped (stopwait != 0)acquireSudog: found s.elem != nil in cachenon-empty mark queue after concurrent markon a locked thread with no template threadout of memory allocating checkmarks bitmappersistentalloc: align is not a power of 2unexpected signal during runtime executiongcBgMarkWorker: unexpected gcMarkWorkerModegrew heap, but no adequate free space foundheapBitsSetTypeGCProg: unexpected bit countnon in-use span found with specials bit setroot level max pages doesn't fit in summaryruntime: casfrom_Gscanstatus bad oldval gp=runtime: heapBitsSetTypeGCProg: total bits runtime: releaseSudog with non-nil gp.paramruntime:stoplockedm: lockedg (atomicstatus=unfinished open-coded defers in deferreturnunknown runnable goroutine during bootstrapgcmarknewobject called while doing checkmarkout of memory allocating heap arena metadataruntime: lfstack.push invalid packing: node=exitsyscall: syscall frame is no longer validheapBitsSetType: called with non-pointer typeruntime: failed mSpanList.remove span.npages=scavengeOne called with unaligned work region (bad use of unsafe.Pointer? try -d=checkptr) memory reservation exceeds address space limitpanicwrap: unexpected string after type name: released less than one physical page of memoryruntime: name offset base pointer out of rangeruntime: panic before malloc heap initialized runtime: text offset base pointer out of rangeruntime: type offset base pointer out of rangeslice bounds out of range [:%x] with length %ystopTheWorld: not stopped (status != _Pgcstop)sysGrow bounds not aligned to pallocChunkBytesP has cached GC work at end of mark terminationracy sudog adjustment due to parking on channelslice bounds out of range [::%x] with length %yslice bounds out of range [:%x] with capacity %ycasgstatus: waiting for Gwaiting but is Grunnablefully empty unfreed span set block found in resetinvalid memory address or nil pointer dereferencepanicwrap: unexpected string after package name: s.allocCount != s.nelems && freeIndex == s.nelemsslice bounds out of range [::%x] with capacity %ymallocgc called with gcphase == _GCmarkterminationrecursive call during initialization - linker skewGC must be disabled to protect validity of fn valuefatal: systemstack called from unexpected goroutinepotentially overlapping in-use allocations detectedcasfrom_Gscanstatus: gp->status is not in scan statemallocgc called without a P or outside bootstrappingruntime: use of FixAlloc_Alloc before FixAlloc_Init span set block with unpopped elements found in resetfunction symbol table not sorted by program counter: goroutine running on other thread; stack unavailable gcControllerState.findRunnable: blackening not enabledno goroutines (main called runtime.Goexit) - deadlock!casfrom_Gscanstatus:top gp->status is not in scan stategentraceback callback cannot be used with non-zero skipnewproc: function arguments too large for new goroutinein gcMark expecting to see gcphase as _GCmarkterminationprofilealloc called without a P or outside bootstrappinggentraceback cannot trace user goroutine on its own stackruntime: checkmarks found unexpected unmarked object obj=addr range base and limit are not in the same memory segmentmanual span allocation called with non-manually-managed typefound bad pointer in Go heap (incorrect use of unsafe or cgo?)runtime: internal error: misuse of lockOSThread/unlockOSThread0w tApath command-line-arguments mod github.com/sigstore/cosign (devel) 2C1 rBA A J3 /.8KIB,-P/p1p&+()*'%  20)                     JB   9.%/? =         A  !)A    0($  h@ $%4   0     ?=!        L  0+"*#$!k7o7w7  ))))) @BDdtTAHco  S e / im m } |!!&!/&"&D'_c*8...vi../e; / b 1/\b /D/t 0g!0#1*2 4n 44'4*5#7;N/8"0 8F^. 808]1819o9o99v29:3:3 B117Dr:DL F H<H;IO=IO=IC=I= I]:@ I It `?I GCI* CIjDI2 IA{EIbIIGI6 PR&K T,OZWo[/L\>O=_ndHb$v?fff f+ grN g grNg?Uh3N hN h.//5~[9W39k3Dr:Ii<9  F    }y9 [[[[ A  ' '  oo">XBi /_ _  o   N Y !!/ &4UD&& &S&Y&L&N'9'U!( ({)J)Y .V .W . .i. i.%..  .pU.C. +. +0":/"2F%2H%2 20&444=4h4z4{448N/ 8$8<$ 8F$D8D D8DD*V9D,V9DV9YDuoaF>:F@: HGHP /III I v?I /III{I I0{EI2v? I?IB{EPIQHPIdHPRQHPXQHPHPD'TQXBiTSXVBiWX(|X*I#ZfJZfJZJZJZqK#Z *_M_o_M__IM_IdH_d2N_ddH _N _o _o_o _N_NgdHgdHhIWh%WhIWh%W / /...z.\ D9DoS:S:dg_diIIIFE$DAAID  A A YZ Z R|foa, >n = Bn ' c     E& &D&(m(s(;,( ( &6//'/50;HP /0":/"0N# 2b%2g; 2i4`I4}4d 5.5. 5.6G6OE 6]o6n,6o. 5 8$8.8.(9 29 29o:?":/":Ax4 >_>g  >v>~  / IT<IiIi<&ICK IEIN\ I I K I\ IiIiIiI@IB: IL@I  I  I IVv?IW IQIsGIz IIGI I ZjZsZ; fr\ fp /f0Rf0R f2QfK fv? f\ fqK fsv? fv\ g<V9g>SDuogC5TgR?UgRQUgpgF gi1hIWh%W ho  A #!@ BBBBBBA DoD oXBimHzo 2 2 BG G G x , E !!0|!9!>!/ !\!V!Ng!Q "m!V!Ng!Q #o!V!Ng!Q &qI4}&{ &{,X,{ /,,  / / i//7i/8'0":/" 0":/"4I4}4444  / 4.40 /44 /4 /499 /9)9. /9W9^  / 9k9q  / 9c9o /9s9| /??6??6@y@~  / IiI  IDIsI /II /I I /I I I "I v?%I I / II / IqIv /I\It I\It II  / XT|X[|X]IX_(J#Z4ZZJZyL_DZyL(_D)b /bbb3  / feK ff fiffuR fuR fRh%Whoh%Who'W'Z  / ..  / 0~[0~#0#0~# 22 /44  / DD /DD /A K     * ( ()    @   A  /!:!@!!B(!D_.! /&&D&D&&.. .i\.{I /w/ /////u&///u</D/E/G  / /c &/d!&!4[/I4}///(0|$0$00N##0g75eN#5d5o# 5o# 5#%5(#5/- 52# 53#59#&9  9 /929J9_ "/#9[3,=5="5==>5 =o= 5F{ FiFi!Fi&FjLII   I /II I /Im:I}I~II:@I I /I i2I BIi B Q6 Ij BQI I I /RN\ RFRGRK /ZZJZ /Z"Z$JZ) /Z6JZ= / Z4Z% Z+KZqKZ< '/('s't,'u  / D8D8< << I~,I  I / GA A J!g)!#51Aܣ '4''&6'U)/F/G /? /M/N/?/Z/g/p"/z )/*00 D 080:D"0 $0 $ 0 $0$0$0$ 4R)4R) 4R)4R)4R)4k)#4K 4"\ /4  !4 ;/<=_ /=Z = /D]8Da8 Da8<<<(D( /D/D0IK I I  /!I\ 9Iv?I* I/I4 /I I BIi BQ6Ij BQIO BIi BQ6Ij B"Q#II :T7IT T /T +/,g"T%gS+g; =gs5Tgd5Tg; gU g3Io g$g:g3FhV h  hVh /hVhhVh /hVhhVh /;= /M &/'+N 7oN   " +N .N B.I.D.  . //&6// #/ ;/B&"W00 /0G"0I00i0l" b|"0D0i33(4R(4\(3 /33 /3(3 /5#5#5,/5#5#85#G5#X7R/7U#7V- 7\#7\B.8^^.7_##:":*(4:/": A4: A4:4"':/"*IiK IjIlIt ,/-I\ 9IK II,-I 5/6I\ ?I I BIi BQ6Ij BQI GC"IK I II:ID#ZLD%I\ <Iuo IwoI}iI:!IG 7IG FIpi[ZqKZqKZqKZqKZqKZqK#ZqK(ZqKZ~KZqKZLZqKZhK+ZqK0ffP fP f /fPff!Qf( /f / R*R. / R4R5 /R7ISIXBiI$IW)I\5ImD=!{ !o !! ! 1!o2!oW!+c!_!  !r!o!!o,!{9!o;&s &t &6&v& &x'&| +&"0..<...i.K/.7.>..  / .. /.. /.. (/)22D 22D2 !/"2G& 2H&2d&A2J2R)2W52c&=2L\4P /4T 4d 7/84g=>44 /4 /4 !/"5-5#5#5-5#5 #&5#,5#8=5="5==>5=Y5 =o=5'="5*==>5.IIr>IXBiIr>I &/'Z Z&LF %Z; 6ZZ3Z Z3Z / ZZZ #/$b8K bF\ b=!bA\ )bKK ?bY\ IbOTbT\ [ff  / f ff /0%0* / 0/i02i04 /!Y !a  !c8 <!0!o2!~o.<C<N!_ !  !r!o!!o-!{:!o<!F33|3~ / 3l'(3r -/.:W" :/" :Y3:Z":\":*(4:/"":_"*:/"-? R!&!4[4UD? ?t!?!%?&4(Iy=I|= I>I Ii#I(I 2/3Tt7I TTD#T 2/3TX8Bi9TzX@BiAK \ K &\ 1ki<rK Gu\ [zm g|K m\ y"+o"-o"*{!o"+a !o!"_+! 0"o,"$o6"So"Uo"R{!o"Sa !o!"E_+! 0"Fo,"Lo6"vo"w!Ng !Q  "o"o"{A!oC"oG"oH#+o#-o#*{!o#+a !o!#_+! 0#o,#$o6#xo#y!Ng !Q  #o#o#{L!oN#oR#oS// //./>/!A/C J/p/// &/R! &!4[/t! /!/4/+4K 444 /4\ %4 .//4\ 54E4F, 4G  / 4?4@,4A /I:I?IW "/#Iu34Iv78I^DIN   IU /Ip $/%Ig 1/2I` 8/9hWhEo hW$hEo,hW6hOo<hWAhOoHhnN hoN .  . CII_I[ I[ I]$. (/){|  4)1&62? D &"I AK D I _\ ' ]# a 8/9 FAB N M /N._ .aK .a.K .".'.i/.i6.G.\ F.N44!444)4*-4++34G54P,?4*4aIxIy Iz I I}III!I % /&I IIII/BIHBI "/#Ih ,Ij 2f Pf$P f%Q f(XBif5.Qf9GQf<GQ$f>GQ6fAGQ;fCGQNIK I`?II /II#Iv?)Ii0I\ 9I?BIuI /II / I_9:Ib ? /@IiI  IFI'I9aI; bIKpqIOIXIXFIdIm/BIHB In /Ih Ij %Is +/,If 2 /3IID I\It I I/B#IHB$I (/)Ih 2Ij 8  //*H` f/gXn Bio  /Hzo! /& $/%:/7 k /lXt Biu K  )'+ : \ @ o\ W K s \ |   $ ) &"!/ "q_!! #"uo&"*"r1"o8"o:"oG"{N !oQ"m+]#!/ #_!! ##o&#*#r1#o8#o<#oK#{R !oU#+a..  / .- .. /.' (. - /.// //  /&/  /*/+0/, " /# I ,/R!&!4[4UD /!//t!/!/4/ -&!E 4[F7y#7#7#7-7/8o/7N#7/7#)7--7/6 8o/77/=I)I7 II:I= I I?&I(IA, I.IE 2 /3IZIIR I IT I I] /IM&IN. /I` 5 I 6Zp  Zr /Zt#ZzJ+Z"K/Z56Z(;Z @ /AZK#!_ !  #%o#)o#7o%#A{0!o3#Q{?!oA#`V#({f !oh#)al !om/Q/\I[ I[I_/e /e/g #/$/kD)I]//V 3 /4///D/\It /I[ %I[&I_*/ / /0/7I]=IK I III"I -/.IHII  O /PI \ VIfIK I I @II /I! \ #I$ //0I0 v?7I4 \ >I M /NI \ VIFI  II %/&I;I@@IBIv?II:@QI Z /[I c /dZ  Z /Z"ZJ)Z041Z=Z"KCZI JZ P /QZ\bK b\ b#b+b\ 5bSbTUb Z/[b\ dbK xb\ bK b\ f}P fP fPfPfQfX Bi!f.Q%fGQ(fGQ/f 9 /:fPGfzJ K.K .\ .).?'.@*.\i5.?.F.K O.U.\ [.d.`q.C.,.\ .^.ai .cK .i..89. >/?.+K.;...\ HP  /.O07":/"0f# 0g#0j#0u#!0#%0"):/",0$10'$60#> 0WH I 0\ R/SI I BIi BQ6Ij BQI I CI I[ ' I[( I_,I / /0 I]7IBIi BQ6 Ij BQIIDI C!I" I[ ) I[* I_.I 3 /4 I]Hh I XBi&I(X8Bi9 @ /AJ N I Oj V/W9 : Do+ )1o3/o0/ /1 2, *& @(oE( D' A" Q#oS# Ro]!o!!o!g!_T! U!E!{j!ol!j'u!o} !V~ !Q !po!to!| !xo&/I4}&2 &8&?0&IA&JB&MQ& R&O[&S _ &"d&W{ & |&Z&^ &"5n- 5-5#5-5-=5-/5-E5-~5-5#5#>5n-5#5#5-5-5#5s# 5s# 5t- 5t- 5{#5{ .5}$."5}#+5#15 .55x#?5x .C5p#L5o#K5p-R5pB.Q8^^.U!ro!s!Ng !Q  !|o!o!o!'!g,!9!oT!3!rh!{ !o!o!o!oI IC 23II <I[ CI[DI_HIK K/LI]RIG V /WI) oI* r sI, {/|I" X8<<)<4X89<=<E<NXTX8W <[ <c <lX|pX8u<y<<IUE IS IEIQISIX IXEIE IQ"IS&I\IIaJ KIbN OIi W/XIj [/\IiII*I"@IIKI"PI#v?RI'E]IFmI~ I  /I FIII /I"F4q4f*44*49%4*/4D4!!N4!O4Q4*s4z {4  /4 /4w4y /!!/ !V_#! %![r)!Zo(!/!4!+H!+X!8!oj!ok!foq!gor!now!ox!'!yo!|{!o!R+&?"&H" &O"&5"&>"(&_"2&b"6&k"<&q"A&{"I&"Q&W&[&"g&"q&&&&"& "& "&&"&!"&#"2H2mR!&!4[4UD2n2v~&$292.2&@2&C2&W2O2&S2'c2s2v2w222'&&22D/9DoD09DoD19DoD79pDo)D890Do4D99; Do?D<9j DoLD=9SDoVD>9]Do`DDV9xDI9DoDJ9DoDK9DoDuo&Q&6&2"&Y"*& "9&"=&&K&: U&: V&<D\&KDb&U m&U r&TDl &"&"&"&"&"&"&"&"&SD& & &"& &"I I  I  I  I  I I  I %I AI_.I D3I \7 It 8I[ HI[II]MI STI /BWIHBXI \/]I fB`Ih xIj ~I I /BIHBI /I fBIh Ij II lAII AIM A Iv AII 2II A4I EFI K/LI V /WI3 `?sI I, `?I^ `?IA iIH IJ /IO IP  I I I /I)  I iI I # /$I 5"/6I  CI W%XI ]'/^ &(,-4BUXx(IIUUU? I$UU I$I1p $ IJ$ """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""A \ IA Aߘ A $b#BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB!C,`@ 3" H# * @@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@$I@!@!@!@BBBBBB!!!!!BBBBBB!!!!!BBBBB     !@!@!@!@!@!BBBBB!!!!!!BBBBB!!!!!BBBBBB     @!@!@!@!@!@BBBBB!!!!!BBBBB!!!!!!BBBBB      !@!@!@!@!@BBBBBB8!C 1D@WUUUUUUUUUUUm۶m۶-*UUUUUUUULA A %A 5A A A A &v̠Eq-g%۶*>1 A )A A A  A A @A  A *A A  A =A ,A  A %A `A `A `A A `A A A A A A A A `A  6A A `A ` A `A `A `A @A A `A `A SA 8`A `A A `A `EA A @A A A  A A A A A A A @A @A p@A +A ?A A @A A A A `A A A 7A A  A ?A `A SA  A @A 4 _ LNA #88A #88A "A "`A "@@A "@@A #@A "@5A "``A "A A A # { A 3 {@ ` A C JP@K@A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A AȀ A܀ A A A A A Aԁ A A A A A  A A A A A Aă A؃ A A A A A AЄ A A A A A Aȅ A܅ A A A A A AԆ A A A A A Ȧ A A A A A AĈ A؈ A A A A A AЉ A A A A A AȊ A܊ A A A A A Aԋ A A A A A Ǎ A A A A A Ač A؍ A A A A A AЎ A A A A A Aȏ A܏ A A A A A AԐ A A A A A Ȃ A A A A A AĒ Aؒ A A A A A AГ A A A A A AȔ Aܔ A A A A A Aԕ A A A A A A̖ A  A  A  A  A  Aė  Aؗ  A  A  A  A  A  AИ  A  A  A  A !A !Aș !Aܙ !A !A !A !A !A !AԚ !A !A !A !A !A !A̛ !A "A "A "A "A "AĜ "A؜ "A "A "A "A "A "AН "A "A "A "A #A #AȞ #Aܞ #A #A #A #A #A #Aԟ #A #A #A #A #A #A̠ #A $A $A $A $A $Aġ $Aء $A $A $A $A $A $AТ $A $A $A $A %A %Aȣ %Aܣ %A %A %A %A %A %AԤ %A %A %A %A %A %Ḁ %A &A &A &A &A &AĦ &Aئ &A &A &A &A &A &AЧ &A &A &A &A 'A 'AȨ 'Aܨ 'A 'A 'A 'A 'A 'Aԩ 'A 'A 'A 'A 'A 'A̪ 'A (A (A (A (A (Aī (Aث (A (A (A (A (A (AЬ (A (A (A (A )A )Aȭ )Aܭ )A )A )A )A )A )AԮ )A )A )A )A )A )A̯ )A *A *A *A *A *Aİ *Aذ *A *A *A *A *A *Aб *A *A *A *A +A +AȲ +Aܲ +A +A +A +A +A +AԳ +A +A +A +A +A +A̴ +A ,A ,A ,A ,A ,Aĵ ,Aص ,A ,A ,A ,A ,A ,Aж ,A ,A ,A ,A -A -Aȷ -Aܷ -A -A -A -A -A -AԸ -A -A -A -A -A -A̹ -A .A .A .A .A .Aĺ .Aغ .A .A .A .A .A .Aл .A .A .A .A /A /Aȼ /Aܼ /A /A /A /A /A /AԽ /A /A /A /A /A /A̾ /A 0A 0A 0A 0A 0AĿ 0Aؿ 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A :A :A :A :A :A :A :A :A :A :A :A :A :A :A :A :A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A <A <A <A <A <A <A <A <A <A <A <A <A <A <A <A <A =A =A =A =A =A =A =A =A =A =A =A =A =A =A =A =A >A >A >A >A >A >A >A >A >A >A >A >A >A >A >A >A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A @A @A @A @A @A @A @A @A @A @A @A @A @A @A @A @A AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA DA DA DA DA DA DA DA DA DA DA DA DA DA DA DA DA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA GA GA GA GA GA GA GA GA GA GA GA GA GA GA GA GA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA IA IA IA IA IA IA IA IA IA IA IA IA IA IA IA IA JA JA JA JA JAĀ JA؀ JA JA JA JA JA JAЁ JA JA JA JA KA KAȂ KA܂ KA KA KA KA KA KAԃ KA KA KA KA KA KĀ KA LA LA LA LA LAą LA؅ LA LA LA LA LA LAІ LA LA LA LA MA MAȇ MA܇ MA MA MA MA MA MAԈ MA MA MA MA MA MẢ MA NA NA NA NA NAĊ NA؊ NA NA NA NA NA NAЋ NA NA NA NA OA OAȌ OA܌ OA OA OA OA OA OAԍ OA OA OA OA OA OA̎ OA PA PA PA PA PAď PA؏ PA PA PA PA PA PAА PA PA PA PA QA QAȑ QAܑ QA QA QA QA QA QAԒ QA QA QA QA QA QA̓ QA RA RA RA RA RAĔ RAؔ RA RA RA RA RA RAЕ RA RA RA RA SA SAȖ SAܖ SA SA SA SA SA SAԗ SA SA SA SA SA SA̘ SA TA TA TA TA TAę TAؙ TA TA TA TA TA TAК TA TA TA TA UA UAț UAܛ UA UA UA UA UA UAԜ UA UA UA UA UA UA̝ UA VA VA VA VA VAĞ VA؞ VA VA VA VA VA VAП VA VA VA VA WA WAȠ WAܠ WA WA WA WA WA WAԡ WA WA WA WA WA WA̢ WA XA XA XA XA XAģ XAأ XA XA XA XA XA XAФ XA XA XA XA YA YAȥ YAܥ YA YA YA YA YA YAԦ YA YA YA YA YA YA̧ YA ZA ZA ZA ZA ZAĨ ZAب ZA ZA ZA ZA ZA ZAЩ ZA ZA ZA ZA [A [AȪ [Aܪ [A [A [A [A [A [Aԫ [A [A [A [A [A [A̬ [A \A \A \A \A \Aĭ \Aح \A \A \A \A \A \AЮ \A \A \A \A ]A ]Aȯ ]Aܯ ]A ]A ]A ]A ]A ]A԰ ]A ]A ]A ]A ]A ]A̱ ]A ^A ^A ^A ^A ^AIJ ^Aز ^A ^A ^A ^A ^A ^Aг ^A ^A ^A ^A _A _Aȴ _Aܴ _A _A _A _A _A _AԵ _A _A _A _A _A _A̶ _A `A `A `A `A `Aķ `Aط `A `A `A `A `A `Aи `A `A `A `A aA aAȹ aAܹ aA aA aA aA aA aAԺ aA aA aA aA aA aA̻ aA bA bA bA bA bAļ bAؼ bA bA bA bA bA bAн bA bA bA bA cA cAȾ cAܾ cA cA cA cA cA cAԿ cA cA cA cA cA cA cA dA dA dA dA dA dA dA dA dA dA dA dA dA dA dA dA eA eA eA eA eA eA eA eA eA eA eA eA eA eA eA eA fA fA fA fA fA fA fA fA fA fA fA fA fA fA fA fA gA gA gA gA gA gA gA gA gA gA gA gA gA gA gA gA hA hA hA hA hA hA hA hA hA hA hA hA hA hA hA hA iA iA iA iA iA iA iA iA iA iA iA iA iA iA iA iA jA jA jA jA jA jA jA jA jA jA jA jA jA jA jA jA kA kA kA kA kA kA kA kA kA kA kA kA kA kA kA kA lA lA lA lA lA lA lA lA lA lA lA lA lA lA lA lA mA mA mA mA mA mA mA mA mA mA mA mA mA mA mA mA nA nA nA nA nA nA nA nA nA nA nA nA nA nA nA nA oA oA oA oA oA oA oA oA oA oA oA oA oA oA oA oA pA pA pA pA pA pA pA pA pA pA pA pA pA pA pA pA qA qA qA qA qA qA qA qA qA qA qA qA qA qA qA qA rA rA rA rA rA rA rA rA rA rA rA rA rA rA rA rA sA sA sA sA sA sA sA sA sA sA sA sA sA sA sA sA tA tA tA tA tA tA tA tA tA tA tA tA tA tA tA tA uA uA uA uA uA uA uA uA uA uA uA uA uA uA uA uA vA vA vA vA vA vA vA vA vA vA vA vA vA vA vA vA wA wA wA wA wA wA wA wA wA wA wA wA wA wA wA wA xA xA xA xA xA xA xA xA xA xA xA xA xA xA xA xA yA yA yA yA yA yA yA yA yA yA yA yA yA yA yA yA zA zA zA zA zA zA zA zA zA zA zA zA zA zA zA zA {A {A {A {A {A {A {A {A {A {A {A {A {A {A {A {A |A |A |A |A |A |A |A |A |A |A |A |A |A |A |A |A }A }A }A }A }A }A }A }A }AԀ }A }A }A }A }A }Á }A ~A ~A ~A ~A ~AĂ ~A؂ ~A ~A ~A ~A ~A ~AЃ ~A ~A ~A ~A A AȄ A܄ A A A A A Aԅ A A A A A Ă A A A A A Ać A؇ A A A A A AЈ A A A A A Aȉ A܉ A A A A A AԊ A A A A A A̋ A A A A A AČ A، A A A A A AЍ A A A A A AȎ A܎ A A A A A Aԏ A A A A A A̐ A A A A A Ađ Aؑ A A A A A AВ A A A A A Aȓ Aܓ A A A A A AԔ A A A A A A̕ A A A A A AĖ Aؖ A A A A A AЗ A A A A A AȘ Aܘ A A A A A Aԙ A A A A A A̚ A A A A A Aě A؛ A A A A A AМ A A A A A Aȝ Aܝ A A A A A AԞ A A A A A A̟ A A A A A AĠ Aؠ A A A A A AС A A A A A AȢ Aܢ A A A A A Aԣ A A A A A A̤ A A A A A Aĥ Aإ A A A A A AЦ A A A A A Aȧ Aܧ A A A A A AԨ A A A A A A̩ A A A A A AĪ Aت A A A A A AЫ A A A A A AȬ Aܬ A A A A A Aԭ A A A A A A̮ A A A A A Aį Aد A A A A A Aа A A A A A Aȱ Aܱ A A A A A AԲ A A A A A A̳ A A A A A AĴ Aش A A A A A Aе A A A A A Aȶ Aܶ A A A A A AԷ A A A A A A̸ A A A A A AĹ Aع A A A A A Aк A A A A A AȻ Aܻ A A A A A AԼ A A A A A A̽ A A A A A Aľ Aؾ A A A A A Aп A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A AЀ A A A A A Aȁ A܁ A A A A A AԂ A A A A A à A A A A A AĄ A؄ A A A A A AЅ A A A A A AȆ A܆ A A A A A Aԇ A A A A A Ä A A A A A Aĉ A؉ A A A A A AЊ A A A A A Aȋ A܋ A A A A A AԌ A A A A A A̍ A A A A A AĎ A؎ A A A A A AЏ A A A A A AȐ Aܐ A A A A A Aԑ A A A A A A̒ A A A A A Aē Aؓ A A A A A AД A A A A A Aȕ Aܕ A A A A A AԖ A A A A A A̗ A A A A A AĘ Aؘ A A A A A AЙ A A A A A AȚ Aܚ A A A A A Aԛ A A A A A A̜ A A A A A Aĝ A؝ A A A A A AО A A A A A Aȟ Aܟ A A A A A AԠ A A A A A A̡ A A A A A AĢ Aآ A A A A A AУ A A A A A AȤ Aܤ A A A A A Aԥ A A A A A A̦ A A A A A Aħ Aا A A A A A AШ A A A A A Aȩ Aܩ A A A A A AԪ A A A A A A̫ A A A A A AĬ Aج A A A A A AЭ A A A A A AȮ Aܮ A A A A A Aԯ A A A A A A̰ A A A A A Aı Aر A A A A A Aв A A A A A Aȳ Aܳ A A A A A AԴ A A A A A A̵ A A A A A AĶ Aض A A A A A Aз A A A A A Aȸ Aܸ A A A A A AԹ A A A A A A̺ A A A A A AĻ Aػ A A A A A Aм A A A A A AȽ Aܽ A A A A A AԾ A A A A A A̿ A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A À A A A A A Aā A؁ A A A A A AЂ A A A A A Aȃ A܃ A A A A A AԄ A A A A A A̅ A A A A A AĆ A؆ A A A A A AЇ A A A A A AȈ A܈ A A A A A Aԉ A A A A A Å A A A A A Aċ A؋ A A A A A AЌ A A A A A Aȍ A܍ A A A A A AԎ A A A A A Ȁ A A A A A AĐ Aؐ A A A A A AБ A A A A A AȒ Aܒ A A A A A Aԓ A A A A A A̔ A A A A A Aĕ Aؕ A A A A A AЖ A A A A A Aȗ Aܗ A A A A A AԘ A A A A A A̙ A A A A A AĚ Aؚ A A A A A AЛ A A A A A AȜ Aܜ A A A A A Aԝ A A A A A A̞ A A A A A Ağ A؟ A A A A A AР A A A A A Aȡ Aܡ A A A A A AԢ A A A A A Ạ A A A A A AĤ Aؤ A A A A A AХ A A A A A AȦ Aܦ A A A A A Aԧ A A A A A Ą A A A A A Aĩ Aة A A A A A AЪ A A A A A Aȫ Aܫ A A A A A AԬ A A A A A A̭ A A A A A AĮ Aخ A A A A A AЯ A A A A A AȰ Aܰ A A A A A AԱ A A A A A A̲ A A A A A Aij Aس A A A A A Aд A A A A A Aȵ Aܵ A A A A A AԶ A A A A A A̷ A A A A A Aĸ Aظ A A A A A Aй A A A A A AȺ Aܺ A A A A A AԻ A A A A A A̼ A A A A A AĽ Aؽ A A A A A Aо A A A A A Aȿ Aܿ A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A AȀ A܀ A A A A A Aԁ A A A A A  A A A A A Aă A؃ A A A A A AЄ A A A A A Aȅ A܅ A A A A A AԆ A A A A A Ȧ A A A A A AĈ A؈ A A A A A AЉ A A A A A AȊ A܊ A A A A A Aԋ A A A A A Ǎ A A A A A Ač A؍ A A A A A AЎ A A A A A Aȏ A܏ A A A A A AԐ A A A A A Ȃ A A A A A AĒ Aؒ A A A A A AГ A A A A A AȔ Aܔ A A A A A Aԕ A A A A A A̖ A  A  A  A  A  Aė  Aؗ  A  A  A  A  A  AИ  A  A  A  A !A !Aș !Aܙ !A !A !A !A !A !AԚ !A !A !A !A !A !A̛ !A "A "A "A "A "AĜ "A؜ "A "A "A "A "A "AН "A "A "A "A #A #AȞ #Aܞ #A #A #A #A #A #Aԟ #A #A #A #A #A #A̠ #A $A $A $A $A $Aġ $Aء $A $A $A $A $A $AТ $A $A $A $A %A %Aȣ %Aܣ %A %A %A %A %A %AԤ %A %A %A %A %A %Ḁ %A &A &A &A &A &AĦ &Aئ &A &A &A &A &A &AЧ &A &A &A &A 'A 'AȨ 'Aܨ 'A 'A 'A 'A 'A 'Aԩ 'A 'A 'A 'A 'A 'A̪ 'A (A (A (A (A (Aī (Aث (A (A (A (A (A (AЬ (A (A (A (A )A )Aȭ )Aܭ )A )A )A )A )A )AԮ )A )A )A )A )A )A̯ )A *A *A *A *A *Aİ *Aذ *A *A *A *A *A *Aб *A *A *A *A +A +AȲ +Aܲ +A +A +A +A +A +AԳ +A +A +A +A +A +A̴ +A ,A ,A ,A ,A ,Aĵ ,Aص ,A ,A ,A ,A ,A ,Aж ,A ,A ,A ,A -A -Aȷ -Aܷ -A -A -A -A -A -AԸ -A -A -A -A -A -A̹ -A .A .A .A .A .Aĺ .Aغ .A .A .A .A .A .Aл .A .A .A .A /A /Aȼ /Aܼ /A /A /A /A /A /AԽ /A /A /A /A /A /A̾ /A 0A 0A 0A 0A 0AĿ 0Aؿ 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A :A :A :A :A :A :A :A :A :A :A :A :A :A :A :A :A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A <A <A <A <A <A <A <A <A <A <A <A <A <A <A <A <A =A =A =A =A =A =A =A =A =A =A =A =A =A =A =A =A >A >A >A >A >A >A >A >A >A >A >A >A >A >A >A >A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A @A @A @A @A @A @A @A @A @A @A @A @A @A @A @A @A AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA DA DA DA DA DA DA DA DA DA DA DA DA DA DA DA DA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA GA GA GA GA GA GA GA GA GA GA GA GA GA GA GA GA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA IA IA IA IA IA IA IA IA IA IA IA IA IA IA IA IA JA JA JA JA JAĀ JA؀ JA JA JA JA JA JAЁ JA JA JA JA KA KAȂ KA܂ KA KA KA KA KA KAԃ KA KA KA KA KA KĀ KA LA LA LA LA LAą LA؅ LA LA LA LA LA LAІ LA LA LA LA MA MAȇ MA܇ MA MA MA MA MA MAԈ MA MA MA MA MA MẢ MA NA NA NA NA NAĊ NA؊ NA NA NA NA NA NAЋ NA NA NA NA OA OAȌ OA܌ OA OA OA OA OA OAԍ OA OA OA OA OA OA̎ OA PA PA PA PA PAď PA؏ PA PA PA PA PA PAА PA PA PA PA QA QAȑ QAܑ QA QA QA QA QA QAԒ QA QA QA QA QA QA̓ QA RA RA RA RA RAĔ RAؔ RA RA RA RA RA RAЕ RA RA RA RA SA SAȖ SAܖ SA SA SA SA SA SAԗ SA SA SA SA SA SA̘ SA TA TA TA TA TAę TAؙ TA TA TA TA TA TAК TA TA TA TA UA UAț UAܛ UA UA UA UA UA UAԜ UA UA UA UA UA UA̝ UA VA VA VA VA VAĞ VA؞ VA VA VA VA VA VAП VA VA VA VA WA WAȠ WAܠ WA WA WA WA WA WAԡ WA WA WA WA WA WA̢ WA XA XA XA XA XAģ XAأ XA XA XA XA XA XAФ XA XA XA XA YA YAȥ YAܥ YA YA YA YA YA YAԦ YA YA YA YA YA YA̧ YA ZA ZA ZA ZA ZAĨ ZAب ZA ZA ZA ZA ZA ZAЩ ZA ZA ZA ZA [A [AȪ [Aܪ [A [A [A [A [A [Aԫ [A [A [A [A [A [A̬ [A \A \A \A \A \Aĭ \Aح \A \A \A \A \A \AЮ \A \A \A \A ]A ]Aȯ ]Aܯ ]A ]A ]A ]A ]A ]A԰ ]A ]A ]A ]A ]A ]A̱ ]A ^A ^A ^A ^A ^AIJ ^Aز ^A ^A ^A ^A ^A ^Aг ^A ^A ^A ^A _A _Aȴ _Aܴ _A _A _A _A _A _AԵ _A _A _A _A _A _A̶ _A `A `A `A `A `Aķ `Aط `A `A `A `A `A `Aи `A `A `A `A aA aAȹ aAܹ aA aA aA aA aA aAԺ aA aA aA aA aA aA̻ aA bA bA bA bA bAļ bAؼ bA bA bA bA bA bAн bA bA bA bA cA cAȾ cAܾ cA cA cA cA cA cAԿ cA cA cA cA cA cA cA dA dA dA dA dA dA dA dA dA dA dA dA dA dA dA dA eA eA eA eA eA eA eA eA eA eA eA eA eA eA eA eA fA fA fA fA fA fA fA fA fA fA fA fA fA fA fA fA gA gA gA gA gA gA gA gA gA gA gA gA gA gA gA gA hA hA hA hA hA hA hA hA hA hA hA hA hA hA hA hA iA iA iA iA iA iA iA iA iA iA iA iA iA iA iA iA jA jA jA jA jA jA jA jA jA jA jA jA jA jA jA jA kA kA kA kA kA kA kA kA kA kA kA kA kA kA kA kA lA lA lA lA lA lA lA lA lA lA lA lA lA lA lA lA mA mA mA mA mA mA mA mA mA mA mA mA mA mA mA mA nA nA nA nA nA nA nA nA nA nA nA nA nA nA nA nA oA oA oA oA oA oA oA oA oA oA oA oA oA oA oA oA pA pA pA pA pA pA pA pA pA pA pA pA pA pA pA pA qA qA qA qA qA qA qA qA qA qA qA qA qA qA qA qA rA rA rA rA rA rA rA rA rA rA rA rA rA rA rA rA sA sA sA sA sA sA sA sA sA sA sA sA sA sA sA sA tA tA tA tA tA tA tA tA tA tA tA tA tA tA tA tA uA uA uA uA uA uA uA uA uA uA uA uA uA uA uA uA vA vA vA vA vA vA vA vA vA vA vA vA vA vA vA vA wA wA wA wA wA wA wA wA wA wA wA wA wA wA wA wA xA xA xA xA xA xA xA xA xA xA xA xA xA xA xA xA yA yA yA yA yA yA yA yA yA yA yA yA yA yA yA yA zA zA zA zA zA zA zA zA zA zA zA zA zA zA zA zA {A {A {A {A {A {A {A {A {A {A {A {A {A {A {A {A |A |A |A |A |A |A |A |A |A |A |A |A |A |A |A |A }A }A }A }A }A }A }A }A }AԀ }A }A }A }A }A }Á }A ~A ~A ~A ~A ~AĂ ~A؂ ~A ~A ~A ~A ~A ~AЃ ~A ~A ~A ~A A AȄ A܄ A A A A A Aԅ A A A A A Ă A A A A A Ać A؇ A A A A A AЈ A A A A A Aȉ A܉ A A A A A AԊ A A A A A A̋ A A A A A AČ A، A A A A A AЍ A A A A A AȎ A܎ A A A A A Aԏ A A A A A A̐ A A A A A Ađ Aؑ A A A A A AВ A A A A A Aȓ Aܓ A A A A A AԔ A A A A A A̕ A A A A A AĖ Aؖ A A A A A AЗ A A A A A AȘ Aܘ A A A A A Aԙ A A A A A A̚ A A A A A Aě A؛ A A A A A AМ A A A A A Aȝ Aܝ A A A A A AԞ A A A A A A̟ A A A A A AĠ Aؠ A A A A A AС A A A A A AȢ Aܢ A A A A A Aԣ A A A A A A̤ A A A A A Aĥ Aإ A A A A A AЦ A A A A A Aȧ Aܧ A A A A A AԨ A A A A A A̩ A A A A A AĪ Aت A A A A A AЫ A A A A A AȬ Aܬ A A A A A Aԭ A A A A A A̮ A A A A A Aį Aد A A A A A Aа A A A A A Aȱ Aܱ A A A A A AԲ A A A A A A̳ A A A A A AĴ Aش A A A A A Aе A A A A A Aȶ Aܶ A A A A A AԷ A A A A A A̸ A A A A A AĹ Aع A A A A A Aк A A A A A AȻ Aܻ A A A A A AԼ A A A A A A̽ A A A A A Aľ Aؾ A A A A A Aп A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A AЀ A A A A A Aȁ A܁ A A A A A AԂ A A A A A à A A A A A AĄ A؄ A A A A A AЅ A A A A A AȆ A܆ A A A A A Aԇ A A A A A Ä A A A A A Aĉ A؉ A A A A A AЊ A A A A A Aȋ A܋ A A A A A AԌ A A A A A A̍ A A A A A AĎ A؎ A A A A A AЏ A A A A A AȐ Aܐ A A A A A Aԑ A A A A A A̒ A A A A A Aē Aؓ A A A A A AД A A A A A Aȕ Aܕ A A A A A AԖ A A A A A A̗ A A A A A AĘ Aؘ A A A A A AЙ A A A A A AȚ Aܚ A A A A A Aԛ A A A A A A̜ A A A A A Aĝ A؝ A A A A A AО A A A A A Aȟ Aܟ A A A A A AԠ A A A A A A̡ A A A A A AĢ Aآ A A A A A AУ A A A A A AȤ Aܤ A A A A A Aԥ A A A A A A̦ A A A A A Aħ Aا A A A A A AШ A A A A A Aȩ Aܩ A A A A A AԪ A A A A A A̫ A A A A A AĬ Aج A A A A A AЭ A A A A A AȮ Aܮ A A A A A Aԯ A A A A A A̰ A A A A A Aı Aر A A A A A Aв A A A A A Aȳ Aܳ A A A A A AԴ A A A A A A̵ A A A A A AĶ Aض A A A A A Aз A A A A A Aȸ Aܸ A A A A A AԹ A A A A A A̺ A A A A A AĻ Aػ A A A A A Aм A A A A A AȽ Aܽ A A A A A AԾ A A A A A A̿ A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A À A A A A A Aā A؁ A A A A A AЂ A A A A A Aȃ A܃ A A A A A AԄ A A A A A A̅ A A A A A AĆ A؆ A A A A A AЇ A A A A A AȈ A܈ A A A A A Aԉ A A A A A Å A A A A A Aċ A؋ A A A A A AЌ A A A A A Aȍ A܍ A A A A A AԎ A A A A A Ȁ A A A A A AĐ Aؐ A A A A A AБ A A A A A AȒ Aܒ A A A A A Aԓ A A A A A A̔ A A A A A Aĕ Aؕ A A A A A AЖ A A A A A Aȗ Aܗ A A A A A AԘ A A A A A A̙ A A A A A AĚ Aؚ A A A A A AЛ A A A A A AȜ Aܜ A A A A A Aԝ A A A A A A̞ A A A A A Ağ A؟ A A A A A AР A A A A A Aȡ Aܡ A A A A A AԢ A A A A A Ạ A A A A A AĤ Aؤ A A A A A AХ A A A A A AȦ Aܦ A A A A A Aԧ A A A A A Ą A A A A A Aĩ Aة A A A A A AЪ A A A A A Aȫ Aܫ A A A A A AԬ A A A A A A̭ A A A A A AĮ Aخ A A A A A AЯ A A A A A AȰ Aܰ A A A A A AԱ A A A A A A̲ A A A A A Aij Aس A A A A A Aд A A A A A Aȵ Aܵ A A A A A AԶ A A A A A A̷ A A A A A Aĸ Aظ A A A A A Aй A A A A A AȺ Aܺ A A A A A AԻ A A A A A A̼ A A A A A AĽ Aؽ A A A A A Aо A A A A A Aȿ Aܿ A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A AȀ A܀ A A A A A Aԁ A A A A A  A A A A A Aă A؃ A A A A A AЄ A A A A A Aȅ A܅ A A A A A AԆ A A A A A Ȧ A A A A A AĈ A؈ A A A A A AЉ A A A A A AȊ A܊ A A A A A Aԋ A A A A A Ǎ A A A A A Ač A؍ A A A A A AЎ A A A A A Aȏ A܏ A A A A A AԐ A A A A A Ȃ A A A A A AĒ Aؒ A A A A A AГ A A A A A AȔ Aܔ A A A A A Aԕ A A A A A A̖ A  A  A  A  A  Aė  Aؗ  A  A  A  A  A  AИ  A  A  A  A !A !Aș !Aܙ !A !A !A !A !A !AԚ !A !A !A !A !A !A̛ !A "A "A "A "A "AĜ "A؜ "A "A "A "A "A "AН "A "A "A "A #A #AȞ #Aܞ #A #A #A #A #A #Aԟ #A #A #A #A #A #A̠ #A $A $A $A $A $Aġ $Aء $A $A $A $A $A $AТ $A $A $A $A %A %Aȣ %Aܣ %A %A %A %A %A %AԤ %A %A %A %A %A %Ḁ %A &A &A &A &A &AĦ &Aئ &A &A &A &A &A &AЧ &A &A &A &A 'A 'AȨ 'Aܨ 'A 'A 'A 'A 'A 'Aԩ 'A 'A 'A 'A 'A 'A̪ 'A (A (A (A (A (Aī (Aث (A (A (A (A (A (AЬ (A (A (A (A )A )Aȭ )Aܭ )A )A )A )A )A )AԮ )A )A )A )A )A )A̯ )A *A *A *A *A *Aİ *Aذ *A *A *A *A *A *Aб *A *A *A *A +A +AȲ +Aܲ +A +A +A +A +A +AԳ +A +A +A +A +A +A̴ +A ,A ,A ,A ,A ,Aĵ ,Aص ,A ,A ,A ,A ,A ,Aж ,A ,A ,A ,A -A -Aȷ -Aܷ -A -A -A -A -A -AԸ -A -A -A -A -A -A̹ -A .A .A .A .A .Aĺ .Aغ .A .A .A .A .A .Aл .A .A .A .A /A /Aȼ /Aܼ /A /A /A /A /A /AԽ /A /A /A /A /A /A̾ /A 0A 0A 0A 0A 0AĿ 0Aؿ 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A :A :A :A :A :A :A :A :A :A :A :A :A :A :A :A :A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A <A <A <A <A <A <A <A <A <A <A <A <A <A <A <A <A =A =A =A =A =A =A =A =A =A =A =A =A =A =A =A =A >A >A >A >A >A >A >A >A >A >A >A >A >A >A >A >A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A @A @A @A @A @A @A @A @A @A @A @A @A @A @A @A @A AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA DA DA DA DA DA DA DA DA DA DA DA DA DA DA DA DA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA GA GA GA GA GA GA GA GA GA GA GA GA GA GA GA GA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA IA IA IA IA IA IA IA IA IA IA IA IA IA IA IA IA JA JA JA JA JAĀ JA؀ JA JA JA JA JA JAЁ JA JA JA JA KA KAȂ KA܂ KA KA KA KA KA KAԃ KA KA KA KA KA KĀ KA LA LA LA LA LAą LA؅ LA LA LA LA LA LAІ LA LA LA LA MA MAȇ MA܇ MA MA MA MA MA MAԈ MA MA MA MA MA MẢ MA NA NA NA NA NAĊ NA؊ NA NA NA NA NA NAЋ NA NA NA NA OA OAȌ OA܌ OA OA OA OA OA OAԍ OA OA OA OA OA OA̎ OA PA PA PA PA PAď PA؏ PA PA PA PA PA PAА PA PA PA PA QA QAȑ QAܑ QA QA QA QA QA QAԒ QA QA QA QA QA QA̓ QA RA RA RA RA RAĔ RAؔ RA RA RA RA RA RAЕ RA RA RA RA SA SAȖ SAܖ SA SA SA SA SA SAԗ SA SA SA SA SA SA̘ SA TA TA TA TA TAę TAؙ TA TA TA TA TA TAК TA TA TA TA UA UAț UAܛ UA UA UA UA UA UAԜ UA UA UA UA UA UA̝ UA VA VA VA VA VAĞ VA؞ VA VA VA VA VA VAП VA VA VA VA WA WAȠ WAܠ WA WA WA WA WA WAԡ WA WA WA WA WA WA̢ WA XA XA XA XA XAģ XAأ XA XA XA XA XA XAФ XA XA XA XA YA YAȥ YAܥ YA YA YA YA YA YAԦ YA YA YA YA YA YA̧ YA ZA ZA ZA ZA ZAĨ ZAب ZA ZA ZA ZA ZA ZAЩ ZA ZA ZA ZA [A [AȪ [Aܪ [A [A [A [A [A [Aԫ [A [A [A [A [A [A̬ [A \A \A \A \A \Aĭ \Aح \A \A \A \A \A \AЮ \A \A \A \A ]A ]Aȯ ]Aܯ ]A ]A ]A ]A ]A ]A԰ ]A ]A ]A ]A ]A ]A̱ ]A ^A ^A ^A ^A ^AIJ ^Aز ^A ^A ^A ^A ^A ^Aг ^A ^A ^A ^A _A _Aȴ _Aܴ _A _A _A _A _A _AԵ _A _A _A _A _A _A̶ _A `A `A `A `A `Aķ `Aط `A `A `A `A `A `Aи `A `A `A `A aA aAȹ aAܹ aA aA aA aA aA aAԺ aA aA aA aA aA aA̻ aA bA bA bA bA bAļ bAؼ bA bA bA bA bA bAн bA bA bA bA cA cAȾ cAܾ cA cA cA cA cA cAԿ cA cA cA cA cA cA cA dA dA dA dA dA dA dA dA dA dA dA dA dA dA dA dA eA eA eA eA eA eA eA eA eA eA eA eA eA eA eA eA fA fA fA fA fA fA fA fA fA fA fA fA fA fA fA fA gA gA gA gA gA gA gA gA gA gA gA gA gA gA gA gA hA hA hA hA hA hA hA hA hA hA hA hA hA hA hA hA iA iA iA iA iA iA iA iA iA iA iA iA iA iA iA iA jA jA jA jA jA jA jA jA jA jA jA jA jA jA jA jA kA kA kA kA kA kA kA kA kA kA kA kA kA kA kA kA lA lA lA lA lA lA lA lA lA lA lA lA lA lA lA lA mA mA mA mA mA mA mA mA mA mA mA mA mA mA mA mA nA nA nA nA nA nA nA nA nA nA nA nA nA nA nA nA oA oA oA oA oA oA oA oA oA oA oA oA oA oA oA oA pA pA pA pA pA pA pA pA pA pA pA pA pA pA pA pA qA qA qA qA qA qA qA qA qA qA qA qA qA qA qA qA rA rA rA rA rA rA rA rA rA rA rA rA rA rA rA rA sA sA sA sA sA sA sA sA sA sA sA sA sA sA sA sA tA tA tA tA tA tA tA tA tA tA tA tA tA tA tA tA uA uA uA uA uA uA uA uA uA uA uA uA uA uA uA uA vA vA vA vA vA vA vA vA vA vA vA vA vA vA vA vA wA wA wA wA wA wA wA wA wA wA wA wA wA wA wA wA xA xA xA xA xA xA xA xA xA xA xA xA xA xA xA xA yA yA yA yA yA yA yA yA yA yA yA yA yA yA yA yA zA zA zA zA zA zA zA zA zA zA zA zA zA zA zA zA {A {A {A {A {A {A {A {A {A {A {A {A {A {A {A {A |A |A |A |A |A |A |A |A |A |A |A |A |A |A |A |A }A }A }A }A }A }A }A }A }AԀ }A }A }A }A }A }Á }A ~A ~A ~A ~A ~AĂ ~A؂ ~A ~A ~A ~A ~A ~AЃ ~A ~A ~A ~A A AȄ A܄ A A A A A Aԅ A A A A A Ă A A A A A Ać A؇ A A A A A AЈ A A A A A Aȉ A܉ A A A A A AԊ A A A A A A̋ A A A A A AČ A، A A A A A AЍ A A A A A AȎ A܎ A A A A A Aԏ A A A A A A̐ A A A A A Ađ Aؑ A A A A A AВ A A A A A Aȓ Aܓ A A A A A AԔ A A A A A A̕ A A A A A AĖ Aؖ A A A A A AЗ A A A A A AȘ Aܘ A A A A A Aԙ A A A A A A̚ A A A A A Aě A؛ A A A A A AМ A A A A A Aȝ Aܝ A A A A A AԞ A A A A A A̟ A A A A A AĠ Aؠ A A A A A AС A A A A A AȢ Aܢ A A A A A Aԣ A A A A A A̤ A A A A A Aĥ Aإ A A A A A AЦ A A A A A Aȧ Aܧ A A A A A AԨ A A A A A A̩ A A A A A AĪ Aت A A A A A AЫ A A A A A AȬ Aܬ A A A A A Aԭ A A A A A A̮ A A A A A Aį Aد A A A A A Aа A A A A A Aȱ Aܱ A A A A A AԲ A A A A A A̳ A A A A A AĴ Aش A A A A A Aе A A A A A Aȶ Aܶ A A A A A AԷ A A A A A A̸ A A A A A AĹ Aع A A A A A Aк A A A A A AȻ Aܻ A A A A A AԼ A A A A A A̽ A A A A A Aľ Aؾ A A A A A Aп A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A AЀ A A A A A Aȁ A܁ A A A A A AԂ A A A A A à A A A A A AĄ A؄ A A A A A AЅ A A A A A AȆ A܆ A A A A A Aԇ A A A A A Ä A A A A A Aĉ A؉ A A A A A AЊ A A A A A Aȋ A܋ A A A A A AԌ A A A A A A̍ A A A A A AĎ A؎ A A A A A AЏ A A A A A AȐ Aܐ A A A A A Aԑ A A A A A A̒ A A A A A Aē Aؓ A A A A A AД A A A A A Aȕ Aܕ A A A A A AԖ A A A A A A̗ A A A A A AĘ Aؘ A A A A A AЙ A A A A A AȚ Aܚ A A A A A Aԛ A A A A A A̜ A A A A A Aĝ A؝ A A A A A AО A A A A A Aȟ Aܟ A A A A A AԠ A A A A A A̡ A A A A A AĢ Aآ A A A A A AУ A A A A A AȤ Aܤ A A A A A Aԥ A A A A A A̦ A A A A A Aħ Aا A A A A A AШ A A A A A Aȩ Aܩ A A A A A AԪ A A A A A A̫ A A A A A AĬ Aج A A A A A AЭ A A A A A AȮ Aܮ A A A A A Aԯ A A A A A A̰ A A A A A Aı Aر A A A A A Aв A A A A A Aȳ Aܳ A A A A A AԴ A A A A A A̵ A A A A A AĶ Aض A A A A A Aз A A A A A Aȸ Aܸ A A A A A AԹ A A A A A A̺ A A A A A AĻ Aػ A A A A A Aм A A A A A AȽ Aܽ A A A A A AԾ A A A A A A̿ A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A À A A A A A Aā A؁ A A A A A AЂ A A A A A Aȃ A܃ A A A A A AԄ A A A A A A̅ A A A A A AĆ A؆ A A A A A AЇ A A A A A AȈ A܈ A A A A A Aԉ A A A A A Å A A A A A Aċ A؋ A A A A A AЌ A A A A A Aȍ A܍ A A A A A AԎ A A A A A Ȁ A A A A A AĐ Aؐ A A A A A AБ A A A A A AȒ Aܒ A A A A A Aԓ A A A A A A̔ A A A A A Aĕ Aؕ A A A A A AЖ A A A A A Aȗ Aܗ A A A A A AԘ A A A A A A̙ A A A A A AĚ Aؚ A A A A A AЛ A A A A A AȜ Aܜ A A A A A Aԝ A A A A A A̞ A A A A A Ağ A؟ A A A A A AР A A A A A Aȡ Aܡ A A A A A AԢ A A A A A Ạ A A A A A AĤ Aؤ A A A A A AХ A A A A A AȦ Aܦ A A A A A Aԧ A A A A A Ą A A A A A Aĩ Aة A A A A A AЪ A A A A A Aȫ Aܫ A A A A A AԬ A A A A A A̭ A A A A A AĮ Aخ A A A A A AЯ A A A A A AȰ Aܰ A A A A A AԱ A A A A A A̲ A A A A A Aij Aس A A A A A Aд A A A A A Aȵ Aܵ A A A A A AԶ A A A A A A̷ A A A A A Aĸ Aظ A A A A A Aй A A A A A AȺ Aܺ A A A A A AԻ A A A A A A̼ A A A A A AĽ Aؽ A A A A A Aо A A A A A Aȿ Aܿ A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A  A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A AȀ A܀ A A A A A Aԁ A A A A A  A A A A A Aă A؃ A A A A A AЄ A A A A A Aȅ A܅ A A A A A AԆ A A A A A Ȧ A A A A A AĈ A؈ A A A A A AЉ A A A A A AȊ A܊ A A A A A Aԋ A A A A A Ǎ A A A A A Ač A؍ A A A A A AЎ A A A A A Aȏ A܏ A A A A A AԐ A A A A A Ȃ A A A A A AĒ Aؒ A A A A A AГ A A A A A AȔ Aܔ A A A A A Aԕ A A A A A A̖ A  A  A  A  A  Aė  Aؗ  A  A  A  A  A  AИ  A  A  A  A !A !Aș !Aܙ !A !A !A !A !A !AԚ !A !A !A !A !A !A̛ !A "A "A "A "A "AĜ "A؜ "A "A "A "A "A "AН "A "A "A "A #A #AȞ #Aܞ #A #A #A #A #A #Aԟ #A #A #A #A #A #A̠ #A $A $A $A $A $Aġ $Aء $A $A $A $A $A $AТ $A $A $A $A %A %Aȣ %Aܣ %A %A %A %A %A %AԤ %A %A %A %A %A %Ḁ %A &A &A &A &A &AĦ &Aئ &A &A &A &A &A &AЧ &A &A &A &A 'A 'AȨ 'Aܨ 'A 'A 'A 'A 'A 'Aԩ 'A 'A 'A 'A 'A 'A̪ 'A (A (A (A (A (Aī (Aث (A (A (A (A (A (AЬ (A (A (A (A )A )Aȭ )Aܭ )A )A )A )A )A )AԮ )A )A )A )A )A )A̯ )A *A *A *A *A *Aİ *Aذ *A *A *A *A *A *Aб *A *A *A *A +A +AȲ +Aܲ +A +A +A +A +A +AԳ +A +A +A +A +A +A̴ +A ,A ,A ,A ,A ,Aĵ ,Aص ,A ,A ,A ,A ,A ,Aж ,A ,A ,A ,A -A -Aȷ -Aܷ -A -A -A -A -A -AԸ -A -A -A -A -A -A̹ -A .A .A .A .A .Aĺ .Aغ .A .A .A .A .A .Aл .A .A .A .A /A /Aȼ /Aܼ /A /A /A /A /A /AԽ /A /A /A /A /A /A̾ /A 0A 0A 0A 0A 0AĿ 0Aؿ 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 2A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 5A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 6A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 7A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 8A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A 9A :A :A :A :A :A :A :A :A :A :A :A :A :A :A :A :A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A ;A <A <A <A <A <A <A <A <A <A <A <A <A <A <A <A <A =A =A =A =A =A =A =A =A =A =A =A =A =A =A =A =A >A >A >A >A >A >A >A >A >A >A >A >A >A >A >A >A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A ?A @A @A @A @A @A @A @A @A @A @A @A @A @A @A @A @A AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA BA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA DA DA DA DA DA DA DA DA DA DA DA DA DA DA DA DA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA EA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA FA GA GA GA GA GA GA GA GA GA GA GA GA GA GA GA GA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA IA IA IA IA IA IA IA IA IA IA IA IA IA IA IA IA JA JA JA JA JAĀ JA؀ JA JA JA JA JA JAЁ JA JA JA JA KA KAȂ KA܂ KA KA KA KA KA KAԃ KA KA KA KA KA KĀ KA LA LA LA LA LAą LA؅ LA LA LA LA LA LAІ LA LA LA LA MA MAȇ MA܇ MA MA MA MA MA MAԈ MA MA MA MA MA MẢ MA NA NA NA NA NAĊ NA؊ NA NA NA NA NA NAЋ NA NA NA NA OA OAȌ OA܌ OA OA OA OA OA OAԍ OA OA OA OA OA OA̎ OA PA PA PA PA PAď PA؏ PA PA PA PA PA PAА PA PA PA PA QA QAȑ QAܑ QA QA QA QA QA QAԒ QA QA QA QA QA QA̓ QA RA RA RA RA RAĔ RAؔ RA RA RA RA RA RAЕ RA RA RA RA SA SAȖ SAܖ SA SA SA SA SA SAԗ SA SA SA SA SA SA̘ SA TA TA TA TA TAę TAؙ TA TA TA TA TA TAК TA TA TA TA UA UAț UAܛ UA UA UA UA UA UAԜ UA UA UA UA UA UA̝ UA VA VA VA VA VAĞ VA؞ VA VA VA VA VA VAП VA VA VA VA WA WAȠ WAܠ WA WA WA WA WA WAԡ WA WA WA WA WA WA̢ WA XA XA XA XA XAģ XAأ XA XA XA XA XA XAФ XA XA XA XA YA YAȥ YAܥ YA YA YA YA YA YAԦ YA YA YA YA YA YA̧ YA ZA ZA ZA ZA ZAĨ ZAب ZA ZA ZA ZA ZA ZAЩ ZA ZA ZA ZA [A [AȪ [Aܪ [A [A [A [A [A [Aԫ [A [A [A [A [A [A̬ [A \A \A \A \A \Aĭ \Aح \A \A \A \A \A \AЮ \A \A \A \A ]A ]Aȯ ]Aܯ ]A ]A ]A ]A ]A ]A԰ ]A ]A ]A ]A ]A ]A̱ ]A ^A ^A ^A ^A ^AIJ ^Aز ^A ^A ^A ^A ^A ^Aг ^A ^A ^A ^A _A _Aȴ _Aܴ _A _A _A _A _A _AԵ _A _A _A _A _A _A̶ _A `A `A `A `A `Aķ `Aط `A `A `A `A `A `Aи `A `A `A `A aA aAȹ aAܹ aA aA aA aA aA aAԺ aA aA aA aA aA aA̻ aA bA bA bA bA bAļ bAؼ bA bA bA bA bA bAн bA bA bA bA cA cAȾ cAܾ cA cA cA cA cA cAԿ cA cA cA cA cA cA cA dA dA dA dA dA dA dA dA dA dA dA dA dA dA dA dA eA eA eA eA eA eA eA eA eA eA eA eA eA eA eA eA fA fA fA fA fA fA fA fA fA fA fA fA fA fA fA fA gA gA gA gA gA gA gA gA gA gA gA gA gA gA gA gA hA hA hA hA hA hA hA hA hA hA hA hA hA hA hA hA iA iA iA iA iA iA iA iA iA iA iA iA iA iA iA iA jA jA jA jA jA jA jA jA jA jA jA jA jA jA jA jA kA kA kA kA kA kA kA kA kA kA kA kA kA kA kA kA lA lA lA lA lA lA lA lA lA lA lA lA lA lA lA lA mA mA mA mA mA mA mA mA mA mA mA mA mA mA mA mA nA nA nA nA nA nA nA nA nA nA nA nA nA nA nA nA oA oA oA oA oA oA oA oA oA oA oA oA oA oA oA oA pA @<<<=@===>@>>>?@???B@@@@@A@AAA@BBBC@CCCD@DDD E@EE@EF@FFFG@GGGH@HHH`I@I II@J@JJJ@K@KKKL@LL`@LM@M`MMN@NN`! NO@OO`OP@@PPPQ@QQQR@RRRS@SSST@TTTU@UUUV @@VVWV@WWWX@XXXrr@sst`tt uuu@vvw`ww xxx@yyz`zz {{{@||}`}} ~~~@` Y@YYYZ@ZZZ[@[[[\@\\\]@]]]^@^^^_@___`@```a@aaab@bbbc@cccd@dddeh i@e`j``j@@`@@@ A A qV@`ikgo.buildidinternal/cpu.processOptionsinternal/cpu.indexBytetype..eq.internal/cpu.CacheLinePadruntime/internal/atomic.LoadAcqruntime/internal/atomic.Load8runtime/internal/atomic.And8runtime/internal/atomic.Or8runtime/internal/atomic.Andruntime/internal/atomic.Orruntime/internal/atomic.StoreRelruntime/internal/atomic.Store8runtime/internal/atomic.Casp1runtime/internal/atomic.CasRelruntime/internal/atomic.Loaduintruntime/internal/atomic.Loadint64runtime/internal/atomic.Xaddint64runtime/internal/atomic.Loadruntime/internal/atomic.Loadpruntime/internal/atomic.Load64runtime/internal/atomic.Xaddruntime/internal/atomic.Xadd64runtime/internal/atomic.Xadduintptrruntime/internal/atomic.Xchgruntime/internal/atomic.Xchg64runtime/internal/atomic.Cas64runtime/internal/atomic.Storeruntime/internal/atomic.Store64runtime/internal/atomic.Casruntime/internal/atomic.Casuintptrruntime/internal/atomic.Storeuintptrruntime/internal/atomic.Loaduintptrruntime/internal/atomic.StorepNoWBmemeqbodyruntime.memequalruntime.memequal_varlenmemchrinternal/bytealg.IndexByteStringruntime.memhash128runtime.memequal0runtime.memequal8runtime.memequal16runtime.memequal32runtime.memequal64runtime.memequal128runtime.f32equalruntime.f64equalruntime.c64equalruntime.c128equalruntime.strequalruntime.interequalruntime.nilinterequalruntime.efaceeqruntime.ifaceeqruntime.alginitruntime.atomicwbruntime.puintptr.ptrruntime.(*wbBuf).putFastruntime.atomicstorepruntime.cgocallruntime.cgoIsGoPointerruntime.activeModulesruntime.cgoInRangeruntime.cgoCheckWriteBarrierruntime.cgoCheckMemmoveruntime.cgoCheckSliceCopyruntime.addruntime.cgoCheckTypedBlockruntime.spanOfUncheckedruntime.arenaIndexruntime.(*mSpanStateBox).getruntime.heapBitsForAddrruntime.heapBits.bitsruntime.heapBits.nextruntime.add1runtime.cgoCheckBitsruntime.addbruntime.cgoCheckUsingTyperuntime.makechanruntime/internal/math.MulUintptrruntime.(*hchan).raceaddrruntime.chansend1runtime.chansendruntime.fullruntime.lockruntime.lockWithRankruntime.unlockruntime.unlockWithRankruntime.(*waitq).enqueueruntime.chanbufruntime.cputicksruntime.nanotimeruntime.sendruntime.sendDirectruntime.recvDirectruntime.closechanruntime.(*gList).pushruntime.(*guintptr).setruntime.(*gList).emptyruntime.(*gList).popruntime.guintptr.ptrruntime.emptyruntime.chanrecv1runtime.chanrecvruntime.recvruntime.chanparkcommitruntime.(*waitq).dequeueruntime.gogetenvruntime.envKeyEqualruntime.(*TypeAssertionError).Errorruntime.errorString.Errorruntime.plainError.Errorruntime.boundsError.Errorruntime.appendIntStrruntime.itoaruntime.printanyruntime.printanycustomtyperuntime.panicwrapruntime.memhashFallbackruntime.readUnaligned64runtime.rotl_31runtime.readUnaligned32runtime.memhash32Fallbackruntime.memhash64Fallbackruntime.(*timeHistogram).recordruntime.getitabruntime.(*_type).nameOffruntime.(*itabTableType).findruntime.itabHashFuncruntime.itabAddruntime.(*itabTableType).addruntime.(*itab).initruntime.(*_type).typeOffruntime.name.isExportedruntime.itabsinitruntime.convTstringruntime.convT2Enoptrruntime.assertE2I2runtime.iterate_itabsruntime.(*lfstack).pushruntime.lfstackPackruntime.lfstackUnpackruntime.(*lfstack).popruntime.lfnodeValidateruntime.lock2runtime.unlock2runtime.notewakeupruntime.notesleepruntime.notetsleepruntime.notetsleepgruntime.acquiremruntime.releasemruntime.clearIdleIDruntime.checkTimeoutsruntime.beforeIdleruntime.handleAsyncEventruntime.handleEventruntime.lockRank.Stringruntime.mallocinitruntime.(*mheap).sysAllocruntime.alignUpruntime.(*fixalloc).freeruntime.sysMapruntime.sysReserveAlignedruntime.sysFreeruntime.nextFreeFastruntime/internal/sys.Ctz64runtime.(*mspan).baseruntime.(*mcache).nextFreeruntime.mallocgcruntime.getMCacheruntime.divRoundUpruntime.makeSpanClassruntime.newobjectruntime.newarrayruntime.profileallocruntime.nextSampleruntime.fastexprandruntime.fastrandruntime.fastlog2runtime.float64bitsruntime.persistentallocruntime.persistentalloc1runtime.(*notInHeap).addruntime.inPersistentAllocruntime.(*linearAlloc).allocruntime.(*hmap).incrnoverflowruntime.(*hmap).newoverflowruntime.(*bmap).overflowruntime.(*bmap).setoverflowruntime.(*hmap).createOverflowruntime.makemap_smallruntime.makemapruntime.overLoadFactorruntime.bucketShiftruntime.makeBucketArrayruntime.roundupsizeruntime.mapaccess2runtime.bucketMaskruntime.tophashruntime.(*maptype).indirectkeyruntime.(*maptype).hashMightPanicruntime.mapaccessKruntime.mapassignruntime.tooManyOverflowBucketsruntime.(*maptype).indirectelemruntime.(*hmap).growingruntime.mapiterinitruntime.mapiternextruntime.(*hmap).sameSizeGrowruntime.(*maptype).reflexivekeyruntime.(*hmap).oldbucketmaskruntime.(*hmap).noldbucketsruntime.hashGrowruntime.growWorkruntime.evacuateruntime.advanceEvacuationMarkruntime.bucketEvacuatedruntime.mapaccess1_fast32runtime.(*bmap).keysruntime.mapaccess2_fast32runtime.mapassign_fast32runtime.growWork_fast32runtime.evacuate_fast32runtime.mapaccess1_fast64runtime.mapassign_fast64ptrruntime.mapdelete_fast64runtime.growWork_fast64runtime.evacuate_fast64runtime.typedmemmoveruntime.reflectcallmoveruntime.typedslicecopyruntime.typedmemclrruntime.memclrHasPointersruntime.(*mspan).refillAllocCacheruntime.(*gcBits).bytepruntime.(*mspan).nextFreeIndexruntime.badPointerruntime.findObjectruntime.spanOfruntime.heapBits.nextArenaruntime.heapBits.forwardruntime.heapBits.forwardOrBoundaryruntime.bulkBarrierPreWriteruntime.heapBits.isPointerruntime.bulkBarrierPreWriteSrcOnlyruntime.bulkBarrierBitmapruntime.typeBitsBulkBarrierruntime.heapBits.initSpanruntime.heapBitsSetTyperuntime.heapBitsSetTypeGCProgruntime.progToPointerMaskruntime.runGCProgruntime.subtract1runtime.subtractbruntime.materializeGCProgruntime.allocmcacheruntime.freemcacheruntime.(*mcache).refillruntime.spanClass.sizeclassruntime.traceHeapAllocruntime.(*mcache).allocLargeruntime.bool2intruntime.(*mcache).releaseAllruntime.(*mcache).prepareForSweepruntime.(*mcentral).cacheSpanruntime.(*mcentral).partialSweptruntime.(*mcentral).partialUnsweptruntime.(*mcentral).fullUnsweptruntime.(*mcentral).uncacheSpanruntime.(*mcentral).fullSweptruntime.(*mcentral).growruntime.startCheckmarksruntime.endCheckmarksruntime.setCheckmarkruntime.markBits.isMarkedruntime.sysAllocruntime.sysReserveruntime.queuefinalizerruntime.wakefingruntime.(*fixalloc).allocruntime.gcinitruntime.readgogcruntime.atoi32runtime.gcenableruntime.(*gcControllerState).startCycleruntime.float64frombitsruntime.(*gcControllerState).reviseruntime.(*gcControllerState).endCycleruntime.(*gcControllerState).enlistWorkerruntime.fastrandnruntime.preemptoneruntime.muintptr.ptrruntime.(*gcControllerState).findRunnableGCWorkerruntime.pollFractionalWorkerExitruntime.gcSetTriggerRatioruntime.gcEffectiveGrowthRatioruntime.gcWaitOnMarkruntime.goparkunlockruntime.gcTrigger.testruntime.gcStartruntime.semacquireruntime.setGCPhaseruntime.gcBgMarkPrepareruntime.semreleaseruntime.traceGCSTWStartruntime.traceGCStartruntime.gcMarkDoneruntime.gcMarkTerminationruntime.itoaDivruntime.printunlockruntime.traceGCDoneruntime.gcBgMarkStartWorkersruntime.noteclearruntime.gcBgMarkWorkerruntime.(*muintptr).setruntime.gcMarkWorkAvailableruntime.(*gcWork).emptyruntime.(*lfstack).emptyruntime.gcMarkruntime.gcSweepruntime.(*sweepClass).clearruntime.gcResetMarkStateruntime.clearpoolsruntime.fmtNSAsMSruntime.gcMarkRootPrepareruntime.gcMarkRootPrepare.func1runtime.gcMarkRootCheckruntime.readgstatusruntime.markrootruntime.markrootBlockruntime.markrootFreeGStacksruntime.(*gList).pushAllruntime.markrootSpansruntime.gcAssistAllocruntime.traceGCMarkAssistDoneruntime.traceGCMarkAssistStartruntime.gcAssistAlloc1runtime.gcWakeAllAssistsruntime.(*gQueue).popListruntime.gcParkAssistruntime.(*gQueue).pushBackruntime.gcFlushBgCreditruntime.(*gQueue).emptyruntime.(*gQueue).popruntime.scanstackruntime.isShrinkStackSaferuntime.(*stackScanState).buildIndexruntime.(*stackScanState).findObjectruntime.(*stackObject).setTyperuntime.dematerializeGCProgruntime.scanframeworkerruntime.funcInfo.validruntime.gcDrainruntime.(*gcWork).tryGetFastruntime.gcDrainNruntime.scanblockruntime.scanobjectruntime.(*gcWork).putFastruntime.scanConservativeruntime.(*mspan).objIndexruntime.(*mspan).isFreeruntime.(*gcBits).bitpruntime.shaderuntime.greyobjectruntime.(*mspan).markBitsForIndexruntime.markBits.setMarkedruntime.pageIndexOfruntime.gcDumpObjectruntime.gcmarknewobjectruntime.gcMarkTinyAllocsruntime.heapRetainedruntime.(*sysMemStat).loadruntime.gcPaceScavengerruntime.wakeScavengertime.stopTimerruntime.scavengeSleeptime.resetTimerruntime.resettimerruntime.bgscavengeruntime.(*pageAlloc).scavengeruntime.addrRange.sizeruntime.offAddr.diffruntime.printScavTraceruntime.(*pageAlloc).scavengeStartGenruntime.(*pageAlloc).scavengeReserveruntime.alignDownruntime.(*pageAlloc).scavengeUnreserveruntime.(*pageAlloc).scavengeOneruntime.chunkIndexruntime.pallocSum.maxruntime.(*pageAlloc).chunkOfruntime.chunkBaseruntime.(*pageAlloc).scavengeOne.func2runtime.(*pageAlloc).scavengeOne.func1runtime.(*pageAlloc).scavengeRangeLockedruntime.fillAlignedruntime.fillAligned.func1runtime.(*pallocData).hasScavengeCandidateruntime.(*pallocData).findScavengeCandidateruntime/internal/sys.LeadingZeros64runtime.(*stackScanState).putPtrruntime.(*stackScanState).getPtrruntime.(*stackScanState).addObjectruntime.binarySearchTreeruntime.(*sweepClass).updateruntime.(*sweepClass).loadruntime.(*mheap).nextSpanForSweepruntime.finishsweep_mruntime.bgsweepruntime.sweeponeruntime.readyForScavengerruntime.(*mspan).ensureSweptruntime.(*mspan).sweepruntime.markBits.setMarkedNonAtomicruntime.(*mspan).markBitsForBaseruntime.(*mspan).allocBitsForIndexruntime.(*markBits).advanceruntime.clobberfreeruntime.(*mspan).countAllocruntime.(*mspan).reportZombiesruntime.deductSweepCreditruntime.(*gcWork).initruntime.(*gcWork).putruntime.(*gcWork).putBatchruntime.(*gcWork).tryGetruntime.(*gcWork).disposeruntime.(*gcWork).balanceruntime.(*workbuf).checknonemptyruntime.(*workbuf).checkemptyruntime.getemptyruntime.putemptyruntime.putfullruntime.trygetfullruntime.handoffruntime.prepareFreeWorkbufsruntime.(*mSpanList).takeAllruntime.(*mSpanList).isEmptyruntime.freeSomeWbufsruntime.recordspanruntime.inHeapOrStackruntime.spanOfHeapruntime.(*mheap).initruntime.(*fixalloc).initruntime.(*mcentral).initruntime.(*mheap).reclaimruntime.(*mheap).reclaimChunkruntime.(*mheap).allocruntime.(*mheap).allocManualruntime.(*mheap).setSpansruntime.(*mheap).allocNeedsZeroruntime.(*mheap).allocMSpanLockedruntime.(*mheap).allocSpanruntime.(*mheap).tryAllocMSpanruntime.spanAllocType.manualruntime.(*mSpanStateBox).setruntime.(*mheap).growruntime.(*mheap).freeSpanruntime.(*mheap).freeManualruntime.(*mheap).freeSpanLockedruntime.(*mheap).freeMSpanLockedruntime.(*mspan).initruntime.(*mSpanList).removeruntime.(*mSpanList).insertruntime.spanHasSpecialsruntime.spanHasNoSpecialsruntime.addspecialruntime.setprofilebucketruntime.freespecialruntime.(*gcBitsArena).tryAllocruntime.newMarkBitsruntime.newAllocBitsruntime.nextMarkBitArenaEpochruntime.newArenaMayUnlockruntime.(*pageAlloc).initruntime.(*pageAlloc).growruntime.(*pageAlloc).updateruntime.addrsToSummaryRangeruntime.(*pageAlloc).allocRangeruntime.chunkPageIndexruntime.(*pageAlloc).findMappedAddrruntime.(*pageAlloc).findruntime.levelIndexToOffAddrruntime.pallocSum.startruntime.pallocSum.endruntime.offAddrToLevelIndexruntime.(*pageAlloc).allocruntime.(*pageAlloc).freeruntime.(*pallocBits).freeruntime.(*pallocBits).freeAllruntime.(*pallocBits).free1runtime.(*pageBits).clearruntime.mergeSummariesruntime.pallocSum.unpackruntime.packPallocSumruntime.(*pageAlloc).sysInitruntime.(*pageAlloc).sysGrowruntime.blockAlignSummaryRangeruntime.(*pageCache).allocruntime.(*pageCache).allocNruntime.findBitRange64runtime.(*pageCache).flushruntime.(*pageCache).emptyruntime.(*pageAlloc).allocToCacheruntime.(*pallocBits).pages64runtime.(*pageBits).block64runtime.(*pageBits).setRangeruntime.(*pageBits).setruntime.(*pageBits).setAllruntime.(*pageBits).clearRangeruntime.(*pageBits).clearAllruntime.(*pageBits).popcntRangeruntime.(*pallocBits).summarizeruntime.(*pallocBits).findruntime.(*pallocBits).find1runtime.(*pallocBits).findSmallNruntime.(*pallocBits).findLargeNruntime.(*pallocData).allocRangeruntime.(*pallocBits).allocRangeruntime.(*pallocData).allocAllruntime.(*pallocBits).allocAllruntime.newBucketruntime.(*bucket).mpruntime.(*bucket).bpruntime.stkbucketruntime.(*bucket).stkruntime.eqsliceruntime.mProf_NextCycleruntime.mProf_Flushruntime.mProf_FlushLockedruntime.(*memRecordCycle).addruntime.mProf_Mallocruntime.mProf_Freeruntime.blockeventruntime.blocksampledruntime.saveblockeventruntime.gcallersruntime.traceallocruntime.tracebackruntime.tracefreeruntime.tracegcruntime.makeAddrRangeruntime.addrRange.removeGreaterEqualruntime.(*addrRanges).initruntime.(*addrRanges).findSuccruntime.addrRange.containsruntime.(*addrRanges).findAddrGreaterEqualruntime.(*addrRanges).addruntime.offAddr.lessThanruntime.offAddr.equalruntime.(*addrRanges).removeLastruntime.offAddr.subruntime.(*addrRanges).removeGreaterEqualruntime.(*addrRanges).cloneIntoruntime.(*spanSet).pushruntime.(*spanSet).popruntime.(*headTailIndex).loadruntime.headTailIndex.splitruntime.headTailIndex.headruntime.(*headTailIndex).casruntime.(*spanSet).resetruntime.(*headTailIndex).resetruntime.(*spanSetBlockAlloc).allocruntime.(*spanSetBlockAlloc).freeruntime.(*headTailIndex).incTailruntime.init.2runtime.flushmcacheruntime.(*sysMemStat).addruntime.(*consistentHeapStats).acquireruntime.(*consistentHeapStats).releaseruntime.(*wbBuf).resetruntime.wbBufFlushruntime.(*wbBuf).discardruntime.wbBufFlush1runtime.netpollGenericInitruntime.write1runtime.sigpanicruntime.panicmemruntime.mpreinitruntime.osinitruntime.panicCheck1runtime.panicCheck2runtime.goPanicIndexruntime.goPanicIndexUruntime.goPanicSliceAlenruntime.goPanicSliceAlenUruntime.goPanicSliceAcapruntime.goPanicSliceAcapUruntime.goPanicSliceBruntime.goPanicSliceBUruntime.goPanicSlice3Alenruntime.goPanicSlice3AlenUruntime.panicshiftruntime.panicdivideruntime.testdefersizesruntime.deferclassruntime.totaldefersizeruntime.init.3runtime.newdeferruntime.freedeferruntime.freedeferpanicruntime.freedeferfnruntime.deferreturnruntime.deferArgsruntime.preprintpanicsruntime.printpanicsruntime.addOneOpenDeferFrameruntime.runOpenDeferFrameruntime.readvarintUnsaferuntime.reflectcallSaveruntime.gopanicruntime.getargpruntime.gorecoverruntime.throwruntime.recoveryruntime.fatalthrowruntime.fatalpanicruntime.crashruntime.startpanic_mruntime.dopanic_mruntime.canpanicruntime.suspendGruntime.resumeGruntime.asyncPreempt2runtime.init.4runtime.funcPCruntime.recordForPanicruntime.printlockruntime.gwriteruntime.printspruntime.printnlruntime.printboolruntime.printfloatruntime.printcomplexruntime.printuintruntime.printintruntime.printhexruntime.printpointerruntime.printuintptrruntime.printstringruntime.bytesruntime.printsliceruntime.hexdumpWordsruntime.mainruntime.lockOSThreadruntime.unlockOSThreadruntime.init.5runtime.forcegchelperruntime.Goschedruntime.goparkruntime.goreadyruntime.acquireSudogruntime.releaseSudogruntime.badmcallruntime.badmcall2runtime.badreflectcallruntime.badmorestackg0runtime.writeruntime.badmorestackgsignalruntime.badctxtruntime.allgaddruntime.atomicAllGruntime.cpuinitinternal/cpu.Initializeruntime.schedinitruntime.moduledataverifyruntime.fastrandinitruntime.goenvsruntime.dumpgstatusruntime.checkmcountruntime.mReserveIDruntime.mcommoninitruntime.int64Hashruntime.readyruntime.freezetheworldruntime.casfrom_Gscanstatusruntime.castogscanstatusruntime.casgstatusruntime.casGToPreemptScanruntime.casGFromPreemptedruntime.stopTheWorldWithSemaruntime.startTheWorldWithSemaruntime.netpollinitedruntime.(*puintptr).setruntime.traceGCSTWDoneruntime.mstartruntime.mstart1runtime.mstartm0runtime.mParkruntime.mexitruntime.forEachPruntime.runSafePointFnruntime.allocmruntime.newextramruntime.unlockextraruntime.oneNewExtraMruntime.lockextraruntime.newmruntime.newm1runtime.newosprocruntime.mDoFixupruntime.stopmruntime.mspinningruntime.startmruntime.mgetruntime.handoffpruntime.wakepruntime.stoplockedmruntime.startlockedmruntime.gcstopmruntime.executeruntime.findrunnableruntime.(*randomEnum).nextruntime.(*randomEnum).doneruntime.(*randomEnum).positionruntime.(*randomOrder).startruntime.pollWorkruntime.wakeNetPollerruntime.resetspinningruntime.injectglistruntime.globrunqputbatchruntime.(*gQueue).pushBackAllruntime.injectglist.func1runtime.scheduleruntime.checkTimersruntime.parkunlock_cruntime.park_mruntime.dropgruntime.setMNoWBruntime.setGNoWBruntime.goschedImplruntime.globrunqputruntime.gosched_mruntime.gopreempt_mruntime.traceGoPreemptruntime.preemptParkruntime.goyieldruntime.goyield_mruntime.goexit1runtime.traceGoEndruntime.goexit0runtime.saveruntime.reentersyscallruntime.entersyscall_sysmonruntime.entersyscall_gcwaitruntime.exitsyscallfastruntime.exitsyscallfast_reacquiredruntime.exitsyscallfast_pidleruntime.exitsyscall0runtime.schedEnabledruntime.malgruntime.round2runtime.newprocruntime.newproc1runtime.gostartcallfnruntime.gostartcallruntime.saveAncestorsruntime.gfputruntime.gfgetruntime.gfpurgeruntime.badunlockosthreadruntime.(*p).initruntime.pMask.setruntime.(*p).destroyruntime.globrunqputheadruntime.(*gQueue).pushruntime.procresizeruntime.(*randomOrder).resetruntime.gcdruntime.traceGoSchedruntime.traceGomaxprocsruntime.acquirepruntime.wirepruntime.releasepruntime.incidlelockedruntime.checkdeadruntime.mcountruntime.preemptallruntime.schedtraceruntime.waitReason.Stringruntime.schedEnableUserruntime.mputruntime.globrunqgetruntime.pMask.readruntime.pMask.clearruntime.updateTimerPMaskruntime.pidleputruntime.pidlegetruntime.runqemptyruntime.runqputruntime.(*guintptr).casruntime.runqputslowruntime.runqputbatchruntime.runqgetruntime.runqgrabruntime.runqstealruntime.doInitruntime.gotracebackruntime.argsruntime.goargsruntime.argv_indexruntime.gostringnocopyruntime.goenvs_unixruntime.testAtomic64runtime.checkruntime.timedivruntime.parsedebugvarsruntime.(*rwmutex).rlockruntime.(*rwmutex).runlockruntime.readyWithTimeruntime.semacquire1runtime.semrootruntime.semrelease1runtime.cansemacquireruntime.(*semaRoot).queueruntime.(*semaRoot).dequeueruntime.(*semaRoot).rotateLeftruntime.(*semaRoot).rotateRightruntime.makeslicecopyruntime.panicmakeslicelenruntime.makesliceruntime.panicmakeslicecapruntime.growsliceruntime.stackinitruntime.(*mSpanList).initruntime.stackpoolallocruntime.gclinkptr.ptrruntime.stackpoolfreeruntime.stackcacherefillruntime.stackcachereleaseruntime.stackcache_clearruntime.stackallocruntime.stacklog2runtime.stackfreeruntime.adjustpointersruntime.adjustframeruntime.adjustpointerruntime.adjustdefersruntime.syncadjustsudogsruntime.adjustsudogsruntime.copystackruntime.findsghiruntime.adjustctxtruntime.adjustpanicsruntime.newstackruntime.canPreemptMruntime.shrinkstackruntime.freeStackSpansruntime.getStackMapruntime.stackmapdataruntime.concatstringsruntime.stringDataOnStackruntime.concatstring2runtime.concatstring4runtime.slicebytetostringruntime.rawstringtmpruntime.rawstringruntime.atoiruntime.findnullruntime.badsystemstackruntime.modulesinitruntime.moduledataverify1runtime.findfuncruntime.findmoduledatapruntime.pcvalueruntime.pcvalueCacheKeyruntime.funcnameruntime.cfuncnameruntime.funcpkgpathruntime.funcnameFromNameoffruntime.cfuncnameFromNameoffruntime.funcfileruntime.funcline1runtime.funclineruntime.funcspdeltaruntime.funcMaxSPDeltaruntime.pcdatavalueruntime.pcdatastartruntime.funcdataruntime.stepruntime.readvarintruntime.doaddtimerruntime.deltimerruntime.dodeltimerruntime.dodeltimer0runtime.modtimerruntime.moveTimersruntime.adjusttimersruntime.addAdjustedTimersruntime.nobarrierWakeTimeruntime.runtimerruntime.runOneTimerruntime.clearDeletedTimersruntime.updateTimer0Whenruntime.updateTimerModifiedEarliestruntime.timeSleepUntilruntime.siftupTimerruntime.siftdownTimerruntime.badTimerruntime.traceReaderruntime.traceProcFreeruntime.traceFullQueueruntime.traceBufPtr.ptrruntime.traceEventruntime.traceEventLockedruntime.(*traceBufPtr).setruntime.(*traceBuf).byteruntime.(*traceBuf).varintruntime.traceStackIDruntime.traceAcquireBufferruntime.traceReleaseBufferruntime.traceFlushruntime.(*traceStackTable).putruntime.(*traceStack).stackruntime.(*traceStackTable).findruntime.traceStackPtr.ptrruntime.(*traceStackTable).newStackruntime.(*traceAlloc).allocruntime.(*traceAllocBlockPtr).setruntime.traceAllocBlockPtr.ptrruntime.traceProcStartruntime.traceProcStopruntime.traceGCSweepStartruntime.traceGCSweepSpanruntime.traceGCSweepDoneruntime.traceGoCreateruntime.traceGoStartruntime.traceGoParkruntime.traceGoUnparkruntime.traceGoSysCallruntime.traceGoSysExitruntime.traceGoSysBlockruntime.traceNextGCruntime.tracebackdefersruntime.getArgInfoFastruntime.gentracebackruntime.topofstackruntime.elideWrapperCallingruntime.getArgInforuntime.tracebackCgoContextruntime.printcreatedbyruntime.printcreatedby1runtime.traceback1runtime.printAncestorTracebackruntime.printAncestorTracebackFuncInforuntime.callersruntime.showframeruntime.showfuncinforuntime.hasPrefixruntime.isExportedRuntimeruntime.goroutineheaderruntime.tracebackothersruntime.atomicAllGIndexruntime.tracebackHexdumpruntime.isSystemGoroutineruntime.printCgoTracebackruntime.printOneCgoTracebackruntime.callCgoSymbolizerruntime.cgoContextPCsruntime.(*_type).stringruntime.(*_type).uncommonruntime.(*_type).pkgpathruntime.resolveNameOffruntime.reflectOffsLockruntime.reflectOffsUnlockruntime.resolveTypeOffruntime.(*_type).textOffruntime.name.tagLenruntime.name.dataruntime.name.nameruntime.name.nameLenruntime.name.tagruntime.name.pkgPathruntime.typelinksinitruntime.typesEqualruntime.(*functype).inruntime.(*functype).outruntime.writeErrruntime.cgoCheckWriteBarrier.func1runtime.cgoCheckTypedBlock.func1runtime.chansend.func1runtime.chanrecv.func1runtime.persistentalloc.func1runtime.allocmcache.func1runtime.freemcache.func1runtime.setGCPercent.func1runtime.(*gcControllerState).findRunnableGCWorker.func1runtime.gcStart.func1runtime.gcStart.func2runtime.gcMarkDone.func1.1runtime.gcMarkDone.func1runtime.gcMarkDone.func2runtime.gcMarkDone.func3runtime.gcMarkTermination.func1runtime.gcMarkTermination.func2runtime.gcMarkTermination.func3runtime.gcMarkTermination.func4.1runtime.gcMarkTermination.func4runtime.gcBgMarkWorker.func1runtime.gcBgMarkWorker.func2runtime.markroot.func1runtime.gcAssistAlloc.func1runtime.scanstack.func1runtime.bgscavenge.func1runtime.bgscavenge.func2runtime.(*pageAlloc).scavengeOne.func3runtime.offAddr.addrruntime.sweepone.func1runtime.getempty.func1runtime.freeSomeWbufs.func1runtime.(*mheap).alloc.func1runtime.(*mheap).freeSpan.func1runtime.(*pageAlloc).find.func1runtime.offAddr.addruntime.mProf_Malloc.func1runtime.tracealloc.func1runtime.tracefree.func1runtime.wbBufFlush.func1runtime.newdefer.func1runtime.newdefer.func2runtime.freedefer.func1runtime.preprintpanics.func1runtime.addOneOpenDeferFrame.func1.1runtime.addOneOpenDeferFrame.func1runtime.throw.func1runtime.fatalthrow.func1runtime.fatalpanic.func1runtime.fatalpanic.func2runtime.hexdumpWords.func1runtime.main.func2runtime.goready.func1runtime.casgstatus.func1runtime.allocm.func1runtime.reentersyscall.func1runtime.exitsyscallfast.func1runtime.exitsyscallfast_reacquired.func1runtime.malg.func1runtime.newproc.func1runtime.gfget.func1runtime.(*p).destroy.func1runtime.(*rwmutex).rlock.func1runtime.callers.func1runtime.tracebackHexdump.func1runtime.initruntime/debug.setGCPercentsync.eventruntime.entersyscallruntime.exitsyscallruntime/debug.SetTracebackruntime.gostringtime.nowruntime.walltimecallRetruntime.rt0_goruntime.checkASMruntime.gogoruntime.mcallruntime.systemstackruntime.systemstack_switchruntime.memhashruntime.memhash32runtime.memhash64runtime.jmpdeferruntime.asminitruntime.publicationBarrierruntime.procyieldruntime.morestackruntime.morestack_noctxtruntime.asmcgocallruntime.reflectcallruntime.call16runtime.call32runtime.call64runtime.call128runtime.call256runtime.call512runtime.call1024runtime.call2048runtime.call4096runtime.call8192runtime.call16384runtime.call32768runtime.call65536runtime.call131072runtime.call262144runtime.call524288runtime.call1048576runtime.call2097152runtime.call4194304runtime.call8388608runtime.call16777216runtime.call33554432runtime.call67108864runtime.call134217728runtime.call268435456runtime.call536870912runtime.call1073741824runtime.goexitruntime.gcWriteBarrierruntime.memclrNoHeapPointersruntime.memmoveruntime.asyncPreempt_rt0_wasm_jswasm_export_runwasm_export_resumewasm_pc_f_loopwasm_export_getspruntime.pauseruntime.exitruntime.wasmMoveruntime.wasmZeroruntime.wasmDivruntime.wasmTruncSruntime.wasmTruncUruntime.exitThreadruntime.osyieldruntime.usleepruntime.currentMemoryruntime.growMemoryruntime.resetMemoryDataViewruntime.wasmExitruntime.wasmWriteruntime.nanotime1runtime.walltime1runtime.scheduleTimeoutEventruntime.clearTimeoutEventruntime.getRandomDataruntime.(*itabTableType).add-fmruntime.(*errorString).Errortype..eq.runtime._panictype..eq.runtime._defertype..eq.runtime.sysmonticktype..eq.runtime.specialtype..eq.runtime.mspantype..eq.runtime.markBitstype..eq.runtime.mcachetype..eq.struct { runtime.gList; runtime.n int32 }type..eq.runtime.gcWorkruntime.(*lockRank).Stringruntime.(*waitReason).Stringtype..eq.runtime.sudogtype..eq.runtime.hchantype..eq.[6]stringtype..eq.[9]stringtype..eq.runtime.bitvectortype..eq.runtime.itabtype..eq.runtime._functype..eq.runtime.modulehashtype..eq.runtime.stackScanStatetype..eq.runtime.arenaHinttype..eq.runtime.mcentraltype..eq.struct { runtime.mcentral runtime.mcentral; runtime.pad [24]uint8 }type..eq.[136]struct { runtime.mcentral runtime.mcentral; runtime.pad [24]uint8 }type..eq.runtime.specialfinalizertype..eq.runtime.rwmutextype..eq.[2]stringtype..eq.[4]stringtype..eq.runtime.TypeAssertionErrortype..eq.runtime.boundsErrorruntime.(*boundsError).Errortype..eq.runtime.eventruntime.(*plainError).Errormain.mainA ,<<L,<vel0 YA|i  U O  D 2~  ` sB8 : v' 4F)a N %L Lo@</usr/local/Cellar/go/1.16.3/libexec/src/internal/cpu/cpu.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/internal/atomic/atomic_wasm.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/internal/atomic/asm_wasm.s/usr/local/Cellar/go/1.16.3/libexec/src/internal/bytealg/equal_wasm.s/usr/local/Cellar/go/1.16.3/libexec/src/internal/bytealg/indexbyte_wasm.s/usr/local/Cellar/go/1.16.3/libexec/src/runtime/alg.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/atomic_pointer.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mwbbuf.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/runtime2.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/cgocall.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/symtab.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/cgocheck.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/stubs.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mbitmap.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mheap.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/chan.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/internal/math/math.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/lock_js.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/lockrank_off.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/os_js.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/time_nofake.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/proc.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/env_posix.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/error.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/hash64.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/histogram.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/iface.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/type.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/lfstack.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/lfstack_64bit.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/runtime1.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/lockrank.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/malloc.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mem_js.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mfixalloc.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/internal/sys/intrinsics.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mcache.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/fastlog2.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/float.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/map.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/msize.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/map_fast32.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/map_fast64.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mbarrier.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/trace.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mcentral.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mcheckmark.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mfinal.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mgc.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/string.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/sema.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/print.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mgcwork.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mgcsweep.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mgcmark.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mgcstack.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/stack.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mgcscavenge.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mstats.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/time.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mranges.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mpagealloc.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/internal/sys/intrinsics_common.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mpallocbits.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mpagealloc_32bit.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mpagecache.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mprof.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/traceback.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/mspanset.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/netpoll.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/panic.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/preempt.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/sys_wasm.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/rwmutex.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/slice.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/write_err.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/select.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/timestub.go/usr/local/Cellar/go/1.16.3/libexec/src/runtime/asm_wasm.s/usr/local/Cellar/go/1.16.3/libexec/src/runtime/memclr_wasm.s/usr/local/Cellar/go/1.16.3/libexec/src/runtime/memmove_wasm.s/usr/local/Cellar/go/1.16.3/libexec/src/runtime/preempt_wasm.s/usr/local/Cellar/go/1.16.3/libexec/src/runtime/rt0_js_wasm.s/usr/local/Cellar/go/1.16.3/libexec/src/runtime/sys_wasm.s/Users/dlorenc/go/src/github.com/sigstore/cosign/main.goA ث {v '  \    5 r\   DP"6f@>    @    p p  0  zyzy, @P  ,      P4 @   `  pZYZYZYZY>=>=>=>=> =>=>=>    !  `p      p>l>=y   3     7 "3  p>       >  P*-*-L*-tB*-@ur       7!swl=}Q              @tB "   P  ``*- tB  tB*-*-  $XZZX<WZZXWWWZZWq `       ! *-*-*-D*-tBB    I/ #          P.tB  4 /   .    *-     @ 0-! $ () !&">   ! p&&|&} )&%$509$#&% $%$!090/&% !2 |  v&wLK:;"#>?./67BC23*+FG&'  vP&> ;        #&    36  ? @        .&/   a0+,  +,+, +,4baoXW  ~{8 !&a  "!,+, ,+,+, 04  36 R6    D 2); L`!;`?(0_^]^]^]R        6  6  ,#  # 6&# Z6    $NM b_`S    (181Z         6      @ 6   P6@6  0 6 p84"9 *7  `8 T   08|  <: <B  ^ <f  @<   <<fefefefefefefefefefefefefe fefefefefefefefe4G                  < <>; :*<+9@ C. *    >  @,>0>B?  |3   %&sBxw xw('Q P  <   `]   #`+ S  s       Q`Bdc  0    3 B('     `,B(' -(  ,#B`_`_`_ TS `_xw`_`_`_`_`_`_xwxwxw('('('xw  *      @   NY             5i #    :  ne       Gy/              @BB  0B TS  030Bxwxw 8/%B\B`_`_baxw `_`_`_ z_  &        61\            B  @Bxwxwxw  Dvuvu hDvu&vu*vuMVUV[^ rqtsn ]Rh!         Dvuvu  p<Dvuvu%            !   <# P_D 6@?5vuvu6@?5656@u6     H _"          `PDvvuvu     $ ~ P        pCDvvuvu  $ C       D,vu vvuvvuvu " <  c  J    U  I     3                   ! $#&%*'('('  ,+,+,+ MDvuvuvuvu$                  ! M2     Dvvu@vu%vuvuvu  @   &l ! #    $S            7  %($4 '      "! 4D5 P  O   (4 ,0D ( !$Dvu vu vuvuvuvuvuvu vu vuvu V  < z   p  dA  BmID                !$#D 0DFtststuvs vsts  "=>ABA> ) &12 D*      j " ) v `oFtststststu " 0  U<  <;!B) }~}% o   )       F        gFtutsts tuts   Z+b}  g.       Hrqrqruvq vqrq `sHrqrqrqrqru " 0    U#$<    wx q 43656%    )       }H rqrq rqrqruru rqrqrq ruvq      ) a}C           H        rHrurqrqrurq   f!#n  r4      PL0 L `L,  %0 L 0LNPN 5 p0N >; 0 P'N     ;'    N    N  N Nrq rqVU VU2121 21212121 2121       !    3!            JNVU VU2121 2121 J4             `7NVU212121212121      %7#     @NVU212121            @"  N      NW "J4h  $  $#" '\xi BR9i0 i   T*%   Y ; `     F           "!$#&%(' *) ,+.- 01216765BNC   Y f + B&NN  1    ( @a  C         "!$#&%(),+.-01 ` NlkP    5P ~}  5  9PPi~}   %9     P!1  0P YRZ ,[\ st^  ]      W+120Y @"R#  !"   ` R TJ   T`/T! .!@V"VF VdcX !  `1Zu   )1 Z `\     ^$ @^ZYZY^,^/0  ?^ /0/0T>e -^ji( 0   2-3- !( 8^FE\[\[656565    HOOHHHHH 85 @*^FEji :*-   /!  ^jijiFE     9^:  "&"  @3 A /%!(; ) ^@^6E65!! KZ        ^  ^DCDCDCDCLKLKLK jiDCDCLCDCLK po poLKDC            $)5%  j             C^LKLKLKLKjiLKpoLK 6   yk      ^ j%Cj%CDCji!!J4343LKLKDUpo* ,       A   _           5 0 ^!" ^ FCDCFEDCDCFEFEFEjiji FCDCFE2 *  T  '   !         2 ^  %&    i^po  E    X w  (E-!Z  K i " E0;^!!!! !!" !! !!!  ! !!! " "! !A ;        `)^!!!!"" """" """)    3^! !!"!"## #"## ## "       7^8$% ? * , $    -``_ZY`_ZY r&%   `3`#$# 4343           J`4343 `_ `_  1@       >       `0`# # DCDCDC# 43#h3  34 9.34 YY ZY        O`  #   # #$  0/   1T - +O     C`1212nmnm nmF A  )    E   J` hgDC hgDCDC      s`;    `# 43#  TT  p9`# 4CDCDC# 4343#   SYYYYY SYYT   D`1 43434C#$1243  -  SSSYYS&RYYYYY      RYYS $     `434343VUVU 4343 4343 4343   "       V?~ I)                              1``_-  T   + C   !  -p`'   zy      *Oj!     [   i     "' P8`    (;"      ` " n`          6     =@  ; m*          3`   >     E   `DC pf`                    f      l`         .        1   l &    .  =`       DC          =*     `  0b  ` b   ,  E0b%"%&dc212A < ,[ZZZc      b%"fedcfc21fe       1b%"21%"21  q&$  8(]o    b     P+b0/0U&  A +  b + pbXW  b    nb                  %, %&%"%&      TB 8       }   Y[Sn         `b         'b( 4.+ #$$    @b  % NbXWXWXW `OPO      &      -#(#N   d /d0 0"d#d  0f @(fCDCD _`u>(`f @)f)&.- )&.-)&   +    Nf   "4 - $ `f   f     lk>=TS 065 D9 >7@*   \[j]  2M("  EG/  C    **  5      "#&'*)*)0/21 *)cf      4c"         @f    )  h  0h `%h&   # (%h $0h     h   h =h  +(+(+,+(+( /  1 !0       h0h  %h+(+. QR  ef    h+(+(-(    pj  ,!    j :?B[ 0!j" QL E0 0Hj                                  H(!     Fj878787-*-.8787-*87    =D     (  ]F       Pj-*-*-*-*-.   0:#     ,U J              j   @j  j     j <G*0Kj:9:9      j:9 :9h-*-*-.-* :9-*-*-.    t              5M /  q 6    k 0O        !$%()('+jPO PO  ( M     j0j-*-   xj6:9 8          -  x       P!    P  j Aj8787-* -b787-b787  #/A      j-*-*   P%j-* -*-* -*      j  +j-* -*-*-*     #@%   j-* -.     Pj-*-*-.% 0l  5lNMNM.AJST   _pojklM/ %       l>&! 9%ir'  &                LlML              @l" l B    +   % 0       c  - 75   :            7  "!  FlG              ol       fe              !l" #$>[nLKLK       n NK h @ p N px p;p in -   +'    UpJG%& ] 36 U    r>   rp  r  4r~}   &!,36+ @    q     4Pr%  r~}  r ~}~}~}~}  r P t    tFEtFE Ct FE#_\ qtut  )/\' t   t747   t7478  t  t7474 t747  0t tFEFE  %t 74 78\[      t7478\[           t747      t747      vBv   v &-v   yvz @ A A \ y    +   Mv AvB          @v /|?<?< ?<?@_\ y  2?1/     9|>=%8           `.|>=    n    | P|~0 ~ ~  @!~&%AB          ~&%AB           $#$#  L12121 2J;$% 1-p  /      EBEF   0T  r  @dpH@?@?@/ -.%&D +( 3   * !  *0"F ; /0"  )            `    0/ 0/ 0/ 0/ 0/ 0/ 0/ 0/ 0/!0/ 0/ 0/0/0/[ -  (/  *)  ,5  87  :9  <;<A  BI  DK  >G8=TO<[  \[  ^]  V  ab  %             U  "! &%('(+,/21('&30     b0/T  ( 3/      q+             ZT  P  @&'  '&<MJMJMJ              W:9:9:9$     P:"@ Y 7?K v-,53I  81W   $  0       R URUS fWUA  P*+  @_B5 -         ,-  . X43H A@@@  @@@@    +@@-V5@43WT   @+      W   WTWXWT       V      g    WTWX vopmno   PW /      "@43* WTWX   ) LK6P,+ WT43$#$#WT B         1       (   &       WT B343 WT            &     P/       rijb    #         434343 "      $   RWTWTWTWXWX      $      Aj     O  WTWT43  :9               ? 4N       @     WX WXWTWTWTWTWT2    Y  ;        gT  WTWTWb  WX     - 7 |y          WTWX    S  WTWT     /S         %    )    `  P"WTWT 2               WTWT!! !!!%WTWT#" """ #pj  eTWT   WT  WT  # #nO2QQ1M###$   a## XIi           PO WTWTWT WTWT$  $ $$$ %%$%$%    % @6 $&"" " 6 $   &!!!"""   @WTWT && &&&  0$ '   ## $  /&%&%&%WTWTWT 43WTWTWTWT43 WTWTWT WTWT$'877777 77$.76Y\/0  1' ''''^777h."* *** ***Z**V ####    &&$++S,, , ,,,001ZZ10=%I+ ++,((+,s%* +*+ 3**93&& #' ('(   +577                      $! $#           "!$%(),+.-2367:9<; >?BCFG JILMPQ  -++    .  .       WTWT WTWT  .////****************.Y+--.YY=--YYYYYY.. /''./1"!234+YYY.. .''..+YYY.  (              "#&%*+.-.-.- 0  '4587<=@?B  9#WT WTWTWXWTWTWX /++  &0 0,,,YYYYY-00,YY,0000   ,,0 0/00/,,  ++2%     #    ! $# ;WTWT431   1 1$12 #& 11    WT212@4 !2, ,-.  ! @WTWT3&&&&, ,-/2 3#YYYYYR#22*YY*?&   3:9:93"""" & 3&''', ,-0# 4`' :9:9400, ,-0####    P :94##pTef4, ,-1344 DT.     5P<6    22 222222   8WTWX77 77 7gT WX 73 77 7   : &!/  ;6666   WTWT ;: ::: `W WTWT;   , ,-7; ;!!YYYYYR;;   !YY!-     "!=//p>q  0/"/  0/ >7777 :: 11&88>>.>8888>>Gq           ! <;@ 33    WX WX@3333 Z<@ @ZZZ<Z<@ @    @DWT WXAZ=ZZZZ@@!@ AZZ==!    +WX  WAA AZZZ=Z=A    C'H05    cWTWTWTWTI  EY YYYR EY YYYR $,S %EE  I II IHIHI.    !$%4343 %+WTWTWX:9 WT WTWX:9J  JJJJ> LWGGGG    QQ GGHH<98769951K KK_KK::GGQJ JJKKJ/;; 1 )         #      ! $%$#(' L  MHHHHHH  p$ MII     WTWXMM MM M WTWTWTWTM    N NAAAANN ./JJQQ JJe..!NN!%NNAA          S$OO%,-65 4343WT: WT 'TSSSSS S OO0  PPPPT T PPHHHEDDO''         :   '08WT WX WXWTUU U T UQYYYU TTU  0UVQQP#  W YSYSYY+#WWWTWXXW XW W    XTT PXTT P Y  ` Y UUUU  UU   ?WTWTZ VVVVVVZ ZZZVYYY%? $      WTWh ZVVYYZ ZZ YYY    p[WWWW WWp [B O(XXXX  \XXXX  r&%&%4343mnmnmnmn 4343m na  aaaa aZaaaaaaaaaa7aaaa``               L      !" 10/0/0;<  ,W(,                   #$&<       8/     D  0( efif~_b7^   #Bt|Lijij mj#B#B    "!          ijmjmjtqrg  ")   ,         jLNO0  jZ L`]W#BL G    ]FW +, ( (:9:9 J32,-e4  :9:9:9:9 7@?87@?8787@7@?87@?87@@?7@?87@?87@:97@?87@?87@:97$8  C^)   Q l +     #  KLKL p) ef ef     '  @1KLKLKLKLefK /*)0 `yzefyv     yvyfyv X    pefyvyv    g yvyvyz efyvKLyvKLKL    Jm ~} _j + 0     t yvyzefKLKLKLKLKL yvyzKL KL      GZ  3t              -    '  &;      ;+gh 2 @# 15 !  001 .      uvyvyz     a    ?    @A  "  c 3      '('(  O!"!"5,  ,           9+z M  U 3        O    5  !"   '( ! =6     ) yvyvyz y        i qh qh>PN9   )+  JW3  5        >/02  2   `v   # ,#21   1 : ? z?@34   mn      m   , 2 ( +,    ;X2#(P4 !$ 2-       W'S 4P                      01$ '   1 $!"  pk #$#$#$#$!"#$#$ #$#$#$#"#$#$ T CF Ladj 9Rj              ,     #   #$#$#$#" !"#$#$ !"#$!"#$#$ #$#$#$#$#$#$#$#$B  a0" ipop   [#A !('$I.'(!.#$j9:9F=>j BG@a              p/0 @?6 7F #0 7    QR " :5  : 1$!  (-12Q  `   `>? \[  ZG)b * 1[ % $        Z[j  {HGnu H#7N 1> E   00 Q     P     &)*    )  E F /<)*               zGBh - # z  2       p   +,+,+,)*+,    +,+,   `d!GB           d#    +     36   !          YZ  YZUVUVe   $ `+,+,+,)*+    )*     `)* )*     )*)*     p  )*              )EFE0 T " `!     D k) a  .  <      p>X 17 # -(%  e  '- 7   ,-!+5'    A i#           " f       a* LG $ )0   $       4 ;<;<&    &!"  3*       *     `:  <3  &;:: ;<;<+,;< +, adcj   :    K ;<;< ;<;<  K         * A     # ,           f x2 .# ! <_J ;# $30   $ ; + 1.       $>%  { k !8     %7 .!A  !&       78 ?P QTSR k  aX"'0+     a     X!      )2u ~)O         %(#$      d   l*  Ps U P   )( ^!!   ^   ^^  "^   ^^FE  ^FEDC `<^ !X565!"  ]YR77YYYYYY>   - `4343     & ` bpb%"%"fefefe%        b           f)&)  hh  j-*-   l    tt\[  'MNMN    P1@?@?@/    MJMN    M0/4    P   6  5 ]  |p       @@@ 6  :;=>A78WTWXIHHI II I 0efijifijT'*  .('     P4. F&%E 'PtFEFE  7PK  B3 9 44B7955u4455    #  $       @./&  % !      @ $Dp<*$@ @ @ @ 6 P dV6p $#<;<;<;<;    AÓ" 7Aғ" H7A" 7A" 7A"  8A" `8A" 8A" 8A”"  9AҔ"  `9A"  9A"  9A"   :A"  `:A" :A" :A•"  ;Aҕ" `;A" ;A" ;A"  <A" `<A" <A" <A–"  =AҖ" `=A" =A" =A"  >A" `>A" >A" >A—"  ?Aҗ" !@?A" "p?A" #?A" $?A" %@A" &P@A" '@A˜" (@AҘ" )AA" *PAA" +AA" ,AA" -BA" .PBA" /BA™" 0BAҙ" 1CA" 2`CA" 3CA" 4CA" 58DA" 6DA" 7DAš" 8 EAҚ" 9hEA" :EA" ;FA" <XFA" =FA" >GA" ?`GA›" @GAқ" AHA" BXHA" CHA" DIA" EPIA" FIA" GIAœ" H8JAҜ" IJA" JJA" K0KA" LKA" MKA" N(LA" OxLA" PLAҝ" QMA" RXMA" SMA" TMA" U@NA" VNA" WNAž" XHOAҞ" YOA" ZOA" [@PA" \PA" ]PA" ^@QA" _QAŸ" `QAҟ" a(RA" bpRA" cRA" dSA" ehSA" fSA" gSA " h@TAҠ" iTA" jTA" kUA" l`UA" mUA" nVA" oXVA¡" pVAҡ" qVA" rPWA" sWA" tWA" u(XA" vpXA" wXA¢" x YAҢ" yxYA" zYA" {(ZA" |pZA" }ZA" ~ [A" x[A£" [Aң"  \A" h\A" \A" ]A" p]A" ]A"  ^A¤" x^AҤ" ^A" (_A" _A" _A" 0`A" `A" `A¥" 8aAҥ" aA" aA" @bA" bA" bA" HcA" cA¦" cAҦ" PdA" dA" eA" HeA" eA" eA"  fA§" hfAҧ" fA" gA" pgA" gA" hA" XhA" hA¨" hAҨ" PiA" iA" jA" XjA" jA" kA" PkA©" kAҩ" lA" XlA" lA" lA" PmA" mA" mAª" 8nAҪ" nA" nA" 0oA" xoA" oA" pA" `pA«" pAҫ" qA" XqA" qA" qA" @rA" rA" rA¬" 8sAҬ" sA" sA" @tA" tA" tA" 8uA" uA­" uAҭ"  vA" xvA" vA" (wA" wA" wA" 0xA®" xAҮ" xA" 8yA" yA" yA" @zA" zA" zA¯" 8{Aү" {A" {A" @|A" |A" |A" H}A" }A°" }AҰ" P~A" ~A" A" XA" A" A" `A±" Aұ" A" hA" A" A" PA" A" A²" XAҲ" A" A" PA" A" A" XA" Aó" Aҳ" PA" A" A" @A" A" A" 8A´" AҴ"  A"  @A"  A"  A"  8A" A" ؊Aµ"  Aҵ" hA" A" A" @A" A" ЌA" A¶" pAҶ" A" A" HA" A" A" @A" A·"  Aҷ" !HA" "A" #A" $PA" %A" &A" '@A¸" (AҸ" )A" *HA" +A" ,A" -@A" .A" /A¹" 08Aҹ" 1A" 2ؕA" 30A" 4A" 5A" 68A" 7Aº" 8ؗAҺ" 9 A" :xA" ;ИA" <A" =pA" >șA" ? A»" @xAһ" AКA" B(A" CA" D؛A" E0A" FA" GМA¼" H(AҼ" IA" J؝A" K0A" LpA" MȞA" NA" OPA½" PAҽ" QA" RXA" SA" TA" U`A" VA" WA¾" XXAҾ" YA" ZA" [HA" \A" ]A" ^PA" _A¿" `Aҿ" aHA" bA" cA" dPA" eA" fA" g8A" hA" iاA" j0A" kA" lA" m8A" nA" oةA" p0A" qA" rЪA" sA" t`A" uA" vA" w0A" xA" yA" z(A" {A" |حA" }0A" ~xA" ЮA" A" XA" A" A" 0A" xA" A" A" PA" A" A" (A" pA" A" A" HA" A" A" @A" A" A" (A" A" A" (A" xA" жA" A" pA" A" A" @A" A" ظA" 0A" xA" йA" (A" A" غA"  A" xA" A" A" pA" A" A" HA" A" ؽA"  A" hA" A" A" @A" A" A" 8A" A" A" (A" A" A"  A" pA" A"  A" hA" A" A" PA" A" A" HA" A" A" @A" A" A" (A" A" A"  A" hA" A" A" PA" A" A" HA" A" A"  A" xA" A" (A" A" A" 0A" A" A" (A" A" A" 0A" pA" A"  A" hA" A" A" pA" A"  A" xA" A" A" `A" A" A" hA" A" A" `A" A" A" XA" A" A" `A" A" A" XA" A" A" XA" A"  A"  `A"  A"  A"  `A" A" A" hA" A" A" `A" A" A" XA" A" A" `A" A" A" hA" A" A" `A"  A" !A" "HA" #A" $A" %@A" &A" 'A" (HA" )A" *A" +PA" ,A" -A" .0A" /A" 0A" 1(A" 2A" 3A" 4A" 5pA" 6A" 7 A" 8xA" 9A" :A" ;pA" <A" =A" >XA" ?A" @A" A`A" BA" CA" DhA" EA" FA" GpA" HA" I A" JxA" KA" L(A" MA" NA" O0A" PA" QA" R8A" SA" TA" U0A" VA" WA" XA" YXA" ZA" [A" \@A" ]A" ^A" _(A" `A" aA" b A" cxA" dA" eA" f`A" gA" hA" iHA" jA" kA" lPA" mA" nA" o8A" pA" qA" r A" shA" tA" uA" vPA" wA" xA" y(A" zA" {A" |A" }XA" ~A" A" P8@808 h@8( p   h   X   @   @   @0p x(0(ph`H x`8@p hPH@8   (!!!"X"""@###$`$$$8%%%0&p&&'p''(A" X(((8)h)))*0* `* * * *  +P++++,P,,,-H---.H.../H/ /!/"0#H0$0%0&1'H1(1)1*2+H2,2-2.3/H30x31323344@45p4647485905:`5;5<5=5> 6?P6@6A6B6C7D@7Ep7F7G7H8IP8J8K8L8M09Nx9O9P:Q`:R:S:T0;Up;V;W;X@<Y<Z<[8=\=]=^>_P>`>a>b(?cp?d?e?f@@g@h@iAj`AkAlAm8BnBoBpCpA# A#   A# DA؂# )>A# (a(8(8(8(8(8(8 (8 0(8 O 8 m(8 (8(8(8!(8$8,'(8K+(8h1(87(8=(8C(8 I(8Q(8#W(8C](8_ e(8m(8s(8wwzAґ#  A# ww}A# #!wwA# #" wwA# ##!wwA# $( ww@%I(8&\('n8(8)8*8+8,8-8.8/8081#82683L 84\ #(+?D85lA# wINV((6|[`q8A# 7889).9(8A# |: HMPmr( ;=(,@<U m XA# ={ 6 `B>/ UZnA# Kd?Q 05D,A# K@kW\qAȣ# A8B( MCz(FKUv{DA# D,E,F  tA# iG.    (8H<  8IN  A ' - \ @`NJ_ (   A# KKl   % 7 > @A# 8L G L O u z xM   A# N   xxO   8P  8Q (    A# RT [adpSe =CT Aį#  @U (8Aа# KkV (8A# KW  "(8A# `X 169mQ(8Y> V\{A# K<Zg Hm49x8A# 0[ @FIej<`\ w| P@A# K] puA# ^ A# /) 8Aж# _ */2>C8`3 HMPZaH 0afiy~@@b[ cq vTlAȹ# Kd  xA# De  #.3(8f:?B(8g INQ^(8h c?h(8i otw?(8j .(8k (l$ (m7 ,t$A# p7n AԿ# u{e8Xo w (A# lp A# >c?V((q A# /&+88 A# r  CFV(sc[^(8tc[e(8u A# wlqt((v v{A# KTwT(8A# Kx~ :=((A# Ky(HMT8 A# Kz MSxA# O{+8|=G u z 8A# K}N#(;glu8A# K0~vu ((A# l @ ;$A# ;(($(@EX|4@A# KA:(8A# K_ 7 < I A# KD'  (A# K -!2!;!DA# K (^!c!! """8@A# K2L(V"["s""""A# K83(#"#4####A# K' ##%$8%>%[%tA# Kz?%%%L&Q&Z&|hA# KS}&&&'''tA# Khl''(7(<(G(A# JR(W(Z(~(((8A# ((()) *`qv*y*|***8A# KG ***;+@+I+l8A# K88v(**t+;+@+I+l8A# J9 ++',,,,l8A# K PR(1-D-~(((8A# Do-u--*./.B.`h9 *u.*;+@+I+l8A# : ...v//x8$Q//D0011A# KDY(R(d1w1~(((8A# @111d2i2|2`hX;X222228m 2222@02223@!3&3)3>538<3A3D3N3(8U3\3n3((A# K(y3~33333((A# KP# 3334 44((A# Kx60!4&494}444((A# X0 4448s8444448@ 4484435O6U6r6hA# Km667777@A# K( (778p8u8|8<A# K# 888 99"9A# K? 99?9B9j9o9z9A# KY 999;;;DA# KHq@c<i<l<<<< @A#   <<<=( ===a>e>k>8A# KX|>>>4>>8A# H>?m#?,?(  I$7?V(( ??E?T????8 A# Kl ???;@@@M@8 A# l@r@u@@@8 @@@@((@@@FAKAvA0TA# K [AAAAAA((A# KAAB@3B8B((A# \A# >MBSBVBqBvB8 A# w}BBByB((8BBBBBBLA# C C CN3n:CC8  C%C,CqBOCVC8A# 4E:C]C83(eCjC{CCCC8A# K4JCCC DDX A# Z[!D&D)DGDpuA# wRDUD((]DbDmD?DD((A# HA# waDDyD8 DDDDD(8A# K\E EEm9E@E(8A# K@KEQEXEvE{EE(8A# Kp?EEEEF F(8A# K,F1FU]UbU((A$ KuUzUU;@ V!V(A$ K\Z,XV^VV;+WW((A؎$ t[rNWUWWgXmXXX <# XXXC:YMYdDA$ KR TYZYuYZ$ZA$ K ?ZDZWZEZZA$ K (ZZZvZ[T8A$ K [[K[[[[LA$ K (B\H\c\0\\T8A$ Kt1!2\\\]((A$ K?!0 ]]L]]]]48A$ K0T! 7^=^\^^^^Aȕ$ K! __9____((A$ <!A$ w____@!_``y``((A$ "Aܗ$ > `%`(`<`((1"A$ /A`F`e`v``((A$ KlV"```j9aa((A$ "b)# bbbmbbA$ K`# @bbc c(8A$ 0#@)c/c~c9d>dQdfN$(dddvddDpA$ Kw$ eee}4QeVe((A$ A˟$ $qeveye3e(8$0eee1f6fEfA$ <  %A``fcfvfDA%fffu f@b%fffAffd8AТ$ $%0g gg'g((%,g1g4gDgOg(8A$ KX%Zg_gngggg((AȤ$ L%A$ /ggg gh((A$ K8 &hh2hOrhh(8A$ K+ &hhh1fhi8 AЦ$  J&!i&i)iu Gi8  g&Ti[iibkhkk0|L~ 2'llGllll8H+Q'A$ mmmqB(m((k'3m8m;mIm(8'NmSmVmvm{m(8' mmmmm8'FGmm m(8'mmmj9 n(8')n.n1neKn(8( Zn]nen((!( Znjnen((?(rnxnn_no83P(7o:oDo((a(7oIoDo((q(So7oXofo8 (mopoo(((A$ /ooomoo((A$ K(FGpp FpSp((A$ )jpopvpj9ppt L)mppj9pp((A$ K ))ppp /q4q((A$ Kt!<)OqTqqqqqq((Aش$ K4")r!rLrrrrXA$ K#)8s s,s"ssA$ D,$) sss4s(%) ssss((&) stt3+t0t((A$ K' * ;tAtDt)htot8 Aи$ K`()*FGvtt tt8 A$ Kt)K* tttvv*vhA$ Kx**vvvibvv@|Aغ$ +*wwV((,*ww#w?6 ,zzz((7,,zzzib*{3{((A$ Z,8@,:wN{V((9U,A$ /[T{e{{{((A$ Kd:s,{{{u {{8 A$ ;,{||3|H@<,&|,|7|?||(8A$ K =, |||x}~}}A$ K>, }}}V~[~r~dA$ K-?0-~~~mQ~(8A$ @T- ~~~ (oA- r(8A$ K B-CIh,(8A$ KdpCx.(uz} (8A$ KD.ł˂ڂv A$ JE.!)EJ(8A$ <F/ UZ]j(8G2/ or(8A$ KHe/N;FP@A$ K( I/ w}OTcA$ K4UJ/)ne΅Ӆ(8A$ K,0څ݅(8LG0)ne΅Ӆ(8A$ Mf0څ݅(8N0 CqB+(8O003J(8A$ KxP0(|چ(8A$ KQ0('*(8A$ KHR1(9<O(8A$ KpS<12(8A$ JT~13m(8A$ U1̇чԇ4((V1!> (A$ K(W144',(A$ K<X1839@#8A$ RY 28|8Z02A$ /ÈȈ׈4((A$ [H2A$ /4=((A$ T\\2A$ /*NQ>gn((A$ P]2u{(^2,g‰щ]U((A$ _2 ((`2&U!?F((A$ Kda2QWlm((A$ h b 3FɊ;@ c13U'6Yd@DdC3A$ /{s38 A$ eS3 ɋ((fi3(U΋ыߋ((g3U΋8h3v!&(8A$ Kxi3 {-03PY(8A$ Kj4`fiA$ K kW4 "%(KN(8A$ Kl4[adyǍԍ(8A$ 4m4 8n43~TA$ K$o4Ǝ;@-@A$ K$.pv5kpw яA$ 0 q5s8r5  8s5)(8t6A$ .1((u#65:=4Q((v76~Z]mm(8wQ6v{ 8A$ K,xx6qeĐӐ3(8A$ hy6! >(8z6"'3]b(8A$ K{6u{V~pA$ T5|6A$ /MGYu((A$ Z4}7 _y~8~ 7A$ / ǒ8 A$ ZB7&UΒҒߒ(8S7A$ ((b7 8v7 (87 Do((7 Do((7 Do((7 (Do((7 0Do((8 8Do((8 @Do((18 HDo((H8 PDo((b8 XDo((}8A$ >_`dyB((8A$ >}B`lyB((8A$ /tzq((A$ 8A$  (@8%+3&kt94єڔd`9A$ >((.9A$ ((B9~CTh  h9_eiA$  9vm(89ɕΕҕd@9,ɗ$A% @9 rv@98%kT :((:څ (-:Εə(;:rΙޙ(8L:A܄% AG( _:. %(:059kp8 : w}-!ך(8A% K:M$(8A% K:+1Mśܛ0A% K :qB(5(8A% :A% >@EIU8 :A% MBZfqB(8 ;Hm(8$;A% /44Ҝל8 AЋ% `;A% /È4'((A% Z6;HmDpE;A% >:,0V((U;Aԍ% :,8V((e;@@DN((w;W\`&((; ((;ȝ̝>ޝ((;}By`((;.((;c,(8;c,$V((;,26XRD(@<KO[b(82<iosG<W(/8@A% <A% >:SWV((<A% /_dr)((A% <AԖ% w((< ȟ͟ib!,Aؗ% <SG( <OU}ߠhA% K@!<$J*.ˡAؙ% !<(=(!=A% >((8=A̛% /ɋ((A% ]=A% /"ɋ((A% ,y=A% w.((=4:L|(8A% Z =<3(=A% /ɋ((A% @=A% /ɢϢ"cvhA% K6#>!4ͣңۣlAР% 7>A% 54((K>_y((^>W?(8A% K.>ۤ~*A% >A% MQo((>fimA(8>w?(8>&(8A% X>!3>#(8 ?-L,0:(8%?A% /?EkܦhxA% K@?B?XקܧhA% dH?A% >494'(?A% >A`OSvq8 ?A% >|((?A% /&U8 A% KT?OqߨqIZ8 A% KTI?ȩkp8A% \@A% /FΪv8 A% j\ @ 28XΫ(@A% /22 8 A% hN@A% #3mq~@` c@2(u@ĬܬATA% @6<Fu mtDT@@Fv8 A% @A% /fǭ׭A8 A% $@A% oS((@"p94A% K]@SN(8Aس% @AA% >!3o>((AA% /ְ۰8 A% K"Au -4DAе% 7AA% /CHX8 A% KtGAϱԱ߱A% K|WAmTAط% K AMµʵ((A% ZA@ ((BA% wM'((B,3ͷdA% xBA% /ryø˹XA% K sB($8NAȻ% K$ Byɺκ8A% JB׺ܺ7(#6A% K BKPx(8Aн% @g!C7<@(83ĊLX4pw(8A% K^Ckʼռ(8Aȿ% |rCA% w((C}4R[A% X"CA% /|((A% A% @C!&3lA% CLPX((C]bv-!ƾ˾8@CA% /G޾2 ((A% DA% /v&: ep8 A% b #Dgh;DA% QG u пտ8(^DA% .mqB#8 A% Kd|D27_A% h D?G._hL Dot~ D0DIf@"  E DA% K #EK rP@A% 8V1E;+Xet(8KF!4-}4SX(8A% K YFgltϱh4A% KjF4=((A% FA% /8 A% K^Fv*"**L((A% KPF_fvMn`A% KDJFE,9((A% 4KDp|u(8F(8A% KG A% G(_*y~(8 -G  ,0(8!AG!D>Pn$A% KT"ZGmj9(8A% Kx#kGe X A% $|GM+0%G9>N)PA% KH&G %*3@A% KF'Gld@A% K$L(G/4Dns~0A% K)G A% K0*H (TA% D+H39id2t@p#,!H-LTXə8 -5Hcg8.BHA% Nouvm8/{HA% / ((A% 0HA% >ȟib3((1HA% /<BJ((A% 2HA% /-!*AXTA% X3FTX(4H`p(L l5Hg(8A% KP6 I4&49B(8A% K@7#I KQsV~A% K68GI??DX?tA% l9[I(8:qI ,@A% K;I 6;B8A% h<IKPTib8=IKPib8>I(gA% K?J Zg$gZg8A% K@BJ@v tA% tATJA% /((A% KBJO ((A% KCJ9ty((A% KDJ3((A% EJA% .3HO8 A% KFJdi} 8 A% K|GK*.8 A% K@XH4K!@$A% K_IFK(*0<vErDA% J]KNd@KK 4in8A% K#LK ,Q0A% KMK  9r(8A% $NLA% /|PA% Kd/O:LE,?(8A% PNLA% /RXvO((A% 0QeLP8N^DRL0~SL88TLX8UL(.Hh`VM8&.88WM0=8@X+M EHLx(8Y8M{ Dt@ZIMA% /ɋ((A% R[((\`MA% 08]tM38,^MSX`(A% K0_M@"1T4A& X`M @EMcjtaN ]D{?8bN(HmtcON(td`N8.$8erN0 l8fN(%+/e;XgNDJ.Z8hN0agop8A& KiN {3lA؆& KjNP4BG8A& K kOZ`hϱ@A& KlOotHA& 0m+Oȟib(8n>OAA)(8oRO@4;O6yAȊ& (apcO  ~B4qvO]cghrO `sO-L (8tO %)uuOmA& vOA&  pnwOI(8xPy ZTou04A& pU(20y~d8U(*> 8*U W\opA& K(kUAЬ& KdU!'Gt4A& \U qB UG u CH(8AЮ& USYqBuP@V dHV2(87V @MV,g188A& eV?BFm8Vpuy28A& KVF;@Aس& KV!5uA& KLVp8A& KW$>A(8A& 7WTW[qt8p^W!W>@ oW@8A& WA& [`HpW 8 `@Watxy~(8WA& >*>;((XAĺ& >V((1XA& /3mߋ((AȻ& HXA& /3mߋ((A& _XA& >8 }XA& /,g8 A& $XAܽ& /o?%*((A& DXA& /,g=Li8A& ZtXsps(8YAԿ& >((YA& <3RD8 /Y!3>(8JYA& >_Dy8 cYA& /8`A& |YA& >RD((YA& >c  V((YA& /p   G P 8 A& YA& : _ V((Yc 4Gh (8ZA& wc p V((7Zox  ? 8A& 8TZA& /   -!6 Q 0A& AqZA& /|  m  hA& ZA& :  V((ZI  8Z  ((ZA& /   )L ] @A& K1Z ~  ͣ  8 A& *[A& /   ' ((A& A[A& > " 8 X[A& >* - = 8 t[A& >D sJ  8 [A& /V e |  ((A& Kd[fx    8 A& [A& >:  V((\A& /H  ɋ((A& \A& /H  ɋ((A& $1\A& >7   ((J\A& /   }48 C @XA& a\A& /V [ w C  8 A& x\A& /  ~3<8A& \A& w_`MyU((\^dlL&0A& `\A& FV(`\A& >a`y8  ]A& / ,38 A& 8"]A& >>DHVh;]A& caV((T]|@i}((o]G2DA& L]A& >ISV((]A& >!3>;((]A& >}By((]A& >((]A& >@^A& >3mߋ((*^A& >|8 =^A& >> 4((S^A& >|"8 g^A& /{*@3ho8 A&  ^A& /)@A& ,&^A& ~y8 ^((^A& $=7(uz(^DyU(^,g((A& ` _A& >:SV((_A& /JW@(A& K&2_ty} (8A& M_>^_((A& x_A&  A& 1(_څڅA& __ww"`_ww%A& '_(A& '_. A& _A& ww6 A& # _ ww:A& # _ww>A& #  `wwBA& ' `FA&  /`A& wwLA& ?`A& .wwP0Z`A& wwTA& l`A& XX[A& #~`wwgA& `A& wwkP` o` :u(` y (` }(` p(` ( a p(a @(,a Г(=a P(Na Д(_a (qa 0( a (!a ("a (#a  ($a P(%a $(&a (0`(' b 4<((b @H()3b LT@(*Hb X`(+]b dm(,sb qz(-b ~(.b `(/bA& A& #0bA& #1bwwA& #2bwwA& 3 cA& Iww4cwwA& #5+cwwA& #6;cwwA& #7NcwwA& #8]cwwA& #9ocwwA& #:}cwwA& ;cA& wwA& <cA& wwA& =cA& wwA& >cA& wwA' ?cA' wwA€' @cAԀ' wwA' AcA' wwA' BdA' wwAҁ' #CdwwA' #D*dwwA' E=dww8FYdww Gjdww0H|dwwpIdwwJdwwKdwwLdwwMd: (8N e(-8A' tO*eU8PBea4y78QZe8RveV((Sea4yB((TeXX>A8UeV((Ve8W f8X#f CDWqBrw8AЌ' KY>f8A' Z[fa4y78[rfa4y78\f8]f8^f8_fڅXR8`fڅXR8af8bga4y78c3gXX>A((dNga4y`((ehgڅDo((fgs((ghsX((h)ha4y78iBhT>ޝ8jUh8khhs8lh8mh4y~8nh8oh8phAܙ' '((A' {@A ' A@ A' Y A' A' t  "(&.0 #62)9? !'%-/518>  $,47=+3<*;:  A'   A'  0@P`p @`@  %&(*058@HJPU`jpA'  !"#$%%&&''((()))*++,,,,,------....//////0001123333333333444444444455666677777888888888889999999999::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<=====>>>>>>>>>>>??????????@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAȨ' uӺ?c?yڌX?9?-^? h?:D?Kx? !? ?8G?2Sg?hz?:?Е1?z?G?g!?Kx?4&?̈Gj?TNK?sp (??P??2Ut?Uᢜ>?&m??l??sjbƈ?UϋE??A'      %   u  w % # 9 g u+ O   9 g +%  9   %  9 1   )    1       o     %  7  M A'  `ii]  _88sHCHCrr`A' pA' 7 P ` 8 @    7e  Aر' A'   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~A' p @ ׽1~23΁@ P d 0 ` 0 P @T @ A'  22A'  A' 6  X ? &..E0O!/j1""ÙbbCA A !  #ށ*1?A' A'  Q A' P !H x=   DŽ ɘ(Q Y A' ܊ı   l M $  "   < 3̱  c   {  $ ȱ A'  ߅ ցƁ~~'n ~  T + / c  l ф u H ` >ۄ ` L A' ǀ producerslanguageGogo1.16.3 processed-byGo cmd/compilego1.16.3name몁 go.buildid internal_cpu.processOptions "type..eq.internal_cpu.CacheLinePad runtime_internal_atomic.LoadAcq runtime_internal_atomic.Load8runtime_internal_atomic.And8runtime_internal_atomic.Or8runtime_internal_atomic.Andruntime_internal_atomic.Or runtime_internal_atomic.StoreRelruntime_internal_atomic.Store8runtime_internal_atomic.Casp1runtime_internal_atomic.CasRel runtime_internal_atomic.Loaduint!runtime_internal_atomic.Loadint64!runtime_internal_atomic.Xaddint64runtime_internal_atomic.Loadruntime_internal_atomic.Loadpruntime_internal_atomic.Load64runtime_internal_atomic.Xaddruntime_internal_atomic.Xadd64#runtime_internal_atomic.Xadduintptrruntime_internal_atomic.Xchg runtime_internal_atomic.Xchg64!runtime_internal_atomic.Cas64"runtime_internal_atomic.Store#runtime_internal_atomic.Store64$runtime_internal_atomic.Cas%"runtime_internal_atomic.Casuintptr&$runtime_internal_atomic.Storeuintptr'#runtime_internal_atomic.Loaduintptr("runtime_internal_atomic.StorepNoWB) memeqbody*runtime.memequal+runtime.memequal_varlen,memchr- internal_bytealg.IndexByteString.runtime.memhash128/runtime.memequal00runtime.memequal81runtime.memequal162runtime.memequal323runtime.memequal644runtime.memequal1285runtime.f32equal6runtime.f64equal7runtime.c64equal8runtime.c128equal9runtime.strequal:runtime.interequal;runtime.nilinterequal<runtime.efaceeq=runtime.ifaceeq>runtime.alginit?runtime.atomicwb@runtime.atomicstorepAruntime.cgocallBruntime.cgoIsGoPointerCruntime.cgoCheckWriteBarrierDruntime.cgoCheckMemmoveEruntime.cgoCheckSliceCopyFruntime.cgoCheckTypedBlockGruntime.cgoCheckBitsHruntime.cgoCheckUsingTypeIruntime.makechanJruntime.chansend1Kruntime.chansendL runtime.sendMruntime.sendDirectNruntime.recvDirectOruntime.closechanP runtime.emptyQruntime.chanrecv1Rruntime.chanrecvS runtime.recvTruntime.chanparkcommitUruntime.__waitq_.dequeueVruntime.gogetenvW#runtime.__TypeAssertionError_.ErrorXruntime.errorString.ErrorYruntime.plainError.ErrorZruntime.boundsError.Error[runtime.printany\runtime.printanycustomtype]runtime.panicwrap^runtime.memhashFallback_runtime.memhash32Fallback`runtime.memhash64Fallbackaruntime.__timeHistogram_.recordbruntime.getitabcruntime.__itabTableType_.finddruntime.itabAdderuntime.__itabTableType_.addfruntime.__itab_.initgruntime.itabsinithruntime.convTstringiruntime.convT2Enoptrjruntime.assertE2I2kruntime.iterate_itabslruntime.__lfstack_.pushmruntime.__lfstack_.popnruntime.lfnodeValidateo runtime.lockp runtime.lock2qruntime.unlockrruntime.unlock2sruntime.notewakeuptruntime.notesleepuruntime.notetsleepvruntime.notetsleepgwruntime.checkTimeoutsxruntime.beforeIdleyruntime.handleAsyncEventzruntime.handleEvent{runtime.lockRank.String|runtime.lockWithRank}runtime.unlockWithRank~runtime.mallocinitruntime.__mheap_.sysAllocruntime.sysReserveAlignedruntime.nextFreeFastruntime.__mcache_.nextFreeruntime.mallocgcruntime.newobjectruntime.newarrayruntime.profileallocruntime.fastexprandruntime.persistentallocruntime.persistentalloc1runtime.inPersistentAllocruntime.__linearAlloc_.allocruntime.__hmap_.incrnoverflowruntime.__hmap_.newoverflowruntime.makemap_smallruntime.makemapruntime.makeBucketArrayruntime.mapaccess2runtime.mapaccessKruntime.mapassignruntime.mapiterinitruntime.mapiternextruntime.hashGrowruntime.growWorkruntime.evacuateruntime.advanceEvacuationMarkruntime.mapaccess1_fast32runtime.mapaccess2_fast32runtime.mapassign_fast32runtime.growWork_fast32runtime.evacuate_fast32runtime.mapaccess1_fast64runtime.mapassign_fast64ptrruntime.mapdelete_fast64runtime.growWork_fast64runtime.evacuate_fast64runtime.typedmemmoveruntime.reflectcallmoveruntime.typedslicecopyruntime.typedmemclrruntime.memclrHasPointers!runtime.__mspan_.refillAllocCacheruntime.__mspan_.nextFreeIndexruntime.badPointerruntime.findObjectruntime.heapBits.nextArenaruntime.heapBits.forward"runtime.heapBits.forwardOrBoundaryruntime.bulkBarrierPreWrite"runtime.bulkBarrierPreWriteSrcOnlyruntime.bulkBarrierBitmapruntime.typeBitsBulkBarrierruntime.heapBits.initSpanruntime.heapBitsSetTyperuntime.heapBitsSetTypeGCProgruntime.progToPointerMaskruntime.runGCProgruntime.materializeGCProgruntime.allocmcacheruntime.freemcacheruntime.__mcache_.refillruntime.__mcache_.allocLargeruntime.__mcache_.releaseAll!runtime.__mcache_.prepareForSweepruntime.__mcentral_.cacheSpanruntime.__mcentral_.uncacheSpanruntime.__mcentral_.growruntime.startCheckmarksruntime.endCheckmarksruntime.setCheckmarkruntime.sysAllocruntime.sysFreeruntime.sysReserveruntime.sysMapruntime.queuefinalizerruntime.wakefingruntime.__fixalloc_.allocruntime.gcinitruntime.readgogcruntime.gcenable'runtime.__gcControllerState_.startCycle#runtime.__gcControllerState_.revise%runtime.__gcControllerState_.endCycle)runtime.__gcControllerState_.enlistWorker1runtime.__gcControllerState_.findRunnableGCWorker runtime.pollFractionalWorkerExitruntime.gcSetTriggerRatioruntime.gcEffectiveGrowthRatioruntime.gcWaitOnMarkruntime.gcTrigger.testruntime.gcStartruntime.gcMarkDoneruntime.gcMarkTerminationruntime.gcBgMarkStartWorkersruntime.gcBgMarkWorkerruntime.gcMarkWorkAvailableruntime.gcMarkruntime.gcSweepruntime.gcResetMarkStateruntime.clearpoolsruntime.fmtNSAsMSruntime.gcMarkRootPrepareruntime.gcMarkRootCheckruntime.markrootruntime.markrootBlockruntime.markrootFreeGStacksruntime.markrootSpansruntime.gcAssistAllocruntime.gcAssistAlloc1runtime.gcWakeAllAssistsruntime.gcParkAssistruntime.gcFlushBgCreditruntime.scanstackruntime.scanframeworkerruntime.gcDrainruntime.gcDrainNruntime.scanblockruntime.scanobjectruntime.scanConservative runtime.shaderuntime.greyobjectruntime.gcDumpObjectruntime.gcmarknewobjectruntime.gcMarkTinyAllocsruntime.heapRetainedruntime.gcPaceScavengerruntime.wakeScavengerruntime.scavengeSleepruntime.bgscavengeruntime.__pageAlloc_.scavengeruntime.printScavTrace%runtime.__pageAlloc_.scavengeStartGen$runtime.__pageAlloc_.scavengeReserve&runtime.__pageAlloc_.scavengeUnreserve runtime.__pageAlloc_.scavengeOne(runtime.__pageAlloc_.scavengeRangeLockedruntime.fillAligned*runtime.__pallocData_.hasScavengeCandidate+runtime.__pallocData_.findScavengeCandidate runtime.__stackScanState_.putPtr runtime.__stackScanState_.getPtr#runtime.__stackScanState_.addObjectruntime.binarySearchTreeruntime.__sweepClass_.update!runtime.__mheap_.nextSpanForSweepruntime.finishsweep_mruntime.bgsweepruntime.sweeponeruntime.__mspan_.ensureSweptruntime.__mspan_.sweepruntime.__mspan_.reportZombiesruntime.deductSweepCreditruntime.__gcWork_.initruntime.__gcWork_.putruntime.__gcWork_.putBatchruntime.__gcWork_.tryGetruntime.__gcWork_.disposeruntime.__gcWork_.balance runtime.__workbuf_.checknonemptyruntime.__workbuf_.checkemptyruntime.getemptyruntime.putemptyruntime.putfullruntime.trygetfullruntime.handoffruntime.prepareFreeWorkbufsruntime.freeSomeWbufsruntime.recordspanruntime.inHeapOrStackruntime.spanOfHeapruntime.__mheap_.initruntime.__mheap_.reclaimruntime.__mheap_.reclaimChunkruntime.__mheap_.allocruntime.__mheap_.allocManualruntime.__mheap_.setSpansruntime.__mheap_.allocNeedsZero!runtime.__mheap_.allocMSpanLockedruntime.__mheap_.allocSpanruntime.__mheap_.growruntime.__mheap_.freeSpanruntime.__mheap_.freeManualruntime.__mheap_.freeSpanLockedruntime.__mspan_.initruntime.__mSpanList_.removeruntime.__mSpanList_.insertruntime.spanHasSpecialsruntime.spanHasNoSpecialsruntime.addspecialruntime.setprofilebucketruntime.freespecialruntime.__gcBitsArena_.tryAllocruntime.newMarkBitsruntime.newAllocBitsruntime.nextMarkBitArenaEpochruntime.newArenaMayUnlockruntime.__pageAlloc_.initruntime.__pageAlloc_.growruntime.__pageAlloc_.updateruntime.__pageAlloc_.allocRange#runtime.__pageAlloc_.findMappedAddrruntime.__pageAlloc_.findruntime.__pageAlloc_.allocruntime.__pageAlloc_.freeruntime.mergeSummariesruntime.__pageAlloc_.sysInitruntime.__pageAlloc_.sysGrowruntime.__pageCache_.allocruntime.__pageCache_.allocNruntime.__pageCache_.flush!runtime.__pageAlloc_.allocToCacheruntime.__pageBits_.setRangeruntime.__pageBits_.setAllruntime.__pageBits_.clearRangeruntime.__pageBits_.clearAllruntime.__pageBits_.popcntRangeruntime.__pallocBits_.summarizeruntime.__pallocBits_.find runtime.__pallocBits_.findSmallN runtime.__pallocBits_.findLargeN runtime.__pallocData_.allocRangeruntime.__pallocData_.allocAllruntime.newBucketruntime.__bucket_.mpruntime.__bucket_.bpruntime.stkbucketruntime.eqsliceruntime.mProf_NextCycleruntime.mProf_Flushruntime.mProf_FlushLockedruntime.mProf_Mallocruntime.mProf_Freeruntime.blockeventruntime.blocksampledruntime.saveblockeventruntime.traceallocruntime.tracefreeruntime.tracegcruntime.makeAddrRange$runtime.addrRange.removeGreaterEqualruntime.__addrRanges_.initruntime.__addrRanges_.findSucc*runtime.__addrRanges_.findAddrGreaterEqualruntime.__addrRanges_.add runtime.__addrRanges_.removeLast(runtime.__addrRanges_.removeGreaterEqualruntime.__addrRanges_.cloneIntoruntime.__spanSet_.pushruntime.__spanSet_.popruntime.__spanSet_.reset"runtime.__spanSetBlockAlloc_.alloc!runtime.__spanSetBlockAlloc_.free runtime.__headTailIndex_.incTailruntime.init.2runtime.flushmcacheruntime.__sysMemStat_.add&runtime.__consistentHeapStats_.acquire&runtime.__consistentHeapStats_.releaseruntime.__wbBuf_.resetruntime.wbBufFlushruntime.wbBufFlush1runtime.netpollGenericInitruntime.write1runtime.sigpanicruntime.mpreinitruntime.osinitruntime.panicCheck1runtime.panicCheck2runtime.goPanicIndexruntime.goPanicIndexUruntime.goPanicSliceAlenruntime.goPanicSliceAlenUruntime.goPanicSliceAcapruntime.goPanicSliceAcapUruntime.goPanicSliceBruntime.goPanicSliceBUruntime.goPanicSlice3Alenruntime.goPanicSlice3AlenUruntime.panicshiftruntime.panicdivideruntime.testdefersizesruntime.init.3runtime.newdeferruntime.freedeferruntime.freedeferpanicruntime.freedeferfnruntime.deferreturnruntime.preprintpanicsruntime.printpanicsruntime.addOneOpenDeferFrameruntime.runOpenDeferFrameruntime.reflectcallSaveruntime.gopanicruntime.getargpruntime.gorecover runtime.throwruntime.recoveryruntime.fatalthrowruntime.fatalpanicruntime.startpanic_mruntime.dopanic_mruntime.canpanicruntime.suspendGruntime.resumeGruntime.asyncPreempt2runtime.init.4runtime.recordForPanicruntime.printlockruntime.printunlockruntime.gwriteruntime.printspruntime.printnlruntime.printboolruntime.printfloatruntime.printcomplexruntime.printuintruntime.printintruntime.printhexruntime.printpointerruntime.printuintptrruntime.printstringruntime.printsliceruntime.hexdumpWords runtime.mainruntime.init.5runtime.forcegchelperruntime.Goschedruntime.goparkruntime.goreadyruntime.acquireSudogruntime.releaseSudogruntime.badmcallruntime.badmcall2runtime.badreflectcallruntime.badmorestackg0runtime.badmorestackgsignalruntime.badctxtruntime.allgaddruntime.atomicAllGruntime.cpuinitruntime.schedinitruntime.dumpgstatusruntime.checkmcountruntime.mReserveIDruntime.mcommoninit runtime.readyruntime.freezetheworldruntime.casfrom_Gscanstatusruntime.castogscanstatusruntime.casgstatusruntime.casGToPreemptScanruntime.casGFromPreemptedruntime.stopTheWorldWithSemaruntime.startTheWorldWithSemaruntime.mstartruntime.mstart1runtime.mstartm0 runtime.mPark runtime.mexitruntime.forEachPruntime.runSafePointFnruntime.allocmruntime.newextramruntime.oneNewExtraMruntime.lockextra runtime.newm runtime.newm1runtime.mDoFixup runtime.stopmruntime.mspinningruntime.startmruntime.handoffp runtime.wakepruntime.stoplockedmruntime.startlockedmruntime.gcstopmruntime.executeruntime.findrunnableruntime.pollWorkruntime.wakeNetPollerruntime.resetspinningruntime.injectglistruntime.scheduleruntime.checkTimersruntime.parkunlock_cruntime.park_mruntime.goschedImplruntime.gosched_mruntime.gopreempt_mruntime.preemptParkruntime.goyieldruntime.goyield_mruntime.goexit1runtime.goexit0 runtime.saveruntime.reentersyscallruntime.entersyscall_sysmonruntime.entersyscall_gcwaitruntime.exitsyscallfast"runtime.exitsyscallfast_reacquiredruntime.exitsyscallfast_pidleruntime.exitsyscall0 runtime.malgruntime.newprocruntime.newproc1runtime.saveAncestors runtime.gfput runtime.gfgetruntime.gfpurgeruntime.badunlockosthreadruntime.__p_.initruntime.__p_.destroyruntime.procresizeruntime.acquirep runtime.wirepruntime.releasepruntime.incidlelockedruntime.checkdeadruntime.preemptallruntime.schedtraceruntime.schedEnableUserruntime.schedEnabled runtime.mputruntime.globrunqgetruntime.pMask.readruntime.pMask.clearruntime.updateTimerPMaskruntime.pidleputruntime.pidlegetruntime.runqemptyruntime.runqputruntime.runqputslowruntime.runqputbatchruntime.runqgetruntime.runqgrabruntime.runqstealruntime.doInitruntime.gotraceback runtime.argsruntime.goargsruntime.goenvs_unixruntime.testAtomic64 runtime.checkruntime.parsedebugvarsruntime.waitReason.Stringruntime.__rwmutex_.rlockruntime.__rwmutex_.runlockruntime.readyWithTimeruntime.semacquire1runtime.semrelease1runtime.cansemacquireruntime.__semaRoot_.queueruntime.__semaRoot_.dequeueruntime.__semaRoot_.rotateLeftruntime.__semaRoot_.rotateRightruntime.makeslicecopyruntime.makesliceruntime.growsliceruntime.stackinitruntime.stackpoolallocruntime.stackpoolfreeruntime.stackcacherefillruntime.stackcachereleaseruntime.stackcache_clearruntime.stackallocruntime.stackfreeruntime.adjustpointersruntime.adjustframeruntime.adjustdefersruntime.syncadjustsudogsruntime.copystackruntime.newstackruntime.shrinkstackruntime.freeStackSpansruntime.getStackMapruntime.concatstringsruntime.concatstring2runtime.concatstring4runtime.slicebytetostringruntime.rawstringtmpruntime.rawstring runtime.atoiruntime.findnullruntime.badsystemstackruntime.fastrandruntime.modulesinitruntime.moduledataverify1runtime.findfuncruntime.pcvalueruntime.funcnameruntime.funcpkgpathruntime.funcnameFromNameoffruntime.funcfileruntime.funcline1runtime.funclineruntime.funcspdeltaruntime.funcMaxSPDeltaruntime.pcdatavalueruntime.funcdata runtime.stepruntime.doaddtimerruntime.deltimerruntime.dodeltimerruntime.dodeltimer0runtime.modtimerruntime.moveTimersruntime.adjusttimersruntime.addAdjustedTimersruntime.nobarrierWakeTimeruntime.runtimerruntime.runOneTimerruntime.clearDeletedTimersruntime.updateTimer0When#runtime.updateTimerModifiedEarliestruntime.timeSleepUntilruntime.siftupTimerruntime.siftdownTimerruntime.badTimer runtime.writeruntime.traceReaderruntime.traceProcFreeruntime.traceEventruntime.traceEventLockedruntime.traceStackIDruntime.traceAcquireBufferruntime.traceReleaseBufferruntime.traceFlushruntime.__traceStackTable_.putruntime.__traceStackTable_.find#runtime.__traceStackTable_.newStackruntime.__traceAlloc_.allocruntime.traceProcStartruntime.traceProcStopruntime.traceGCSweepStartruntime.traceGCSweepSpanruntime.traceGCSweepDoneruntime.traceGoCreateruntime.traceGoStartruntime.traceGoSchedruntime.traceGoParkruntime.traceGoUnparkruntime.traceGoSysCallruntime.traceGoSysExitruntime.traceGoSysBlockruntime.traceNextGCruntime.tracebackdefersruntime.gentracebackruntime.getArgInforuntime.tracebackCgoContextruntime.printcreatedbyruntime.printcreatedby1runtime.tracebackruntime.traceback1runtime.printAncestorTraceback&runtime.printAncestorTracebackFuncInforuntime.callersruntime.gcallersruntime.showframeruntime.showfuncinforuntime.goroutineheaderruntime.tracebackothersruntime.tracebackHexdumpruntime.isSystemGoroutineruntime.printCgoTracebackruntime.printOneCgoTracebackruntime.callCgoSymbolizerruntime.cgoContextPCsruntime.___type_.stringruntime.___type_.uncommonruntime.___type_.pkgpathruntime.resolveNameOffruntime.resolveTypeOffruntime.___type_.textOffruntime.name.tagLenruntime.name.nameruntime.name.tagruntime.name.pkgPathruntime.typelinksinitruntime.typesEqualruntime.writeErr"runtime.cgoCheckWriteBarrier.func1 runtime.cgoCheckTypedBlock.func1runtime.chansend.func1runtime.chanrecv.func1runtime.persistentalloc.func1runtime.allocmcache.func1runtime.freemcache.func1runtime.setGCPercent.func17runtime.__gcControllerState_.findRunnableGCWorker.func1runtime.gcStart.func1runtime.gcStart.func2runtime.gcMarkDone.func1.1runtime.gcMarkDone.func1runtime.gcMarkDone.func2runtime.gcMarkDone.func3runtime.gcMarkTermination.func1runtime.gcMarkTermination.func2runtime.gcMarkTermination.func3!runtime.gcMarkTermination.func4.1runtime.gcMarkTermination.func4runtime.gcBgMarkWorker.func1runtime.gcBgMarkWorker.func2runtime.markroot.func1runtime.gcAssistAlloc.func1runtime.scanstack.func1runtime.bgscavenge.func1runtime.bgscavenge.func2&runtime.__pageAlloc_.scavengeOne.func3runtime.sweepone.func1runtime.getempty.func1runtime.freeSomeWbufs.func1runtime.__mheap_.alloc.func1runtime.__mheap_.freeSpan.func1runtime.__pageAlloc_.find.func1runtime.mProf_Malloc.func1runtime.tracealloc.func1runtime.tracefree.func1runtime.wbBufFlush.func1runtime.newdefer.func1runtime.newdefer.func2runtime.freedefer.func1runtime.preprintpanics.func1$runtime.addOneOpenDeferFrame.func1.1"runtime.addOneOpenDeferFrame.func1runtime.throw.func1runtime.fatalthrow.func1runtime.fatalpanic.func1runtime.fatalpanic.func2runtime.hexdumpWords.func1runtime.main.func2runtime.goready.func1runtime.casgstatus.func1runtime.allocm.func1runtime.reentersyscall.func1runtime.exitsyscallfast.func1(runtime.exitsyscallfast_reacquired.func1runtime.malg.func1runtime.newproc.func1runtime.gfget.func1runtime.__p_.destroy.func1runtime.__rwmutex_.rlock.func1runtime.callers.func1runtime.tracebackHexdump.func1 runtime.initruntime_debug.setGCPercent sync.eventruntime.entersyscallruntime.exitsyscallruntime_debug.SetTracebackruntime.gostringtime.nowcallRetruntime.rt0_goruntime.checkASM runtime.gogo runtime.mcallruntime.systemstackruntime.systemstack_switchruntime.memhashruntime.memhash32runtime.memhash64runtime.jmpdeferruntime.asminitruntime.publicationBarrierruntime.procyieldruntime.morestackruntime.morestack_noctxtruntime.asmcgocallruntime.reflectcallruntime.call16runtime.call32runtime.call64runtime.call128runtime.call256runtime.call512runtime.call1024runtime.call2048runtime.call4096runtime.call8192runtime.call16384runtime.call32768runtime.call65536runtime.call131072runtime.call262144runtime.call524288runtime.call1048576runtime.call2097152runtime.call4194304runtime.call8388608runtime.call16777216runtime.call33554432runtime.call67108864runtime.call134217728runtime.call268435456runtime.call536870912runtime.call1073741824runtime.goexitruntime.gcWriteBarrierruntime.memclrNoHeapPointersruntime.memmoveruntime.asyncPreempt _rt0_wasm_jswasm_export_runwasm_export_resumewasm_pc_f_loopwasm_export_getsp runtime.pause runtime.exitruntime.wasmMoveruntime.wasmZeroruntime.wasmDivruntime.wasmTruncSruntime.wasmTruncUruntime.exitThreadruntime.osyieldruntime.usleepruntime.currentMemoryruntime.growMemoryruntime.resetMemoryDataViewruntime.wasmExitruntime.wasmWriteruntime.nanotime1runtime.walltime1runtime.scheduleTimeoutEventruntime.clearTimeoutEventruntime.getRandomDataruntime.__itabTableType_.add_fmruntime.__errorString_.Errortype..eq.runtime._panictype..eq.runtime._defertype..eq.runtime.sysmonticktype..eq.runtime.specialtype..eq.runtime.mspantype..eq.runtime.markBitstype..eq.runtime.mcache2type..eq.struct___runtime.gList__runtime.n_int32__type..eq.runtime.gcWorkruntime.__lockRank_.Stringruntime.__waitReason_.Stringtype..eq.runtime.sudogtype..eq.runtime.hchantype..eq._6_stringtype..eq._9_stringtype..eq.runtime.bitvectortype..eq.runtime.itabtype..eq.runtime._functype..eq.runtime.modulehashtype..eq.runtime.stackScanStatetype..eq.runtime.arenaHinttype..eq.runtime.mcentralLtype..eq.struct___runtime.mcentral_runtime.mcentral__runtime.pad__24_uint8__Qtype..eq._136_struct___runtime.mcentral_runtime.mcentral__runtime.pad__24_uint8__!type..eq.runtime.specialfinalizertype..eq.runtime.rwmutextype..eq._2_stringtype..eq._4_string#type..eq.runtime.TypeAssertionErrortype..eq.runtime.boundsErrorruntime.__boundsError_.Errortype..eq.runtime.eventruntime.__plainError_.Error main.main