diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b39c03108ed..5c17c1be8f6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -35,3 +35,13 @@ updates: update-types: - "minor" - "patch" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + groups: + all: + update-types: + - "minor" + - "patch" diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 71b9de6c8c7..d057cb2c222 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -44,22 +44,26 @@ jobs: packages: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 + - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false # will use the latest release available for ko - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 - name: Set up Cloud SDK - uses: google-github-actions/auth@140bb5113ffb6b65a7e9b937a81fa96cf5064462 # v2.1.11 + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 with: workload_identity_provider: 'projects/498091336538/locations/global/workloadIdentityPools/githubactions/providers/sigstore-cosign' service_account: 'github-actions@projectsigstore.iam.gserviceaccount.com' @@ -68,7 +72,7 @@ jobs: run: gcloud auth configure-docker --quiet - name: Login to GitHub Container Registry - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b9ec879d307..f84dc286778 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -51,12 +51,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Utilize Go Module Cache - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ~/go/pkg/mod @@ -65,11 +65,14 @@ jobs: restore-keys: | ${{ runner.os }}-go- - - name: Set correct version of Golang to use during CodeQL run - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/conformance-nightly.yml b/.github/workflows/conformance-nightly.yml index 29b8590970a..d18812a304b 100644 --- a/.github/workflows/conformance-nightly.yml +++ b/.github/workflows/conformance-nightly.yml @@ -26,31 +26,41 @@ permissions: jobs: conformance: runs-on: ubuntu-latest + strategy: + matrix: + environment: [ production, staging ] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false - run: make cosign conformance - uses: sigstore/sigstore-conformance@main with: entrypoint: ${{ github.workspace }}/conformance + environment: ${{ matrix.environment }} + xfail: "test_verify*PATH-message-digest-mismatch_fail]" - name: Create Issue on Failure if: failure() - uses: actions/github-script@v7 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 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 issueTitle = 'Conformance Tests Failed (${{ matrix.environment }})'; + const issueBody = `The nightly conformance tests have failed on ${{ matrix.environment }}. 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({ diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index d64220099de..c0dabac1637 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -28,17 +28,26 @@ permissions: jobs: conformance: runs-on: ubuntu-latest + strategy: + matrix: + environment: [ production, staging ] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false - run: make cosign conformance - - uses: sigstore/sigstore-conformance@fd90e6b0f3046f2276a6659481de6df495dea3b9 # v0.0.18 + - uses: sigstore/sigstore-conformance@21533cde107c734ebc153c3e3a24d75fc9811a36 # v0.0.29 with: entrypoint: ${{ github.workspace }}/conformance + environment: ${{ matrix.environment }} diff --git a/.github/workflows/donotsubmit.yaml b/.github/workflows/donotsubmit.yaml index 43cdb3a4975..2aeb491fa1f 100644 --- a/.github/workflows/donotsubmit.yaml +++ b/.github/workflows/donotsubmit.yaml @@ -35,9 +35,9 @@ jobs: steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v2.4.0 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v2.4.0 with: persist-credentials: false - name: Do Not Submit - uses: chainguard-dev/actions/donotsubmit@708219d4822f33611ac1a2653815cc10e1ab54a6 # v1.4.7 + uses: chainguard-dev/actions/donotsubmit@3b7bbeebc3a5d2bc37aa008350202651c32a26e1 # v1.6.22 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index b8ceccc4247..d1845f6035b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -39,13 +39,18 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false - name: Run cross platform e2e tests run: go test -tags=e2e,cross -v ./test/... @@ -54,13 +59,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false - name: Run pkcs11 end-to-end tests shell: bash @@ -74,6 +84,7 @@ jobs: env: VAULT_DEV_ROOT_TOKEN_ID: root options: >- + --cap-add=IPC_LOCK --health-cmd "VAULT_ADDR=http://127.0.0.1:8200 vault status" --health-interval 1s --health-timeout 5s @@ -89,19 +100,23 @@ jobs: SCAFFOLDING_RELEASE_VERSION: "v0.7.24" steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: setup vault - uses: cpanato/vault-installer@e7c1d664fa15219e89e43739e39a9df11ba00849 # v1.2.0 + uses: cpanato/vault-installer@fe568170412f5d81202ec528148f05176efbecc1 # v1.4.0 - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false - - uses: imjasonh/setup-crane@31b88efe9de28ae0ffa220711af4b60be9435f6e # v0.4 + - uses: imjasonh/setup-crane@59c71e96a00b28651f10369ba3359a6d730740a0 # v0.6 - name: Install cluster + sigstore uses: sigstore/scaffolding/actions/setup@main @@ -121,16 +136,21 @@ jobs: SCAFFOLDING_RELEASE_VERSION: "v0.7.24" steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false - name: Setup mirror - uses: chainguard-dev/actions/setup-mirror@main + uses: chainguard-dev/actions/setup-mirror@3b7bbeebc3a5d2bc37aa008350202651c32a26e1 # v1.6.22 with: mirror: mirror.gcr.io @@ -167,6 +187,7 @@ jobs: run: go test -tags=e2e,registry -v ./test/... env: COSIGN_TEST_REPO: insecure-registry.notlocal:5001 + TUF_ROOT_JSON: ${{ github.workspace }}/root.json - name: Setup local insecure OCI 1.1 registry run: | @@ -217,7 +238,24 @@ jobs: env: OCI11: yes COSIGN_TEST_REPO: insecure-oci-registry.notlocal:5002 + TUF_ROOT_JSON: ${{ github.workspace }}/root.json + + - name: Set up local HTTP registry + run: | + docker run -d --restart=always \ + --name $HTTP_REGISTRY_NAME \ + -p $HTTP_REGISTRY_PORT:5000 registry:2.8.1 + sudo echo "127.0.0.1 $HTTP_REGISTRY_NAME" | sudo tee -a /etc/hosts + env: + HTTP_REGISTRY_NAME: http-registry.notlocal + HTTP_REGISTRY_PORT: 5003 + + - name: Run HTTP registry tests + run: go test -tags=e2e,registry -v ./test/... + env: + COSIGN_TEST_REPO: http-registry.notlocal:5003 + TUF_ROOT_JSON: ${{ github.workspace }}/root.json - name: Collect diagnostics if: ${{ failure() }} - uses: chainguard-dev/actions/kind-diag@708219d4822f33611ac1a2653815cc10e1ab54a6 # v1.4.7 + uses: chainguard-dev/actions/kind-diag@3b7bbeebc3a5d2bc37aa008350202651c32a26e1 # v1.6.22 diff --git a/.github/workflows/e2e-with-binary.yml b/.github/workflows/e2e-with-binary.yml index ccb4ae7cc4f..9a41f3384e3 100644 --- a/.github/workflows/e2e-with-binary.yml +++ b/.github/workflows/e2e-with-binary.yml @@ -48,35 +48,26 @@ jobs: COSIGN_YES: "true" steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + shell: bash # To use awk on Windows + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false + - 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 + ./cosign sign-blob --bundle sigstore.json --yes README.md # Verify with sign-blob - ./cosign verify-blob README.md --certificate certificate.pem --signature README.md.sig + ./cosign verify-blob --bundle sigstore.json --certificate-identity-regexp ".*" --certificate-oidc-issuer-regexp ".*" README.md diff --git a/.github/workflows/github-oidc.yaml b/.github/workflows/github-oidc.yaml index e9a837fa96c..68c694713ae 100644 --- a/.github/workflows/github-oidc.yaml +++ b/.github/workflows/github-oidc.yaml @@ -48,14 +48,18 @@ jobs: KO_PREFIX: ghcr.io/${{ github.repository }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true - cache: true + cache: false # Install tools. - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index e78b11219bc..cb6f1575eb8 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -31,17 +31,23 @@ jobs: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false + - name: golangci-lint - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 + uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1 with: - version: v2.2 + version: v2.12 golangci-test-e2e: name: lint-test-e2e @@ -51,15 +57,21 @@ jobs: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false + - name: golangci-lint - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 + uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1 with: - version: v2.2 + version: v2.9 args: --build-tags e2e ./test diff --git a/.github/workflows/kind-verify-attestation.yaml b/.github/workflows/kind-verify-attestation.yaml index cddb4c31444..26aa72c599e 100644 --- a/.github/workflows/kind-verify-attestation.yaml +++ b/.github/workflows/kind-verify-attestation.yaml @@ -53,19 +53,24 @@ jobs: COSIGN_YES: "true" steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false # will use the latest release available for ko - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 - name: Install yq - uses: mikefarah/yq@f03c9dc599c37bfcaf533427211d05e51e6fee64 # v4.47.1 + uses: mikefarah/yq@1b9b4ac5187171d2e5e3129be0cfa827c7f9d53d # v4.53.3 - name: build cosign run: | @@ -100,38 +105,58 @@ jobs: 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' }} + - name: Get copy of TUF repository 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 + + - name: Initialize with custom TUF root pointing to local filesystem + if: ${{ matrix.tuf-root == 'air-gap' }} + run: | + # Grab the compressed repository for airgap testing. PWD=$(pwd) ROOT=${PWD}/repository/1.root.json REPOSITORY=${PWD}/repository ./cosign initialize --root ${ROOT} --mirror file://${REPOSITORY} + - name: Set TrustedRoot + run: | + trustedroot=$(find ./repository/targets -name "*.trusted_root.json") + echo "trustedroot=$trustedroot" >> $GITHUB_ENV + + - name: Create SigningConfig + run: | + ./cosign signing-config create \ + --fulcio="url=${FULCIO_URL},api-version=1,start-time=2024-01-01T00:00:00Z,operator=sigstore.dev" \ + --rekor="url=${REKOR_URL},api-version=1,start-time=2024-01-01T00:00:00Z,operator=sigstore.dev" \ + --rekor-config="ANY" \ + --tsa="url=${TSA_URL}/api/v1/timestamp,api-version=1,start-time=2024-01-01T00:00:00Z,operator=sigstore.dev" \ + --tsa-config="EXACT:1" \ + --out signingconfig.json + echo "signingconfig=signingconfig.json" >> $GITHUB_ENV + - 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} + ./cosign sign --signing-config=${signingconfig} --trusted-root=${trustedroot} --yes --allow-http-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} + ./cosign attest --predicate ./predicate-file --signing-config=${signingconfig} --trusted-root=${trustedroot} --allow-http-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} + ./cosign sign-blob README.md --signing-config=${signingconfig} --trusted-root=${trustedroot} --bundle blob.sigstore.json --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" + ./cosign verify --trusted-root=${trustedroot} --allow-http-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 + if ! ./cosign verify-attestation --trusted-root=${trustedroot} --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 --allow-http-registry ${demoimage} ; then echo Failed to verify attestation with a valid policy exit 1 else @@ -142,7 +167,7 @@ jobs: - 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 + if ./cosign verify-attestation --trusted-root=${trustedroot} --policy ./test/testdata/policies/cue-fails.cue --allow-http-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 @@ -152,20 +177,20 @@ jobs: - 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" + ./cosign verify-blob README.md --trusted-root=${trustedroot} --bundle blob.sigstore.json --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@708219d4822f33611ac1a2653815cc10e1ab54a6 # v1.4.7 + uses: chainguard-dev/actions/kind-diag@3b7bbeebc3a5d2bc37aa008350202651c32a26e1 # v1.6.22 - 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} + ./cosign attest --predicate ./test/testdata/attestations/vuln-predicate.json --type vuln --signing-config=${signingconfig} --trusted-root=${trustedroot} --allow-http-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 + if ! ./cosign verify-attestation --trusted-root=${trustedroot} --type vuln --policy ./test/testdata/policies/cue-vuln-works.cue --allow-http-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 @@ -176,7 +201,7 @@ jobs: - 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 + if ./cosign verify-attestation --trusted-root=${trustedroot} --type vuln --policy ./test/testdata/policies/cue-vuln-fails.cue --allow-http-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 diff --git a/.github/workflows/scorecard-action.yml b/.github/workflows/scorecard-action.yml index 3083085cf82..8dafc9c9a72 100644 --- a/.github/workflows/scorecard-action.yml +++ b/.github/workflows/scorecard-action.yml @@ -40,12 +40,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif @@ -61,7 +61,7 @@ jobs: # 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 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d87b0bfd58b..34e29070ff7 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -41,11 +41,12 @@ jobs: OS: ${{ matrix.os }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 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 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # In order: # * Module download cache @@ -60,16 +61,25 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + shell: bash # To use awk on Windows + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false + - name: Run Go tests run: go test -covermode atomic -coverprofile coverage.txt $(go list ./... | grep -v third_party/) + - name: Upload Coverage Report - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: env_vars: OS + - name: Run Go tests w/ `-race` if: ${{ runner.os == 'Linux' }} run: go test -race $(go list ./... | grep -v third_party/) @@ -81,7 +91,7 @@ jobs: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false # Related to https://github.com/sigstore/cosign/issues/3149 @@ -138,7 +148,7 @@ jobs: - 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 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # In order: # * Module download cache @@ -153,10 +163,16 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false + - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 - name: setup kind cluster run: | @@ -169,7 +185,7 @@ jobs: - name: Collect diagnostics if: ${{ failure() }} - uses: chainguard-dev/actions/kind-diag@708219d4822f33611ac1a2653815cc10e1ab54a6 # v1.4.7 + uses: chainguard-dev/actions/kind-diag@3b7bbeebc3a5d2bc37aa008350202651c32a26e1 # v1.6.22 e2e-windows-powershell-tests: name: Run PowerShell E2E tests @@ -177,16 +193,22 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + shell: bash # To use awk on Windows + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false # https://github.com/mvdan/github-actions-golang#how-do-i-set-up-caching-between-builds - - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # In order: # * Module download cache @@ -207,15 +229,22 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false + - name: Install addlicense run: go install github.com/google/addlicense@latest + - name: Check license headers run: | set -e diff --git a/.github/workflows/validate-release.yml b/.github/workflows/validate-release.yml index 7def77f8458..b0c5ac0d427 100644 --- a/.github/workflows/validate-release.yml +++ b/.github/workflows/validate-release.yml @@ -16,24 +16,29 @@ name: CI-Validate-Release-Job on: - push: + pull_request: branches: - main - release-* - pull_request: + +permissions: {} jobs: check-signature: runs-on: ubuntu-latest + + permissions: + contents: read + container: - image: ghcr.io/sigstore/cosign/cosign:v2.5.2-dev@sha256:14a20131240190350e18f002bdd61345d2803eff370913737392281e834ee22a + image: ghcr.io/sigstore/cosign/cosign:v3.0.6-dev@sha256:fd2e25faec7ea4c47bfbe6f50f00ff0e465fcea1f6d121eed7491b79278b19f7 steps: - name: Check Signature run: | - cosign verify ghcr.io/gythialy/golang-cross:v1.24.5-0@sha256:492c51e60ed27ff597511b0a24e6c5acb6e3e2e97bb68d7bd35f81a7e3dfa4d0 \ + cosign verify ghcr.io/gythialy/golang-cross:v1.26.3-0@sha256:9f7a53d7205e2f1f2742d624ff0b6e1531e8c22863dfb24ca966be5efbdee48b \ --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.5-0" + --certificate-identity "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.26.3-0" env: TUF_ROOT: /tmp @@ -42,16 +47,17 @@ jobs: needs: - check-signature + permissions: + contents: read + container: - image: ghcr.io/gythialy/golang-cross:v1.24.5-0@sha256:492c51e60ed27ff597511b0a24e6c5acb6e3e2e97bb68d7bd35f81a7e3dfa4d0 + image: ghcr.io/gythialy/golang-cross:v1.26.3-0@sha256:9f7a53d7205e2f1f2742d624ff0b6e1531e8c22863dfb24ca966be5efbdee48b volumes: - /usr:/host_usr - /opt:/host_opt - permissions: {} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false @@ -83,6 +89,7 @@ jobs: 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 @@ -90,7 +97,7 @@ jobs: run: make snapshot env: PROJECT_ID: honk-fake-project - RUNTIME_IMAGE: gcr.io/distroless/static-debian12:nonroot + RUNTIME_IMAGE: gcr.io/distroless/static-debian13:nonroot - name: check binaries run: | diff --git a/.github/workflows/verify-docgen.yaml b/.github/workflows/verify-docgen.yaml index 79d81f15d87..729b6902c77 100644 --- a/.github/workflows/verify-docgen.yaml +++ b/.github/workflows/verify-docgen.yaml @@ -36,11 +36,18 @@ jobs: steps: - name: deps run: sudo apt-get update && sudo apt-get install -yq libpcsclite-dev - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + + - name: Extract version of Go to use + run: echo "GOVERSION=$(awk -F'[:@]' '/FROM golang/{print $2; exit}' Dockerfile)" >> $GITHUB_ENV + + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: 'go.mod' + go-version: '${{ env.GOVERSION }}' check-latest: true + cache: false + - run: ./cmd/help/verify.sh diff --git a/.github/workflows/whitespace.yaml b/.github/workflows/whitespace.yaml index 525a9d3b776..faaaafe2ace 100644 --- a/.github/workflows/whitespace.yaml +++ b/.github/workflows/whitespace.yaml @@ -34,12 +34,12 @@ jobs: steps: - name: Check out code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: chainguard-dev/actions/trailing-space@708219d4822f33611ac1a2653815cc10e1ab54a6 # v1.4.7 + - uses: chainguard-dev/actions/trailing-space@3b7bbeebc3a5d2bc37aa008350202651c32a26e1 # v1.6.22 if: ${{ always() }} - - uses: chainguard-dev/actions/eof-newline@708219d4822f33611ac1a2653815cc10e1ab54a6 # v1.4.7 + - uses: chainguard-dev/actions/eof-newline@3b7bbeebc3a5d2bc37aa008350202651c32a26e1 # v1.6.22 if: ${{ always() }} diff --git a/.gitignore b/.gitignore index 473584dc6fa..4c5ee9a79ea 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ bin* dist/ cosignImagerefs -bundle +/bundle signature certificate sigstore-conformance diff --git a/.golangci.yml b/.golangci.yml index 5dacb8d3224..6f3f84d9346 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,8 +51,8 @@ linters: settings: printf: funcs: - - github.com/sigstore/cosign/v2/internal/ui.Infof - - github.com/sigstore/cosign/v2/internal/ui.Warnf + - github.com/sigstore/cosign/v3/internal/ui.Infof + - github.com/sigstore/cosign/v3/internal/ui.Warnf exclusions: generated: lax presets: @@ -78,6 +78,16 @@ linters: path: pkg/cosign/verify.go # NewEntry used for Rekor v1, will update to NewTlogEntry for Rekor v2 support text: SA1019 + - linters: + - staticcheck + path: pkg/cosign/verify_bundle_test.go + # NewEntry used for Rekor v1, will update to NewTlogEntry for Rekor v2 support + text: SA1019 + - linters: + - revive + path: cmd/cosign/errors/ + # Allow 'errors' package name in cmd/cosign/errors - it doesn't conflict with std lib + text: "var-naming: avoid package names that conflict with Go standard library package names" paths: - third_party$ - builtin$ diff --git a/.goreleaser.yml b/.goreleaser.yml index f9ad566f9d4..5c4f276f5b1 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -153,28 +153,25 @@ builds: 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}"] + args: ["sign-blob", "--bundle", "${signature}", "--key", "gcpkms://projects/{{ .Env.PROJECT_ID }}/locations/{{ .Env.KEY_LOCATION }}/keyRings/{{ .Env.KEY_RING }}/cryptoKeys/{{ .Env.KEY_NAME }}/versions/{{ .Env.KEY_VERSION }}", "${artifact}"] + signature: "${artifact}-kms.sigstore.json" 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}"] + args: ["sign-blob", "--bundle", "${signature}", "${artifact}"] + signature: "${artifact}.sigstore.json" 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}"] + args: ["sign-blob", "--bundle", "${signature}", "${artifact}"] + signature: "${artifact}.sigstore.json" 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}"] + args: ["sign-blob", "--bundle", "${signature}", "${artifact}"] + signature: "${artifact}.sigstore.json" artifacts: package nfpms: diff --git a/.ko.yaml b/.ko.yaml index bfd932461cf..e9bfa067d2b 100644 --- a/.ko.yaml +++ b/.ko.yaml @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -defaultBaseImage: gcr.io/distroless/static-debian12:nonroot +defaultBaseImage: gcr.io/distroless/static-debian13:nonroot builds: - id: cosign diff --git a/CHANGELOG.md b/CHANGELOG.md index f4000a587d6..a167dbdba50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,195 @@ +# v3.0.5 + +## Deprecations + +* Deprecate rekor-entry-type flag (#4691) +* Deprecate cosign triangulate (#4676) +* Deprecate cosign copy (#4681) + +## Features + +* Automatically require signed timestamp with Rekor v2 entries (#4666) +* Allow --local-image with --new-bundle-format for v2 and v3 signatures (#4626) +* Add mTLS support for TSA client connections when signing with a signing config (#4620) +* Enforce TSA requirement for Rekor v2, Fuclio signing (#4683) + +## Bug Fixes + +* Add empty predicate to cosign sign when payload type is application/vnd.in-toto+json (#4635) +* fix: avoid panic on malformed attestation payload (#4651) +* fix: avoid panic on malformed tlog entries (#4649) +* fix: avoid panic on malformed replace payload (#4653) +* Gracefully fail if bundle payload body is not a string (#4648) +* Verify validity of chain rather than just certificate (#4663) +* fix: avoid panic on malformed tlog entry body (#4652) + +## Documentation + +* docs(cosign): clarify RFC3161 revocation semantics (#4642) +* Fix typo in CLI help (#4701) + +# v3.0.4 + +v3.0.4 resolves https://github.com/sigstore/cosign/security/advisories/GHSA-whqx-f9j3-ch6m. + +## Changes + +* Fix bundle verify path for old bundle/trusted root (GHSA-whqx-f9j3-ch6m) (#4623) +* Optimize cosign tree performance by caching digest resolution (#4612) +* Don't require a trusted root to verify offline with a key (#4613) +* Support default services for trusted-root and signing-config creation (#4592) + +# v2.6.2 + +v2.6.2 resolves https://github.com/sigstore/cosign/security/advisories/GHSA-whqx-f9j3-ch6m. + +## Changes + +* Fix bundle verify path for old bundle/trusted root (GHSA-whqx-f9j3-ch6m) (#4624) +* bump sigstore deps to resolve build errors (#4619) + +# v3.0.3 + +Thank you for all of your feedback on Cosign v3! v3.0.3 fixes a number of bugs reported by +the community along with adding compatibility for the new bundle format and attestation +storage in OCI to additional commands. We're continuing to work on compatibility with +the remaining commands and will have a new release shortly. If you run into any problems, +please [file an issue](https://github.com/sigstore/cosign/issues) + +## Changes + +* 4554: Closes 4554 - Add warning when --output* is used (#4556) +* Protobuf bundle support for subcommand `clean` (#4539) +* Add staging flag to initialize with staging TUF metadata +* Updating sign-blob to also support signing with a certificate (#4547) +* Protobuf bundle support for subcommands `save` and `load` (#4538) +* Fix cert attachment for new bundle with signing config +* Fix OCI verification with local cert - old bundle +* Deprecate tlog-upload flag (#4458) +* fix: Use signal context for `sign` cli package. +* update offline verification directions (#4526) +* Fix signing/verifying annotations for new bundle +* Add support to download and attach for protobuf bundles (#4477) +* Add --signing-algorithm flag (#3497) +* Refactor signcommon bundle helpers +* Add --bundle and fix --upload for new bundle +* Pass insecure registry flags through to referrers +* Add protobuf bundle support for tree subcommand (#4491) +* Remove stale embed import (#4492) +* Support multiple container identities +* Fix segfault when no attestations are found (#4472) +* Use overridden repository for new bundle format (#4473) +* Remove --out flag from `cosign initialize` (#4462) +* Deprecate offline flag (#4457) +* Deduplicate code in sign/attest* and verify* commands (#4449) +* Cache signing config when calling initialize (#4456) + +# v3.0.2 + +v3.0.2 is a functionally equivalent release to v3.0.0 and v3.0.1, with a fix for CI to publish signed releases in the new bundle format. + +* Note that the `--bundle` flag specifying an output file to write the Sigstore bundle (which contains all relevant verification material) has moved from optional to required in v3. + +## Changes + +* choose different signature filename for KMS-signed release signatures (#4448) +* Update rekor-tiles version path (#4450) + +# v3.0.1 + +v3.0.1 is an equivalent release to v3.0.0, which was never published due to a failure in our CI workflows. + +* Note that the `--bundle` flag specifying an output file to write the Sigstore bundle (which contains all relevant verification material) has moved from optional to required in v3. + +## Changes + +* update goreleaser config for v3.0.0 release (#4446) + +# v3.0.0 + +Announcing the next major release of Cosign! + +Cosign v3 is a minor change from Cosign v2.6.x, with all of the new capabilities of recent +releases **on by default**, but will still allow you to disable them if you need the older functionality. +These new features include support for the standardized bundle format (`--new-bundle-fomat`), providing roots +of trust for verification and service URLs for signing via one file (`--trusted-root`, `--signing-config`), +and container signatures stored as an OCI Image 1.1 referring artifact. + +Learn more on our [v3 announcement blog post](https://blog.sigstore.dev/cosign-3-0-available/)! See +the changelogs for [v2.6.0](#v260), [v2.5.0](#v250), and [v2.4.0](#v240) for more information on recent +changes. + +If you have any feedback, please reach out on Slack or file an issue on GitHub. + +## Changes + +* Default to using the new protobuf format (#4318) +* Fetch service URLs from the TUF PGI signing config by default (#4428) +* Bump module version to v3 for Cosign v3.0 (#4427) + +# v2.6.1 + +## Bug Fixes + +* Partially populate the output of cosign verify when working with new bundles (#4416) +* Bump sigstore-go, move conformance back to tagged release (#4426) + +# v2.6.0 + +v2.6.0 introduces a number of new features, including: + +* Signing an in-toto statement rather than Cosign constructing one from a predicate, along with verifying a statement's subject using a digest and digest algorithm rather than providing a file reference (#4306) +* Uploading a signature and its verification material (a ["bundle"](https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto)) as an OCI Image 1.1 referring artifact, completing [#3927](https://github.com/sigstore/cosign/issues/3927) (#4316) +* Providing service URLs for signing and attesting using a [SigningConfig](https://github.com/sigstore/protobuf-specs/blob/4df5baadcdb582a70c2bc032e042c0a218eb3841/protos/sigstore_trustroot.proto#L185). Note that this is required when using a [Rekor v2](https://github.com/sigstore/rekor-tiles) instance (#4319) + +Example generation and verification of a signed in-toto statement: + +``` +cosign attest-blob --new-bundle-format=true --bundle="digest-key-test.sigstore.json" --key="cosign.key" --statement="../sigstore-go/examples/sigstore-go-signing/intoto.txt" +cosign verify-blob-attestation --bundle="digest-key-test.sigstore.json" --key=cosign.pub --type=unused --digest="b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" --digestAlg="sha256" +``` + +Example container signing and verification using the new bundle format and referring artifacts: + +``` +cosign sign --new-bundle-format=true ghcr.io/user/alpine@sha256:a19367999603840546b8612572e338ec076c6d1f2fec61760a9e11410f546733 +cosign verify --new-bundle-format=true ghcr.io/user/alpine@sha256:a19367999603840546b8612572e338ec076c6d1f2fec61760a9e11410f546733 +``` + +Example usage of a signing config provided by the public good instance's TUF repository: + +``` +cosign sign-blob --use-signing-config --bundle sigstore.json README.md +cosign verify-blob --new-bundle-format --bundle sigstore.json --certificate-identity $EMAIL --certificate-oidc-issuer $ISSUER --use-signed-timestamps README.md +``` + +v2.6.0 leverages sigstore-go's signing and verification APIs gated behind these new flags. In an upcoming major release, we will be +updating Cosign to default to producing and consuming bundles to align with all other Sigstore SDKs. + +## Features + +* Add to `attest-blob` the ability to supply a complete in-toto statement, and add to `verify-blob-attestation` the ability to verify with just a digest (#4306) +* Have cosign sign support bundle format (#4316) +* Add support for SigningConfig for sign-blob/attest-blob, support Rekor v2 (#4319) +* Add support for SigningConfig in sign/attest (#4371) +* Support self-managed keys when signing with sigstore-go (#4368) +* Don't require timestamps when verifying with a key (#4337) +* Don't load content from TUF if trusted root path is specified (#4347) +* Add a terminal spinner while signing with sigstore-go (#4402) +* Require exclusively a SigningConfig or service URLs when signing (#4403) +* Remove SHA256 assumption in sign-blob/verify-blob (#4050) +* Bump sigstore-go, support alternative hash algorithms with keys (#4386) + +## Breaking API Changes + +* `sign.SignerFromKeyOpts` no longer generates a key. Instead, it returns whether or not the client needs to generate a key, and if so, clients +should call `sign.KeylessSigner`. This allows clients to more easily manage key generation. + +## Bug Fixes + +* Verify subject with bundle only when checking claims (#4320) +* Fixes to cosign sign / verify for the new bundle format (#4346) + # v2.5.3 ## Features @@ -2447,7 +2639,7 @@ We would love to thank the contributors: 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. +The main goal of this release is to release something we can start using to sign other releases of [sigstore](https://sigstore.dev) projects, including `cosign` itself. We expect many flags, commands, and formats to change going forward. No backwards compatibility is promised or implied. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..97d18f41e63 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +# +# 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. + +# This is used to we scrap the go version and use in CI to get the latest go version +# and we use dependabot to keep the go version up to date +FROM golang:1.26.3 diff --git a/Makefile b/Makefile index 92b2171e3b9..53ded438b33 100644 --- a/Makefile +++ b/Makefile @@ -171,7 +171,7 @@ ko-cosign: 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 + github.com/sigstore/cosign/v3/cmd/cosign .PHONY: ko-cosign-dev ko-cosign-dev: @@ -180,7 +180,7 @@ ko-cosign-dev: 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 + github.com/sigstore/cosign/v3/cmd/cosign .PHONY: ko-local ko-local: @@ -189,7 +189,7 @@ ko-local: KOCACHE=$(KOCACHE_PATH) ko build --base-import-paths \ --tags $(GIT_VERSION) --tags $(GIT_HASH) \ $(ARTIFACT_HUB_LABELS) \ - github.com/sigstore/cosign/v2/cmd/cosign + github.com/sigstore/cosign/v3/cmd/cosign .PHONY: ko-local-dev ko-local-dev: @@ -198,7 +198,7 @@ ko-local-dev: 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 + github.com/sigstore/cosign/v3/cmd/cosign ################## # help diff --git a/README.md b/README.md index 7083fd71df4..4dcb7060f48 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Cosign supports: `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. +Click [here](https://join.slack.com/t/sigstore/shared_invite/zt-2ub0ztl5z-PkWb_Ldwef5d6nb~oryaTA) for the invite link. ## Installation @@ -79,6 +79,7 @@ ENTRYPOINT [ "cosign" ] 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 +* explore broader keyless blob signing/verification flows in the [Sigstore Cosign Quickstart](https://docs.sigstore.dev/quickstart/quickstart-cosign/) ### Sign a container and store the signature in the registry @@ -141,6 +142,14 @@ The following checks were performed on these signatures: ### Verify a container in an air-gapped environment +**Note:** This section is out of date. + +**Note:** Most verification workflows require periodically requesting service keys from a TUF repository. +For airgapped verification of signatures using the public-good instance, you will need to retrieve the +[trusted root](https://github.com/sigstore/root-signing/blob/main/targets/trusted_root.json) file from the production +TUF repository. The contents of this file will change without notification. By not using TUF, you will need +to build your own mechanism to keep your airgapped copy of this file up-to-date. + 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. @@ -156,8 +165,14 @@ 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 +```shell +cosign verify \ + --certificate-identity $CERT_IDENTITY \ + --certificate-oidc-issuer $CERT_OIDC_ISSUER \ + --offline=true \ + --new-bundle-format=false \ # for artifacts signed without the new protobuf bundle format + --trusted-root ~/.sigstore/root/tuf-repo-cdn.sigstore.dev/targets/trusted_root.json \ # default location of trusted root + --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. @@ -167,21 +182,34 @@ If you signed with a keypair, the same command will work, assuming the public ke cosign verify --key cosign.pub --offline --local-image ./path/to/dir ``` -### What ** is not ** production ready? +### Identity-based blob signing and verification + +Use keyless blob signing (`cosign sign-blob` without `--key`) and verify against the expected signer identity: + +```shell +$ cosign sign-blob artifact --bundle artifact.sigstore.json --yes +$ cosign verify-blob artifact \ + --bundle artifact.sigstore.json \ + --certificate-identity "https://github.com/ORG/REPO/.github/workflows/release.yml@refs/heads/main" \ + --certificate-oidc-issuer "https://token.actions.githubusercontent.com" +``` + +### Troubleshooting -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! +If you encounter issues with Cosign, first make sure you are using a recent release: The Cosign project actively supports the most recent release as well as the last release in the v2 series. -#### Formats/Specifications +#### Common issues and remedies -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. +1. Verification fails with `failed to verify timestamps: threshold not met for verified log entry integrated timestamps: 0 < 1`: You may be verifying a signature that requires RFC3161 timestamp support + * Upgrade to most recent Cosign or + * With Cosign 2.6.x, use `--use-signed-timestamps` +1. Verification fails with `no signatures found`: You may be verifying an image signature that requires support for Rekor v2 transparency log + * Upgrade to most recent Cosign +1. Signing fails with HTTP errors: Signing with Cosign depends on multiple Sigstore services. Retrying on failure may be a useful workaround if any of these services fail -- filing issues for specific failures is also appreciated -These include: +#### My problem is something else -* The SBOM specification for storing SBOMs in a container registry -* The In-Toto attestation format +Please open an [issue](https://github.com/sigstore/cosign/issues/new/choose) or ask in the [slack channel](#info). ## Working with Other Artifacts @@ -761,8 +789,6 @@ will be released when there are breaking features. 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. +## Bundle files in GitHub Release Assets -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. +The GitHub release assets for `cosign` contain Sigstore bundle files 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](https://docs.sigstore.dev/cosign/system_config/installation/#verifying-cosign-with-artifact-key). diff --git a/cmd/conformance/main.go b/cmd/conformance/main.go index d4916df6df6..e47d6292d4e 100644 --- a/cmd/conformance/main.go +++ b/cmd/conformance/main.go @@ -30,22 +30,20 @@ var certOIDC *string var certSAN *string var identityToken *string var trustedRootPath *string +var signingConfigPath *string +var keyPath *string +var inToto bool +var staging bool 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]) + fmt.Printf("\t%s sign-bundle [--staging] [--in-toto] --identity-token TOKEN [--signing-config FILE] [--trusted-root FILE] --bundle FILE FILE\n", os.Args[0]) + fmt.Printf("\t%s verify-bundle [--staging] --bundle FILE [--certificate-identity IDENTITY --certificate-oidc-issuer URL] [--key FILE] [--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 @@ -61,6 +59,18 @@ func parseArgs() { case "--trusted-root": trustedRootPath = &os.Args[i+1] i += 2 + case "--signing-config": + signingConfigPath = &os.Args[i+1] + i += 2 + case "--key": + keyPath = &os.Args[i+1] + i += 2 + case "--in-toto": + inToto = true + i++ + case "--staging": + staging = true + i++ default: i++ } @@ -79,7 +89,11 @@ func main() { switch os.Args[1] { case "sign-bundle": - args = append(args, "sign-blob") + if inToto { + args = append(args, "attest-blob") + } else { + args = append(args, "sign-blob") + } args = append(args, "-y") case "verify-bundle": @@ -102,7 +116,7 @@ func main() { } default: - log.Fatalf("Unsupported command %s", os.Args[1]) + log.Fatalf("Unsupported command %q", os.Args[1]) // #nosec G706 -- CLI tool, args are operator-supplied } if bundlePath != nil { @@ -121,15 +135,30 @@ func main() { if trustedRootPath != nil { args = append(args, "--trusted-root", *trustedRootPath) } + if signingConfigPath != nil { + args = append(args, "--signing-config", *signingConfigPath) + } + if keyPath != nil { + args = append(args, "--key", *keyPath) + } + if inToto { + args = append(args, "--statement") + } args = append(args, os.Args[len(os.Args)-1]) dir := filepath.Dir(os.Args[0]) - initCmd := exec.Command(filepath.Join(dir, "cosign"), "initialize") // #nosec G204 + initArgs := []string{"initialize"} + if staging { + initArgs = append(initArgs, "--staging") + } + initCmd := exec.Command(filepath.Join(dir, "cosign"), initArgs...) // #nosec G204,G702 -- conformance harness invokes the sibling cosign binary + initCmd.Stdout = os.Stdout + initCmd.Stderr = os.Stderr err := initCmd.Run() if err != nil { log.Fatal(err) } - cmd := exec.Command(filepath.Join(dir, "cosign"), args...) // #nosec G204 + cmd := exec.Command(filepath.Join(dir, "cosign"), args...) // #nosec G204,G702 -- conformance harness invokes the sibling cosign binary var out strings.Builder cmd.Stdout = &out cmd.Stderr = &out diff --git a/cmd/cosign/cli/attach.go b/cmd/cosign/cli/attach.go index f8c384f97ac..915404e83a9 100644 --- a/cmd/cosign/cli/attach.go +++ b/cmd/cosign/cli/attach.go @@ -19,15 +19,16 @@ import ( "fmt" "os" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/attach" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/attach" + "github.com/sigstore/cosign/v3/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", + Use: "attach", + Short: "Provides utilities for attaching artifacts to other artifacts in a registry", + Deprecated: "attach will be removed in v4.0.0 (see https://github.com/sigstore/cosign/issues/4696). Instead, please use oras for attaching artifacts to other artifacts in a registry", } cmd.AddCommand( diff --git a/cmd/cosign/cli/attach/attach.go b/cmd/cosign/cli/attach/attach.go index 76bb5078c8f..15440379cb9 100644 --- a/cmd/cosign/cli/attach/attach.go +++ b/cmd/cosign/cli/attach/attach.go @@ -22,12 +22,13 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "github.com/sigstore/cosign/v3/pkg/types" + "github.com/sigstore/sigstore-go/pkg/bundle" ) func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, signedPayloads []string, imageRef string) error { @@ -37,7 +38,28 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, signed } for _, payload := range signedPayloads { - if err := attachAttestation(ctx, ociremoteOpts, payload, imageRef, regOpts.NameOptions()); err != nil { + fmt.Fprintf(os.Stderr, "Using payload from: %s", payload) + + ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) + if err != nil { + return err + } + if _, ok := ref.(name.Digest); !ok { + ui.Warnf(ctx, ui.TagReferenceMessage, imageRef) + } + + digest, err := ociremote.ResolveDigest(ref, ociremoteOpts...) + if err != nil { + return err + } + + // Detect if we are using new bundle format + b, err := bundle.LoadJSONFromPath(payload) + if err == nil { + return attachAttestationNewBundle(ociremoteOpts, b, digest) + } + + if err := attachAttestation(ociremoteOpts, payload, digest); err != nil { return fmt.Errorf("attaching payload from %s: %w", payload, err) } } @@ -45,8 +67,29 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, signed 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) +func attachAttestationNewBundle(remoteOpts []ociremote.Option, b *bundle.Bundle, digest name.Digest) error { + envelope, err := b.Envelope() + if err != nil { + return err + } + if envelope == nil { + return fmt.Errorf("bundle does not have DSSE envelope") + } + statement, err := envelope.Statement() + if err != nil { + return err + } + if statement == nil { + return fmt.Errorf("unable to understand bundle envelope statement") + } + bundleBytes, err := b.MarshalJSON() + if err != nil { + return err + } + return ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, statement.PredicateType, remoteOpts...) +} + +func attachAttestation(remoteOpts []ociremote.Option, signedPayload string, digest name.Digest) error { attestationFile, err := os.Open(signedPayload) if err != nil { return err @@ -73,22 +116,6 @@ func attachAttestation(ctx context.Context, remoteOpts []ociremote.Option, signe 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 { - ui.Warnf(ctx, ui.TagReferenceMessage, imageRef) - } - 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 { diff --git a/cmd/cosign/cli/attach/sbom.go b/cmd/cosign/cli/attach/sbom.go index df651197f63..a7a4f84dd78 100644 --- a/cmd/cosign/cli/attach/sbom.go +++ b/cmd/cosign/cli/attach/sbom.go @@ -33,11 +33,11 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote" + "github.com/sigstore/cosign/v3/internal/ui" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/oci/static" ) func SBOMCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts options.RegistryExperimentalOptions, sbomRef string, sbomType ocitypes.MediaType, imageRef string) error { diff --git a/cmd/cosign/cli/attach/sig.go b/cmd/cosign/cli/attach/sig.go index 812c5cc87a6..c09fe03df3e 100644 --- a/cmd/cosign/cli/attach/sig.go +++ b/cmd/cosign/cli/attach/sig.go @@ -24,22 +24,16 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/oci/static" + sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" ) 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 @@ -52,10 +46,12 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, 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 + + // Detect if we are using new bundle format + b, err := sgbundle.LoadJSONFromPath(payloadRef) + if err == nil { + return attachAttestationNewBundle(ociremoteOpts, b, digest) + } var payload []byte if payloadRef == "" { @@ -67,6 +63,13 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, sigRef, return err } + b64SigBytes, err := signatureBytes(sigRef) + if err != nil { + return err + } else if len(b64SigBytes) == 0 { + return errors.New("empty signature") + } + sig, err := static.NewSignature(payload, string(b64SigBytes)) if err != nil { return err diff --git a/cmd/cosign/cli/attest.go b/cmd/cosign/cli/attest.go index f9ac6a85c55..e3c6c34b7ac 100644 --- a/cmd/cosign/cli/attest.go +++ b/cmd/cosign/cli/attest.go @@ -16,15 +16,12 @@ package cli import ( - "context" "fmt" - "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/sigstore/cosign/v2/internal/ui" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/attest" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" "github.com/spf13/cobra" ) @@ -33,7 +30,7 @@ func Attest() *cobra.Command { cmd := &cobra.Command{ Use: "attest", - Short: "Attest the supplied container image.", + 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 @@ -71,6 +68,12 @@ func Attest() *cobra.Command { Args: cobra.MinimumNArgs(1), PersistentPreRun: options.BindViper, + PreRunE: func(_ *cobra.Command, _ []string) error { + if o.NewBundleFormat && o.NoUpload && o.BundlePath == "" { + return fmt.Errorf("must enable upload to the OCI registry or specify a local --bundle path with --new-bundle-format") + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { oidcClientSecret, err := o.OIDC.ClientSecret() if err != nil { @@ -78,35 +81,37 @@ func Attest() *cobra.Command { } 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, + 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, + IssueCertificateForExistingKey: o.IssueCertificate, + BundlePath: o.BundlePath, + NewBundleFormat: o.NewBundleFormat, } - if o.Key == "" && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" { // Get the trusted root if using fulcio for signing - trustedMaterial, err := cosign.TrustedRoot() - if err != nil { - ui.Warnf(context.Background(), "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) - } - ko.TrustedMaterial = trustedMaterial + if err := signcommon.LoadTrustedMaterialAndSigningConfig(cmd.Context(), &ko, o.UseSigningConfig, o.SigningConfigPath, + o.Rekor.URL, o.Fulcio.URL, o.OIDC.Issuer, o.TSAServerURL, o.TrustedRootPath, o.TlogUpload, + o.NewBundleFormat, "", o.Key, o.IssueCertificate, + "", "", "", "", "", ""); err != nil { + return err } + attestCommand := attest.AttestCommand{ KeyOpts: ko, RegistryOptions: o.Registry, diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index 6a8443726e8..afdcaa34eba 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -16,57 +16,30 @@ 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" + "google.golang.org/protobuf/encoding/protojson" + + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" + cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + cremote "github.com/sigstore/cosign/v3/pkg/cosign/remote" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "github.com/sigstore/cosign/v3/pkg/types" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + "github.com/sigstore/sigstore/pkg/signature" ) -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 @@ -103,14 +76,10 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { if err != nil { return err } - ref, err := name.ParseReference(imageRef, c.NameOptions()...) + ref, err := signcommon.ParseOCIReference(ctx, 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 @@ -122,6 +91,9 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { if err != nil { return err } + if c.RegistryOptions.AllowHTTPRegistry || c.RegistryOptions.AllowInsecure { + ociremoteOpts = append(ociremoteOpts, ociremote.WithNameOptions(name.Insecure)) + } digest, err := ociremote.ResolveDigest(ref, ociremoteOpts...) if err != nil { return err @@ -132,14 +104,6 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { // 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) @@ -156,112 +120,114 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { return err } - payload, err := json.Marshal(sh) + payload, err := sh.MarshalJSON() if err != nil { return err } - signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx)) - if err != nil { - return fmt.Errorf("signing: %w", err) + + bundleOpts := signcommon.CommonBundleOpts{ + Payload: payload, + Digest: digest, + PredicateType: predicateURI, + BundlePath: c.BundlePath, + Upload: !c.NoUpload, + OCIRemoteOpts: ociremoteOpts, } - if c.NoUpload { - fmt.Println(string(signedPayload)) - return nil + if c.SigningConfig == nil { + c.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(c.KeyOpts, c.TlogUpload) + if err != nil { + return fmt.Errorf("creating signing config: %w", err) + } } - opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} - if sv.Cert != nil { - opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) + bundleBytes, pubKey, pubKeyPem, hashAlgProto, err := signcommon.NewAttestationBundle(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial) + if err != nil { + return fmt.Errorf("creating bundle: %w", err) } - 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 + + if c.NewBundleFormat { + if c.BundlePath != "" { + if err := os.WriteFile(c.BundlePath, bundleBytes, 0600); err != nil { + return fmt.Errorf("create bundle file: %w", err) } - } else { - tsaPayload = signedPayload + ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath) } - 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)) + if !c.NoUpload { + if err := ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, bundleOpts.PredicateType, ociremoteOpts...); err != nil { + return fmt.Errorf("writing bundle: %w", err) + } + } + return nil } - predicateType, err := options.ParsePredicateType(c.PredicateType) - if err != nil { - return err + var pb protobundle.Bundle + if err := protojson.Unmarshal(bundleBytes, &pb); err != nil { + return fmt.Errorf("unmarshalling bundle: %w", err) } - predicateTypeAnnotation := map[string]string{ - "predicateType": predicateType, + bundleComponents, err := signcommon.ExtractComponentsFromProtoBundle(&pb) + if err != nil { + return fmt.Errorf("extracting components from bundle: %w", err) } - // 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) + legacyBundleBytes, err := signcommon.NewLegacyBundleFromProtoBundleComponents(bundleComponents, pubKeyPem) if err != nil { - return fmt.Errorf("should upload to tlog: %w", err) + return fmt.Errorf("creating legacy bundle: %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 + if c.BundlePath != "" { + if err := os.WriteFile(c.BundlePath, legacyBundleBytes, 0600); err != nil { + return fmt.Errorf("create bundle file: %w", err) } - opts = append(opts, static.WithBundle(cbundle.EntryToBundle(rekorEntry))) + ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath) } - sig, err := static.NewAttestation(signedPayload, opts...) - if err != nil { - return err + if c.NoUpload { + return nil } - 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...) + certPem, chainPem := signcommon.EncodeCertificatesToPEM(bundleComponents.Certificates) + + opts := []static.Option{ + static.WithLayerMediaType(types.DssePayloadType), + static.WithAnnotations(map[string]string{ + "predicateType": predicateURI, + }), + } + if certPem != nil { + opts = append(opts, static.WithCertChain(certPem, chainPem)) + } + + if len(bundleComponents.RFC3161Timestamps) > 0 { + opts = append(opts, static.WithRFC3161Timestamp(cbundle.TimestampToRFC3161Timestamp(bundleComponents.RFC3161Timestamps[0].GetSignedTimestamp()))) + } + + predicateTypeAnnotation := map[string]string{ + "predicateType": predicateURI, + } + // Add predicateType as manifest annotation + opts = append(opts, static.WithAnnotations(predicateTypeAnnotation)) + + if len(bundleComponents.RekorEntries) > 0 { + opts = append(opts, static.WithBundle(signcommon.RekorBundleFromProtoTlogEntry(bundleComponents.RekorEntries[0]))) + } + + ociSig, err := static.NewAttestation(bundleComponents.Signature, opts...) + if err != nil { + return fmt.Errorf("creating attestation: %w", err) } // 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...) + ddVerifier, err := signature.LoadVerifier(pubKey, signcommon.ProtoHashAlgoToHash(hashAlgProto)) + if err != nil { + return fmt.Errorf("loading verifier: %w", err) + } + dd := cremote.NewDupeDetector(ddVerifier) signOpts := []mutate.SignOption{ mutate.WithDupeDetector(dd), mutate.WithRecordCreationTimestamp(c.RecordCreationTimestamp), @@ -273,9 +239,9 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { } // Attach the attestation to the entity. - newSE, err := mutate.AttachAttestationToEntity(se, sig, signOpts...) + newSE, err := mutate.AttachAttestationToEntity(se, ociSig, signOpts...) if err != nil { - return err + return fmt.Errorf("attaching attestation: %w", err) } // Publish the attestations associated with this entity diff --git a/cmd/cosign/cli/attest/attest_blob.go b/cmd/cosign/cli/attest/attest_blob.go index b899e3e9606..a337400f7f6 100644 --- a/cmd/cosign/cli/attest/attest_blob.go +++ b/cmd/cosign/cli/attest/attest_blob.go @@ -18,12 +18,8 @@ import ( "bytes" "context" "crypto" - "crypto/sha256" - "crypto/x509" - "encoding/base64" "encoding/hex" "encoding/json" - "errors" "fmt" "io" "os" @@ -33,23 +29,13 @@ import ( "time" intotov1 "github.com/in-toto/attestation/go/v1" - "github.com/secure-systems-lab/go-securesystemslib/dsse" - "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" + cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle" 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" "google.golang.org/protobuf/encoding/protojson" ) @@ -96,20 +82,10 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error 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") - } - - 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) var payload []byte + var err error if c.StatementPath != "" { fmt.Fprintln(os.Stderr, "Using statement from:", c.StatementPath) @@ -159,220 +135,103 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error if err != nil { return err } - payload, err = json.Marshal(sh) + payload, err = sh.MarshalJSON() if err != nil { return err } } - sig, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx)) - if err != nil { - return fmt.Errorf("signing: %w", err) + bundleOpts := signcommon.CommonBundleOpts{ + Payload: payload, + BundlePath: c.BundlePath, } - 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 c.SigningConfig == nil { + var err error + c.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(c.KeyOpts, c.TlogUpload) if err != nil { - return err + return fmt.Errorf("creating signing config: %w", err) } - rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(timestampBytes) - // TODO: Consider uploading RFC3161 TS to Rekor + } - if rfc3161Timestamp == nil { - return fmt.Errorf("rfc3161 timestamp is nil") - } + bundleBytes, _, pubKeyPem, _, err := signcommon.NewAttestationBundle(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial) + if err != nil { + return fmt.Errorf("creating bundle: %w", err) + } - 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) + if c.NewBundleFormat { + if err := os.WriteFile(c.BundlePath, bundleBytes, 0600); err != nil { + return fmt.Errorf("create bundle file: %w", err) } + ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath) + return nil } - signer, err := sv.Bytes(ctx) - if err != nil { - return err + var pb protobundle.Bundle + if err := protojson.Unmarshal(bundleBytes, &pb); err != nil { + return fmt.Errorf("unmarshalling bundle: %w", 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) + bundleComponents, err := signcommon.ExtractComponentsFromProtoBundle(&pb) + if err != nil { + return err } 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 - } + contents, err := signcommon.NewLegacyBundleFromProtoBundleComponents(bundleComponents, pubKeyPem) + if err != nil { + return fmt.Errorf("creating legacy bundle: %w", 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) + ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath) } if c.OutputSignature != "" { - if err := os.WriteFile(c.OutputSignature, sig, 0600); err != nil { + if err := os.WriteFile(c.OutputSignature, bundleComponents.Signature, 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)) + fmt.Fprintln(os.Stdout, string(bundleComponents.Signature)) } if c.OutputAttestation != "" { if err := os.WriteFile(c.OutputAttestation, payload, 0600); err != nil { - return fmt.Errorf("create signature file: %w", err) + return fmt.Errorf("create attestation 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) + if len(bundleComponents.Certificates) == 0 { + return fmt.Errorf("no certificate found in bundle") } - 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 { + certPem, _ := signcommon.EncodeCertificatesToPEM(bundleComponents.Certificates) + if err := os.WriteFile(c.OutputCertificate, certPem, 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 + if c.RFC3161TimestampPath != "" { + if len(bundleComponents.RFC3161Timestamps) == 0 { + return fmt.Errorf("no RFC3161 timestamp found in bundle") } - pkixPubKey, err := x509.MarshalPKIXPublicKey(pubKey) + legacyTimestamp := cbundle.TimestampToRFC3161Timestamp(bundleComponents.RFC3161Timestamps[0].GetSignedTimestamp()) + ts, err := json.Marshal(legacyTimestamp) if err != nil { - return nil, err + return fmt.Errorf("marshalling timestamp: %w", 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 + if err := os.WriteFile(c.RFC3161TimestampPath, ts, 0600); err != nil { + return fmt.Errorf("create timestamp file: %w", err) + } + fmt.Fprintln(os.Stderr, "Timestamp wrote in the file ", c.RFC3161TimestampPath) } - return contents, nil + return nil } func validateStatement(payload []byte) (string, error) { diff --git a/cmd/cosign/cli/attest/attest_blob_test.go b/cmd/cosign/cli/attest/attest_blob_test.go index 806de15d058..b4aa3200f39 100644 --- a/cmd/cosign/cli/attest/attest_blob_test.go +++ b/cmd/cosign/cli/attest/attest_blob_test.go @@ -30,13 +30,13 @@ import ( "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/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" "github.com/stretchr/testify/assert" @@ -172,6 +172,7 @@ func TestAttestBlobCmdLocalKeyAndCert(t *testing.T) { keyOpts := options.KeyOpts{KeyRef: tc.keyref} if tc.newBundle { keyOpts.NewBundleFormat = true + keyOpts.BundlePath = filepath.Join(td, "output.bundle") } at := AttestBlobCommand{ KeyOpts: keyOpts, @@ -250,8 +251,8 @@ func TestAttestBlob(t *testing.T) { if err != nil { t.Fatalf("decoding dsse payload: %v", err) } - var statement in_toto.Statement - if err := json.Unmarshal(decodedPredicate, &statement); err != nil { + statement := &attestation.Statement{} + if err := statement.UnmarshalJSON(decodedPredicate); err != nil { t.Fatalf("decoding predicate: %v", err) } if statement.Subject == nil || len(statement.Subject) != 1 { diff --git a/cmd/cosign/cli/attest/common.go b/cmd/cosign/cli/attest/common.go index e5f4589a343..b9bb6dcebcc 100644 --- a/cmd/cosign/cli/attest/common.go +++ b/cmd/cosign/cli/attest/common.go @@ -15,13 +15,9 @@ 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) { @@ -37,15 +33,3 @@ func predicateReader(predicatePath string) (io.ReadCloser, error) { } 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) -} diff --git a/cmd/cosign/cli/attest_blob.go b/cmd/cosign/cli/attest_blob.go index 022bd095e7f..dd1f7204d86 100644 --- a/cmd/cosign/cli/attest_blob.go +++ b/cmd/cosign/cli/attest_blob.go @@ -15,14 +15,12 @@ package cli import ( - "context" + "fmt" - "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/sigstore/cosign/v2/internal/ui" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/attest" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" "github.com/spf13/cobra" ) @@ -31,7 +29,7 @@ func AttestBlob() *cobra.Command { cmd := &cobra.Command{ Use: "attest-blob", - Short: "Attest the supplied 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 @@ -53,6 +51,12 @@ func AttestBlob() *cobra.Command { echo | cosign attest-blob --predicate - --yes`, PersistentPreRun: options.BindViper, + PreRunE: func(_ *cobra.Command, _ []string) error { + if o.NewBundleFormat && o.BundlePath == "" { + return fmt.Errorf("must specify --bundle with --new-bundle-format") + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { if o.Predicate.Statement == "" && len(args) != 1 { return cobra.ExactArgs(1)(cmd, args) @@ -63,37 +67,38 @@ func AttestBlob() *cobra.Command { } 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, + 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, + IssueCertificateForExistingKey: o.IssueCertificate, + BundlePath: o.BundlePath, + NewBundleFormat: o.NewBundleFormat, } - if o.Key == "" && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" { // Get the trusted root if using fulcio for signing - trustedMaterial, err := cosign.TrustedRoot() - if err != nil { - ui.Warnf(context.Background(), "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) - } - ko.TrustedMaterial = trustedMaterial + if err := signcommon.LoadTrustedMaterialAndSigningConfig(cmd.Context(), &ko, o.UseSigningConfig, o.SigningConfigPath, + o.Rekor.URL, o.Fulcio.URL, o.OIDC.Issuer, o.TSAServerURL, o.TrustedRootPath, o.TlogUpload, + o.NewBundleFormat, o.BundlePath, o.Key, o.IssueCertificate, + "", o.OutputAttestation, o.OutputCertificate, "", o.OutputSignature, o.RFC3161TimestampPath); err != nil { + return err } + v := attest.AttestBlobCommand{ KeyOpts: ko, CertPath: o.Cert, diff --git a/cmd/cosign/cli/bundle.go b/cmd/cosign/cli/bundle.go index ba08d6b6545..ea2354c033c 100644 --- a/cmd/cosign/cli/bundle.go +++ b/cmd/cosign/cli/bundle.go @@ -18,8 +18,8 @@ package cli import ( "context" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/bundle" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/bundle" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" "github.com/spf13/cobra" ) @@ -31,6 +31,8 @@ func Bundle() *cobra.Command { } cmd.AddCommand(bundleCreate()) + cmd.AddCommand(bundleUpgrade()) + cmd.AddCommand(bundleInspect()) return cmd } @@ -42,6 +44,11 @@ func bundleCreate() *cobra.Command { Use: "create", Short: "Create a Sigstore protobuf bundle", Long: "Create a Sigstore protobuf bundle by supplying signed material", + Example: ` # create a bundle from a signature and certificate + cosign bundle create --artifact --signature --certificate --out bundle.sigstore.json + + # create a bundle from an attestation + cosign bundle create --artifact --attestation --out bundle.sigstore.json`, RunE: func(cmd *cobra.Command, _ []string) error { bundleCreateCmd := &bundle.CreateCmd{ Artifact: o.Artifact, @@ -68,3 +75,45 @@ func bundleCreate() *cobra.Command { o.AddFlags(cmd) return cmd } + +func bundleUpgrade() *cobra.Command { + o := &options.BundleUpgradeOptions{} + + cmd := &cobra.Command{ + Use: "upgrade ", + Short: "Upgrade a Sigstore protobuf bundle", + Long: "Upgrade a Sigstore Protobuf bundle to the latest version. This command only supports standardized bundles.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + bundleUpgradeCmd := &bundle.UpgradeCmd{ + Out: o.Out, + RekorURL: o.RekorURL, + } + + ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) + defer cancel() + + return bundleUpgradeCmd.Exec(ctx, args[0]) + }, + } + + o.AddFlags(cmd) + return cmd +} + +func bundleInspect() *cobra.Command { + cmd := &cobra.Command{ + Use: "inspect BUNDLE", + Short: "Inspect a Sigstore protobuf bundle", + Args: cobra.ExactArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + bundleInspectCmd := &bundle.InspectCmd{ + BundlePath: args[0], + } + + return bundleInspectCmd.Exec() + }, + } + + return cmd +} diff --git a/cmd/cosign/cli/bundle/bundle.go b/cmd/cosign/cli/bundle/bundle.go index 54778e45e99..d14f55cdee4 100644 --- a/cmd/cosign/cli/bundle/bundle.go +++ b/cmd/cosign/cli/bundle/bundle.go @@ -29,14 +29,14 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/rekor" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/cosign/pivkey" + "github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key" + sigs "github.com/sigstore/cosign/v3/pkg/signature" ) type CreateCmd struct { @@ -76,6 +76,10 @@ func (c *CreateCmd) Exec(ctx context.Context) (err error) { return err } + if c.IgnoreTlog && b.Bundle != nil && len(b.Bundle.SignedEntryTimestamp) > 0 { + return fmt.Errorf("cannot ignore transparency log when the provided bundle contains a Signed Entry Timestamp") + } + if b.Cert != "" { certPEM, err := base64.StdEncoding.DecodeString(b.Cert) if err != nil { @@ -207,7 +211,7 @@ func (c *CreateCmd) Exec(ctx context.Context) (err error) { } if c.Out != "" { - err = os.WriteFile(c.Out, bundleBytes, 0600) + err = os.WriteFile(c.Out, bundleBytes, 0600) // #nosec G703 -- user-supplied output path is intentional if err != nil { return err } diff --git a/cmd/cosign/cli/bundle/bundle_test.go b/cmd/cosign/cli/bundle/bundle_test.go index 279d59bf86b..3b87f38a641 100644 --- a/cmd/cosign/cli/bundle/bundle_test.go +++ b/cmd/cosign/cli/bundle/bundle_test.go @@ -30,8 +30,9 @@ import ( "path/filepath" "testing" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/cosign/v2/test" + "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" sgBundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore/pkg/cryptoutils" ) @@ -148,3 +149,96 @@ func checkErr(t *testing.T, err error) { t.Fatal(err) } } + +func TestCreateCmd_FailOnIgnoreTlogWithSET(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) + + 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) + + // Test using an old bundle with a SET + signedPayloadWithSET := cosign.LocalSignedPayload{} + signedPayloadWithSET.Base64Signature = base64.StdEncoding.EncodeToString(sigBytes) + + certBytes, err := cryptoutils.MarshalCertificateToPEM(leafCert) + checkErr(t, err) + + signedPayloadWithSET.Cert = base64.StdEncoding.EncodeToString(certBytes) + signedPayloadWithSET.Bundle = &bundle.RekorBundle{ + SignedEntryTimestamp: []byte("set"), + } + + bundleContentsWithSET, err := json.Marshal(signedPayloadWithSET) + checkErr(t, err) + bundlePathWithSET := filepath.Join(td, "old-bundle-with-set.json") + err = os.WriteFile(bundlePathWithSET, bundleContentsWithSET, 0600) + checkErr(t, err) + + // Test using an old bundle without a SET + signedPayloadWithoutSET := cosign.LocalSignedPayload{} + signedPayloadWithoutSET.Base64Signature = signedPayloadWithSET.Base64Signature + signedPayloadWithoutSET.Cert = signedPayloadWithSET.Cert + + bundleContentsWithoutSET, err := json.Marshal(signedPayloadWithoutSET) + checkErr(t, err) + bundlePathWithoutSET := filepath.Join(td, "old-bundle-without-set.json") + err = os.WriteFile(bundlePathWithoutSET, bundleContentsWithoutSET, 0600) + checkErr(t, err) + + tests := []struct { + name string + bundlePath string + ignoreTlog bool + expectError bool + errStr string + }{ + { + name: "Fail when bundle has SET and IgnoreTlog is true", + bundlePath: bundlePathWithSET, + ignoreTlog: true, + expectError: true, + errStr: "cannot ignore transparency log when the provided bundle contains a Signed Entry Timestamp", + }, + { + name: "Pass when bundle has no SET and IgnoreTlog is true", + bundlePath: bundlePathWithoutSET, + ignoreTlog: true, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + outPath := filepath.Join(td, "out-"+filepath.Base(tt.bundlePath)) + bundleCreate := CreateCmd{ + Artifact: artifactPath, + BundlePath: tt.bundlePath, + IgnoreTlog: tt.ignoreTlog, + Out: outPath, + } + + err := bundleCreate.Exec(ctx) + if tt.expectError { + if err == nil { + t.Fatal("expected error, got nil") + } + if tt.errStr != "" && err.Error() != tt.errStr { + t.Fatalf("expected error %q, got %q", tt.errStr, err.Error()) + } + } else { + checkErr(t, err) + } + }) + } +} diff --git a/cmd/cosign/cli/bundle/inspect.go b/cmd/cosign/cli/bundle/inspect.go new file mode 100644 index 00000000000..9656f0c96cf --- /dev/null +++ b/cmd/cosign/cli/bundle/inspect.go @@ -0,0 +1,642 @@ +// +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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 ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "encoding/asn1" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/digitorus/timestamp" + "github.com/google/certificate-transparency-go/x509util" + "github.com/sigstore/cosign/v3/pkg/cosign" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + rekorv1 "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" + "github.com/sigstore/rekor/pkg/util" + "github.com/sigstore/sigstore-go/pkg/root" + "google.golang.org/protobuf/encoding/protojson" +) + +// OIDs that are explicitly handled earlier in certificate population +// and should be skipped in the "Other Extensions" loop +var handledOIDs = map[string]bool{ + "2.5.29.15": true, // Key Usage + "2.5.29.37": true, // Extended Key Usage + "2.5.29.14": true, // Subject Key Identifier + "2.5.29.35": true, // Authority Key Identifier + "2.5.29.17": true, // Subject Alternative Name +} + +var keyUsageNames = []struct { + usage x509.KeyUsage + name string +}{ + {x509.KeyUsageDigitalSignature, "Digital Signature"}, + {x509.KeyUsageContentCommitment, "Content Commitment"}, + {x509.KeyUsageKeyEncipherment, "Key Encipherment"}, + {x509.KeyUsageDataEncipherment, "Data Encipherment"}, + {x509.KeyUsageKeyAgreement, "Key Agreement"}, + {x509.KeyUsageCertSign, "Cert Sign"}, + {x509.KeyUsageCRLSign, "CRL Sign"}, + {x509.KeyUsageEncipherOnly, "Encipher Only"}, + {x509.KeyUsageDecipherOnly, "Decipher Only"}, +} + +var extKeyUsageNames = map[x509.ExtKeyUsage]string{ + x509.ExtKeyUsageAny: "Any", + x509.ExtKeyUsageServerAuth: "Server Auth", + x509.ExtKeyUsageClientAuth: "Client Auth", + x509.ExtKeyUsageCodeSigning: "Code Signing", + x509.ExtKeyUsageEmailProtection: "Email Protection", + x509.ExtKeyUsageIPSECEndSystem: "IPSEC End System", + x509.ExtKeyUsageIPSECTunnel: "IPSEC Tunnel", + x509.ExtKeyUsageIPSECUser: "IPSEC User", + x509.ExtKeyUsageTimeStamping: "Time Stamping", + x509.ExtKeyUsageOCSPSigning: "OCSP Signing", +} + +// Fulcio cert-extensions, documented here: https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md +var certExtensionMap = map[string]string{ + "1.3.6.1.4.1.57264.1.1": "OIDC Issuer", + "1.3.6.1.4.1.57264.1.2": "GitHub Workflow Trigger", + "1.3.6.1.4.1.57264.1.3": "GitHub Workflow SHA", + "1.3.6.1.4.1.57264.1.4": "GitHub Workflow Name", + "1.3.6.1.4.1.57264.1.5": "GitHub Workflow Repository", + "1.3.6.1.4.1.57264.1.6": "GitHub Workflow Ref", + "1.3.6.1.4.1.57264.1.7": "OtherName SAN", + "1.3.6.1.4.1.57264.1.8": "Issuer (V2)", + "1.3.6.1.4.1.57264.1.9": "Build Signer URI", + "1.3.6.1.4.1.57264.1.10": "Build Signer Digest", + "1.3.6.1.4.1.57264.1.11": "Runner Environment", + "1.3.6.1.4.1.57264.1.12": "Source Repository URI", + "1.3.6.1.4.1.57264.1.13": "Source Repository Digest", + "1.3.6.1.4.1.57264.1.14": "Source Repository Ref", + "1.3.6.1.4.1.57264.1.15": "Source Repository Identifier", + "1.3.6.1.4.1.57264.1.16": "Source Repository Owner URI", + "1.3.6.1.4.1.57264.1.17": "Source Repository Owner Identifier", + "1.3.6.1.4.1.57264.1.18": "Build Config URI", + "1.3.6.1.4.1.57264.1.19": "Build Config Digest", + "1.3.6.1.4.1.57264.1.20": "Build Trigger", + "1.3.6.1.4.1.57264.1.21": "Run Invocation URI", + "1.3.6.1.4.1.57264.1.22": "Source Repository Visibility At Signing", + "1.3.6.1.4.1.57264.1.23": "Deployment Environment", + "1.3.6.1.4.1.57264.1.24": "Token Subject", +} + +const sctExtensionOid = "1.3.6.1.4.1.11129.2.4.2" + +var recognizedMediaTypes = map[string]int{ + "application/vnd.dev.sigstore.bundle+json;version=0.1": 1, + "application/vnd.dev.sigstore.bundle+json;version=0.2": 2, + "application/vnd.dev.sigstore.bundle+json;version=0.3": 3, + "application/vnd.dev.sigstore.bundle.v0.3+json": 3, +} + +type InspectCmd struct { + BundlePath string + Out io.Writer +} + +func (c *InspectCmd) Exec() error { + fmt.Fprintln(os.Stderr, "WARNING: This command only inspects the contents of the bundle.") + fmt.Fprintln(os.Stderr, "It does not perform cryptographic verification or verify log inclusion.") + fmt.Fprintln(os.Stderr, "") + + tr, err := cosign.TrustedRoot() + if err != nil { + fmt.Fprintf(os.Stderr, "Local TUF environment not initialized, some details will be omitted: %v\n", err) + } + + f, err := os.Open(c.BundlePath) + if err != nil { + return fmt.Errorf("opening bundle file: %w", err) + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + return fmt.Errorf("reading bundle: %w", err) + } + + var b protobundle.Bundle + if err := protojson.Unmarshal(data, &b); err != nil { + return fmt.Errorf("unmarshaling bundle: %w", err) + } + + rootNode := &node{} + + rootNode.addChild("Bundle Media Type", b.MediaType) + version, ok := recognizedMediaTypes[b.MediaType] + if b.MediaType == "" { + rootNode.addChild("[!] WARNING", "Missing Bundle Media Type") + } else if !ok { + rootNode.addChild("[!] WARNING", "Unrecognized Media Type") + } + + if b.VerificationMaterial != nil { + populateVerificationMaterialSummary(rootNode, b.VerificationMaterial, tr, version) + } else { + rootNode.addChild("[!] WARNING", "Missing Verification Material") + } + + if b.Content != nil { + populateContentSummary(rootNode, &b) + } else { + rootNode.addChild("[!] WARNING", "Missing Content") + } + + out := c.Out + if out == nil { + out = os.Stdout + } + rootNode.print(out, 0) + + return nil +} + +type node struct { + Label string + Value string + Children []node +} + +func (n *node) addChild(label, value string) *node { + n.Children = append(n.Children, node{Label: label, Value: value}) + return &n.Children[len(n.Children)-1] +} + +func (n *node) print(w io.Writer, depth int) { + if n.Label != "" { + indent := strings.Repeat(" ", depth) + if n.Value != "" { + fmt.Fprintf(w, "%s- %s: %s\n", indent, n.Label, n.Value) + } else { + fmt.Fprintf(w, "%s- %s:\n", indent, n.Label) + } + depth++ + } + for _, child := range n.Children { + child.print(w, depth) + } +} + +func populateVerificationMaterialSummary(n *node, vm *protobundle.VerificationMaterial, tr root.TrustedMaterial, version int) { + vmNode := n.addChild("Verification Material", "") + + switch content := vm.Content.(type) { + case *protobundle.VerificationMaterial_Certificate: + certNode := vmNode.addChild("Content", "X.509 Certificate") + if len(content.Certificate.RawBytes) > 0 { + populateCertificateSummary(certNode, content.Certificate.RawBytes, tr) + } else { + certNode.addChild("[!] WARNING", "Certificate raw_bytes is empty") + } + + if version > 0 && version < 3 { + certNode.addChild("[!] WARNING", "v0.1/v0.2 bundle should use certificate chain, not single certificate") + } + + case *protobundle.VerificationMaterial_X509CertificateChain: + chainNode := vmNode.addChild("Content", "X.509 Certificate Chain") + if len(content.X509CertificateChain.Certificates) > 0 { + chainNode.addChild("Certificates", fmt.Sprintf("%d", len(content.X509CertificateChain.Certificates))) + for i, cert := range content.X509CertificateChain.Certificates { + certNode := chainNode.addChild(fmt.Sprintf("Certificate [%d]", i), "") + populateCertificateSummary(certNode, cert.RawBytes, tr) + } + } else { + chainNode.addChild("[!] WARNING", "Certificate chain is empty") + } + + if version >= 3 { + chainNode.addChild("[!] WARNING", "v0.3 bundle should use single certificate, not chain") + } + + case *protobundle.VerificationMaterial_PublicKey: + pkNode := vmNode.addChild("Content", "Public Key Identifier") + hint := content.PublicKey.GetHint() + if hint != "" { + if decoded, err := base64.StdEncoding.DecodeString(hint); err == nil { + pkNode.addChild("Hint", hex.EncodeToString(decoded)) + } else { + pkNode.addChild("Hint", hint) + } + } else { + pkNode.addChild("[!] WARNING", "Public key hint is empty") + } + + case nil: + vmNode.addChild("[!] WARNING", "Missing Verification Material Content (must be certificate, chain, or public key)") + } + + if len(vm.TlogEntries) > 0 { + tlogNode := vmNode.addChild("Transparency Log Entries", fmt.Sprintf("%d", len(vm.TlogEntries))) + for i, entry := range vm.TlogEntries { + entryNode := tlogNode.addChild(fmt.Sprintf("Entry [%d]", i), "") + populateTlogEntrySummary(entryNode, entry, tr) + + if version == 1 && entry.GetInclusionPromise() == nil { + entryNode.addChild("[!] WARNING", "v0.1 bundle tlog entry MUST contain an inclusion promise") + } + + if version >= 2 && entry.GetInclusionProof() == nil { + entryNode.addChild("[!] WARNING", fmt.Sprintf("v0.%d bundle tlog entry MUST contain an inclusion proof", version)) + } + } + } + + tsData := vm.TimestampVerificationData + if tsData != nil { + tsNode := vmNode.addChild("Timestamp Verification Data", "") + populateTimestampSummary(tsNode, tsData) + } +} + +func populateCertificateSummary(n *node, rawBytes []byte, tr root.TrustedMaterial) { + cert, err := x509.ParseCertificate(rawBytes) + if err != nil { + n.addChild("Error", fmt.Sprintf("parsing certificate: %v", err)) + return + } + + n.addChild("Signature Algorithm", cert.SignatureAlgorithm.String()) + + if cert.Subject.String() != "" { + n.addChild("Subject", cert.Subject.String()) + } + + hasIssuer := cert.Issuer.CommonName != "" || len(cert.Issuer.Organization) > 0 || len(cert.Issuer.Country) > 0 + if hasIssuer { + issuerNode := n.addChild("Issuer", "") + if cert.Issuer.CommonName != "" { + issuerNode.addChild("Common Name", cert.Issuer.CommonName) + } + if len(cert.Issuer.Organization) > 0 { + issuerNode.addChild("Organization", cert.Issuer.Organization[0]) + } + if len(cert.Issuer.Country) > 0 { + issuerNode.addChild("Country", cert.Issuer.Country[0]) + } + } + + validityNode := n.addChild("Validity", "") + validityNode.addChild("Not Before", cert.NotBefore.Format(time.RFC3339)) + validityNode.addChild("Not After", cert.NotAfter.Format(time.RFC3339)) + + spkiNode := n.addChild("Subject Public Key Info", "") + switch pub := cert.PublicKey.(type) { + case *ecdsa.PublicKey: + spkiNode.addChild("Algorithm", "ECDSA") + spkiNode.addChild("Curve", pub.Curve.Params().Name) + if ecdhPub, err := pub.ECDH(); err == nil { + pubBytes := ecdhPub.Bytes() + pubHex := hex.EncodeToString(pubBytes) + if len(pubHex) > 20 { + spkiNode.addChild("Public Key", fmt.Sprintf("%s...%s (%d bytes)", pubHex[:8], pubHex[len(pubHex)-8:], len(pubBytes))) + } else { + spkiNode.addChild("Public Key", pubHex) + } + } else { + spkiNode.addChild("Public Key", fmt.Sprintf("Error extracting key: %v", err)) + } + + case ed25519.PublicKey: + spkiNode.addChild("Algorithm", "Ed25519") + pubHex := hex.EncodeToString(pub) + if len(pubHex) > 20 { + spkiNode.addChild("Public Key", fmt.Sprintf("%s...%s (%d bytes)", pubHex[:8], pubHex[len(pubHex)-8:], len(pub))) + } else { + spkiNode.addChild("Public Key", pubHex) + } + + case *rsa.PublicKey: + spkiNode.addChild("Algorithm", "RSA") + spkiNode.addChild("Key Size", fmt.Sprintf("%d bits", pub.N.BitLen())) + + default: + spkiNode.addChild("Algorithm", "Unknown") + } + + extsNode := n.addChild("Extensions", "") + + if cert.Subject.CommonName != "" || len(cert.EmailAddresses) > 0 || len(cert.URIs) > 0 { + identityNode := extsNode.addChild("Identity (Subject Alternative Name)", "") + if cert.Subject.CommonName != "" { + identityNode.addChild("Common Name", cert.Subject.CommonName) + } + for _, email := range cert.EmailAddresses { + identityNode.addChild("Email", email) + } + for _, uri := range cert.URIs { + identityNode.addChild("URI", uri.String()) + } + } + + var usages []string + for _, ku := range keyUsageNames { + if cert.KeyUsage&ku.usage != 0 { + usages = append(usages, ku.name) + } + } + if len(usages) > 0 { + extsNode.addChild("Key Usage", strings.Join(usages, ", ")) + } + + var extUsages []string + for _, u := range cert.ExtKeyUsage { + if name, ok := extKeyUsageNames[u]; ok { + extUsages = append(extUsages, name) + } + } + if len(extUsages) > 0 { + extsNode.addChild("Extended Key Usage", strings.Join(extUsages, ", ")) + } + + var unknownExtUsages []string + for _, u := range cert.UnknownExtKeyUsage { + unknownExtUsages = append(unknownExtUsages, u.String()) + } + if len(unknownExtUsages) > 0 { + extsNode.addChild("Unknown Extended Key Usage", strings.Join(unknownExtUsages, ", ")) + } + + if len(cert.SubjectKeyId) > 0 { + extsNode.addChild("Subject Key ID", hex.EncodeToString(cert.SubjectKeyId)) + } + if len(cert.AuthorityKeyId) > 0 { + keyID := hex.EncodeToString(cert.AuthorityKeyId) + url := findFulcioURL(tr, keyID) + if url != "" { + extsNode.addChild("Authority URL", url) + } else { + extsNode.addChild("Authority Key ID", keyID) + } + } + + // Other Extensions + for _, ext := range cert.Extensions { + oidStr := ext.Id.String() + if handledOIDs[oidStr] { + continue + } + + if oidStr == sctExtensionOid { + embeddedSCTs, err := x509util.ParseSCTsFromCertificate(cert.Raw) + if err == nil && len(embeddedSCTs) > 0 { + sctsNode := extsNode.addChild("Signed Certificate Timestamps", fmt.Sprintf("%d", len(embeddedSCTs))) + for i, sct := range embeddedSCTs { + sctNode := sctsNode.addChild(fmt.Sprintf("Signed Certificate Timestamp [%d]", i), "") + sctNode.addChild("Version", fmt.Sprintf("v%d", sct.SCTVersion+1)) + + logID := hex.EncodeToString(sct.LogID.KeyID[:]) + url := findCTLogURL(tr, logID) + if url != "" { + sctNode.addChild("Log URL", url) + } else { + sctNode.addChild("Log ID", logID) + } + + sctNode.addChild("Timestamp", time.Unix(0, int64(sct.Timestamp)*1000000).Format(time.RFC3339)) + + extStr := "none" + if len(sct.Extensions) > 0 { + extStr = fmt.Sprintf("%d bytes", len(sct.Extensions)) + } + sctNode.addChild("Extensions", extStr) + + sctNode.addChild("Signature", fmt.Sprintf("Present (%d bytes)", len(sct.Signature.Signature))) + } + continue + } + } + + if readableName, ok := certExtensionMap[oidStr]; ok { + valStr := string(ext.Value) + var decodedStr string + _, err := asn1.Unmarshal(ext.Value, &decodedStr) + if err == nil { + valStr = decodedStr + } + extsNode.addChild(readableName, valStr) + } else { + extsNode.addChild(fmt.Sprintf("OID: %s", oidStr), fmt.Sprintf("(Critical: %t) [%d bytes]", ext.Critical, len(ext.Value))) + } + } + + n.addChild("Signature", fmt.Sprintf("Present (%d bytes)", len(cert.Signature))) +} + +func populateTlogEntrySummary(n *node, entry *rekorv1.TransparencyLogEntry, tr root.TrustedMaterial) { + n.addChild("Log Index", fmt.Sprintf("%d", entry.LogIndex)) + + if entry.LogId != nil { + logID := hex.EncodeToString(entry.LogId.KeyId) + url := findRekorURL(tr, logID) + if url != "" { + n.addChild("Log URL", url) + } else { + n.addChild("Log ID", logID) + } + } + + if entry.KindVersion != nil { + n.addChild("Kind", entry.KindVersion.Kind) + n.addChild("Version", entry.KindVersion.Version) + } + + if entry.IntegratedTime == 0 { + n.addChild("Integrated Time", "0 (Not set)") + } else { + t := time.Unix(entry.IntegratedTime, 0).UTC() + n.addChild("Integrated Time", t.Format(time.RFC3339)) + } + + if entry.GetInclusionPromise() != nil { + promise := entry.GetInclusionPromise() + promiseNode := n.addChild("Inclusion Promise", "") + promiseNode.addChild("Signed Entry Timestamp", fmt.Sprintf("Present (%d bytes)", len(promise.SignedEntryTimestamp))) + } + + if entry.GetInclusionProof() != nil { + proof := entry.GetInclusionProof() + proofNode := n.addChild("Inclusion Proof", "") + proofNode.addChild("Log Index", fmt.Sprintf("%d", proof.LogIndex)) + proofNode.addChild("Tree Size", fmt.Sprintf("%d", proof.TreeSize)) + proofNode.addChild("Hashes", fmt.Sprintf("%d", len(proof.Hashes))) + + if proof.Checkpoint != nil { + checkpointNode := proofNode.addChild("Checkpoint", "") + var sc util.SignedCheckpoint + if err := sc.UnmarshalText([]byte(proof.Checkpoint.Envelope)); err == nil { + checkpointNode.addChild("Origin", sc.Origin) + checkpointNode.addChild("Tree Size", fmt.Sprintf("%d", sc.Size)) + + if len(sc.OtherContent) > 0 { + otherNode := checkpointNode.addChild("Other Content", fmt.Sprintf("%d lines", len(sc.OtherContent))) + for i, line := range sc.OtherContent { + otherNode.addChild(fmt.Sprintf("[%d]", i), line) + } + } + + if len(sc.Signatures) > 0 { + witnessesNode := checkpointNode.addChild("Witnesses", fmt.Sprintf("%d", len(sc.Signatures))) + for _, sig := range sc.Signatures { + sigBytes, err := base64.StdEncoding.DecodeString(sig.Base64) + sigNode := witnessesNode.addChild(sig.Name, fmt.Sprintf("Signature Present (%d bytes)", len(sigBytes))) + if err != nil { + sigNode.addChild("[!] WARNING", "Malformed Base64") + } + } + } + } else { + checkpointNode.addChild("Envelope", fmt.Sprintf("Present (%d bytes)", len(proof.Checkpoint.Envelope))) + } + } + } + + if len(entry.CanonicalizedBody) > 0 { + n.addChild("Canonicalized Body", fmt.Sprintf("Present (%d bytes)", len(entry.CanonicalizedBody))) + } +} + +func populateTimestampSummary(n *node, tsData *protobundle.TimestampVerificationData) { + n.addChild("RFC3161 Timestamps", fmt.Sprintf("%d", len(tsData.GetRfc3161Timestamps()))) + for i, ts := range tsData.GetRfc3161Timestamps() { + singleTsNode := n.addChild(fmt.Sprintf("Timestamp [%d]", i), "") + + if parsedTs, err := timestamp.ParseResponse(ts.SignedTimestamp); err == nil { + singleTsNode.addChild("Time", parsedTs.Time.Format(time.RFC3339)) + singleTsNode.addChild("Hash Algorithm", parsedTs.HashAlgorithm.String()) + singleTsNode.addChild("Message Imprint", fmt.Sprintf("Present (%d bytes)", len(parsedTs.HashedMessage))) + singleTsNode.addChild("Serial Number", fmt.Sprintf("%s", parsedTs.SerialNumber)) + singleTsNode.addChild("Policy", parsedTs.Policy.String()) + } + } +} + +func populateContentSummary(n *node, b *protobundle.Bundle) { + contentNode := n.addChild("Content", "") + switch content := b.Content.(type) { + case *protobundle.Bundle_DsseEnvelope: + contentNode.addChild("Type", "DSSE Envelope") + contentNode.addChild("Payload Type", content.DsseEnvelope.PayloadType) + contentNode.addChild("Payload", fmt.Sprintf("Present (%d bytes)", len(content.DsseEnvelope.Payload))) + + if content.DsseEnvelope.PayloadType == "application/vnd.in-toto+json" { + var statement map[string]interface{} + if err := json.Unmarshal(content.DsseEnvelope.Payload, &statement); err == nil { + payloadNode := contentNode.addChild("Payload Content (in-toto)", "") + if predicateType, ok := statement["predicateType"].(string); ok { + payloadNode.addChild("Predicate Type", predicateType) + } + if subjects, ok := statement["subject"].([]interface{}); ok { + subjectsNode := payloadNode.addChild("Subjects", fmt.Sprintf("%d", len(subjects))) + for _, s := range subjects { + if subMap, ok := s.(map[string]interface{}); ok { + if name, ok := subMap["name"].(string); ok { + subjectsNode.addChild("Name", name) + } + } + } + } + } + } + + sigsNode := contentNode.addChild("Signatures", fmt.Sprintf("%d", len(content.DsseEnvelope.Signatures))) + if len(content.DsseEnvelope.Signatures) != 1 { + contentNode.addChild("[!] WARNING", "DSSE envelope MUST contain only one signature") + } + + for i, sig := range content.DsseEnvelope.Signatures { + sigNode := sigsNode.addChild(fmt.Sprintf("Signature [%d]", i), "") + sigNode.addChild("Signature", fmt.Sprintf("Present (%d bytes)", len(sig.Sig))) + if sig.Keyid != "" { + if decoded, err := base64.StdEncoding.DecodeString(sig.Keyid); err == nil { + sigNode.addChild("Key Hint", hex.EncodeToString(decoded)) + } else { + sigNode.addChild("Key Hint", sig.Keyid) + } + + if b.VerificationMaterial != nil && b.VerificationMaterial.GetPublicKey() != nil { + vmHint := b.VerificationMaterial.GetPublicKey().GetHint() + if vmHint != sig.Keyid { + sigNode.addChild("[!] WARNING", "Key hint mismatch with Verification Material") + } + } + } + } + + case *protobundle.Bundle_MessageSignature: + contentNode.addChild("Type", "Message Signature") + if content.MessageSignature.MessageDigest != nil { + digestNode := contentNode.addChild("Message Digest", "") + digestNode.addChild("Algorithm", content.MessageSignature.MessageDigest.Algorithm.String()) + digestNode.addChild("Digest", fmt.Sprintf("Present (%d bytes)", len(content.MessageSignature.MessageDigest.Digest))) + } + contentNode.addChild("Signature", fmt.Sprintf("Present (%d bytes)", len(content.MessageSignature.Signature))) + } +} + +func findFulcioURL(tr root.TrustedMaterial, authorityKeyID string) string { + if tr == nil { + return "" + } + for _, ca := range tr.FulcioCertificateAuthorities() { + if fca, ok := ca.(*root.FulcioCertificateAuthority); ok { + if hex.EncodeToString(fca.Root.SubjectKeyId) == authorityKeyID { + return fca.URI + } + for _, incert := range fca.Intermediates { + if hex.EncodeToString(incert.SubjectKeyId) == authorityKeyID { + return fca.URI + } + } + } + } + return "" +} + +func findCTLogURL(tr root.TrustedMaterial, logID string) string { + if tr == nil { + return "" + } + ctlogs := tr.CTLogs() + if log, ok := ctlogs[logID]; ok { + return log.BaseURL + } + return "" +} + +func findRekorURL(tr root.TrustedMaterial, logID string) string { + if tr == nil { + return "" + } + tlogs := tr.RekorLogs() + if log, ok := tlogs[logID]; ok { + return log.BaseURL + } + return "" +} diff --git a/cmd/cosign/cli/bundle/inspect_test.go b/cmd/cosign/cli/bundle/inspect_test.go new file mode 100644 index 00000000000..acb98be3437 --- /dev/null +++ b/cmd/cosign/cli/bundle/inspect_test.go @@ -0,0 +1,941 @@ +// +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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 ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/hex" + "math/big" + "net/url" + "time" + + "github.com/digitorus/timestamp" + tsaMock "github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/mock" + "github.com/sigstore/cosign/v3/internal/test" + 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" + rekorv1 "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" + "github.com/sigstore/sigstore-go/pkg/root" + "google.golang.org/protobuf/encoding/protojson" +) + +func TestInspectCmd(t *testing.T) { + rootCert, rootKey, err := test.GenerateRootCa() + if err != nil { + t.Fatal(err) + } + leafCert, _, err := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + bundle *protobundle.Bundle + expectedOutputs []string + }{ + { + name: "v0.3 bundle with single cert", + bundle: &protobundle.Bundle{ + MediaType: "application/vnd.dev.sigstore.bundle.v0.3+json", + VerificationMaterial: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_Certificate{ + Certificate: &protocommon.X509Certificate{RawBytes: leafCert.Raw}, + }, + }, + Content: &protobundle.Bundle_MessageSignature{ + MessageSignature: &protocommon.MessageSignature{ + Signature: []byte("sig"), + }, + }, + }, + expectedOutputs: []string{"Bundle Media Type", "Verification Material", "X.509 Certificate", "Message Signature"}, + }, + + { + name: "Bundle with missing media type", + bundle: &protobundle.Bundle{ + MediaType: "", + }, + expectedOutputs: []string{"Missing Bundle Media Type"}, + }, + { + name: "Bundle with unrecognized media type", + bundle: &protobundle.Bundle{ + MediaType: "application/garbage", + }, + expectedOutputs: []string{"Unrecognized Media Type"}, + }, + { + name: "Bundle with missing verification material", + bundle: &protobundle.Bundle{ + MediaType: "application/vnd.dev.sigstore.bundle.v0.3+json", + VerificationMaterial: nil, + }, + expectedOutputs: []string{"Missing Verification Material"}, + }, + { + name: "Bundle with missing content", + bundle: &protobundle.Bundle{ + MediaType: "application/vnd.dev.sigstore.bundle.v0.3+json", + VerificationMaterial: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_Certificate{ + Certificate: &protocommon.X509Certificate{RawBytes: leafCert.Raw}, + }, + }, + Content: nil, + }, + expectedOutputs: []string{"Missing Content"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + jsonData, err := protojson.Marshal(tc.bundle) + if err != nil { + t.Fatal(err) + } + + tmpDir := t.TempDir() + bundleFile := filepath.Join(tmpDir, "bundle.json") + err = os.WriteFile(bundleFile, jsonData, 0600) + if err != nil { + t.Fatal(err) + } + + var buf bytes.Buffer + cmd := &InspectCmd{ + BundlePath: bundleFile, + Out: &buf, + } + + err = cmd.Exec() + if err != nil { + t.Fatal(err) + } + + output := buf.String() + + for _, expected := range tc.expectedOutputs { + if !strings.Contains(output, expected) { + t.Errorf("expected output to contain %q, got:\n%s", expected, output) + } + } + }) + } +} + +func TestPopulateVerificationMaterialSummary(t *testing.T) { + rootCert, rootKey, err := test.GenerateRootCa() + if err != nil { + t.Fatal(err) + } + leafCert, _, err := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + version int + vm *protobundle.VerificationMaterial + expectedOutputs []string + }{ + { + name: "v0.3 bundle with public key", + version: 3, + vm: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_PublicKey{ + PublicKey: &protocommon.PublicKeyIdentifier{Hint: "amtiZEdWemRDaz0="}, + }, + }, + expectedOutputs: []string{"Public Key Identifier", "Hint"}, + }, + { + name: "v0.1 bundle with single cert (expects warning)", + version: 1, + vm: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_Certificate{ + Certificate: &protocommon.X509Certificate{RawBytes: leafCert.Raw}, + }, + }, + expectedOutputs: []string{"v0.1/v0.2 bundle should use certificate chain, not single certificate"}, + }, + { + name: "v0.3 bundle with certificate chain (expects warning)", + version: 3, + vm: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_X509CertificateChain{ + X509CertificateChain: &protocommon.X509CertificateChain{ + Certificates: []*protocommon.X509Certificate{{RawBytes: leafCert.Raw}}, + }, + }, + }, + expectedOutputs: []string{"v0.3 bundle should use single certificate, not chain"}, + }, + { + name: "v0.1 bundle with tlog entry missing promise", + version: 1, + vm: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_Certificate{ + Certificate: &protocommon.X509Certificate{RawBytes: leafCert.Raw}, + }, + TlogEntries: []*rekorv1.TransparencyLogEntry{ + {LogIndex: 123}, + }, + }, + expectedOutputs: []string{"v0.1 bundle tlog entry MUST contain an inclusion promise"}, + }, + { + name: "v0.3 bundle with tlog entry missing proof", + version: 3, + vm: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_Certificate{ + Certificate: &protocommon.X509Certificate{RawBytes: leafCert.Raw}, + }, + TlogEntries: []*rekorv1.TransparencyLogEntry{ + {LogIndex: 456}, + }, + }, + expectedOutputs: []string{"v0.3 bundle tlog entry MUST contain an inclusion proof"}, + }, + { + name: "v0.3 bundle with empty certificate raw_bytes", + version: 3, + vm: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_Certificate{ + Certificate: &protocommon.X509Certificate{RawBytes: []byte{}}, + }, + }, + expectedOutputs: []string{"Certificate raw_bytes is empty"}, + }, + { + name: "v0.3 bundle with empty certificate chain", + version: 3, + vm: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_X509CertificateChain{ + X509CertificateChain: &protocommon.X509CertificateChain{ + Certificates: []*protocommon.X509Certificate{}, + }, + }, + }, + expectedOutputs: []string{"Certificate chain is empty"}, + }, + { + name: "v0.3 bundle with missing verification material content", + version: 3, + vm: &protobundle.VerificationMaterial{ + Content: nil, + }, + expectedOutputs: []string{"Missing Verification Material Content (must be certificate, chain, or public key)"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + n := &node{} + populateVerificationMaterialSummary(n, tc.vm, nil, tc.version) + + for _, expected := range tc.expectedOutputs { + if !nodeTreeContains(n, expected) { + t.Errorf("expected node tree to contain %q", expected) + } + } + }) + } +} + +func nodeTreeContains(n *node, str string) bool { + if strings.Contains(n.Label, str) || strings.Contains(n.Value, str) { + return true + } + for i := range n.Children { + if nodeTreeContains(&n.Children[i], str) { + return true + } + } + return false +} + +func TestPopulateCertificateSummary(t *testing.T) { + genCert := func(t *testing.T, pub any, priv any) []byte { + parsedURL, _ := url.Parse("https://example.com") + oidcIssuerOID := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1} + issuerBytes, _ := asn1.Marshal("https://issuer.example.com") + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "test", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + EmailAddresses: []string{"test@example.com"}, + URIs: []*url.URL{parsedURL}, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + SubjectKeyId: []byte{1, 2, 3, 4}, + AuthorityKeyId: []byte{5, 6, 7, 8}, + ExtraExtensions: []pkix.Extension{ + { + Id: oidcIssuerOID, + Critical: false, + Value: issuerBytes, + }, + }, + } + certBytes, err := x509.CreateCertificate(rand.Reader, template, template, pub, priv) + if err != nil { + t.Fatal(err) + } + return certBytes + } + + ecdsaPriv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + ecdsaCert := genCert(t, &ecdsaPriv.PublicKey, ecdsaPriv) + + edPub, edPriv, _ := ed25519.GenerateKey(rand.Reader) + edCert := genCert(t, edPub, edPriv) + + rsaPriv, _ := rsa.GenerateKey(rand.Reader, 2048) + rsaCert := genCert(t, &rsaPriv.PublicKey, rsaPriv) + + tests := []struct { + name string + certBytes []byte + expectedLabel string + expectedValue string + }{ + { + name: "ECDSA", + certBytes: ecdsaCert, + expectedLabel: "Algorithm", + expectedValue: "ECDSA", + }, + { + name: "Ed25519", + certBytes: edCert, + expectedLabel: "Algorithm", + expectedValue: "Ed25519", + }, + { + name: "RSA", + certBytes: rsaCert, + expectedLabel: "Algorithm", + expectedValue: "RSA", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + n := &node{} + populateCertificateSummary(n, tc.certBytes, nil) + + found := false + for _, child := range n.Children { + if child.Label == "Subject Public Key Info" { + for _, grandChild := range child.Children { + if grandChild.Label == tc.expectedLabel && grandChild.Value == tc.expectedValue { + found = true + break + } + } + } + } + if !found { + t.Errorf("expected child with label %s and value %s under 'Subject Public Key Info'", tc.expectedLabel, tc.expectedValue) + } + }) + } +} + +func TestPopulateCertificateSummary_Authority(t *testing.T) { + trBytes, err := os.ReadFile("../../../../pkg/cosign/testdata/trusted_root_pgi.json") + if err != nil { + t.Fatal(err) + } + tr, err := root.NewTrustedRootFromJSON(trBytes) + if err != nil { + t.Fatal(err) + } + + genCert := func(t *testing.T, authKeyID []byte) []byte { + priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "test", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + AuthorityKeyId: authKeyID, + } + certBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv) + if err != nil { + t.Fatal(err) + } + return certBytes + } + + fulcioCAs := tr.FulcioCertificateAuthorities() + if len(fulcioCAs) == 0 { + t.Fatal("expected Fulcio CAs in trusted root") + } + fca, ok := fulcioCAs[0].(*root.FulcioCertificateAuthority) + if !ok { + t.Fatal("expected *root.FulcioCertificateAuthority") + } + + foundCert := genCert(t, fca.Root.SubjectKeyId) + notFoundCert := genCert(t, []byte{5, 6, 7, 8}) + + t.Run("Found in initialized trusted root", func(t *testing.T) { + n := &node{} + populateCertificateSummary(n, foundCert, tr) + + var extensionsNode *node + for i := range n.Children { + if n.Children[i].Label == "Extensions" { + extensionsNode = &n.Children[i] + break + } + } + if extensionsNode == nil { + t.Fatal("expected 'Extensions' node") + } + + found := false + for _, child := range extensionsNode.Children { + if child.Label == "Authority URL" && child.Value == "https://fulcio.sigstore.dev" { + found = true + break + } + } + if !found { + t.Error("expected 'Authority URL' with value 'https://fulcio.sigstore.dev'") + } + }) + + t.Run("Not found in initialized trusted root", func(t *testing.T) { + n := &node{} + populateCertificateSummary(n, notFoundCert, tr) + + var extensionsNode *node + for i := range n.Children { + if n.Children[i].Label == "Extensions" { + extensionsNode = &n.Children[i] + break + } + } + if extensionsNode == nil { + t.Fatal("expected 'Extensions' node") + } + + found := false + for _, child := range extensionsNode.Children { + if child.Label == "Authority Key ID" && child.Value == "05060708" { + found = true + break + } + } + if !found { + t.Error("expected 'Authority Key ID' with value '05060708'") + } + }) +} + +func TestPopulateTlogEntrySummary(t *testing.T) { + trBytes, err := os.ReadFile("../../../../pkg/cosign/testdata/trusted_root_pgi.json") + if err != nil { + t.Fatal(err) + } + tr, err := root.NewTrustedRootFromJSON(trBytes) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + entry *rekorv1.TransparencyLogEntry + expectedPairs map[string]string + }{ + { + name: "Not found in initialized trusted root", + entry: &rekorv1.TransparencyLogEntry{ + LogIndex: 123, + LogId: &protocommon.LogId{ + KeyId: []byte("log-id"), + }, + }, + expectedPairs: map[string]string{ + "Log Index": "123", + "Log ID": "6c6f672d6964", + }, + }, + { + name: "Found in initialized trusted root", + entry: &rekorv1.TransparencyLogEntry{ + LogIndex: 456, + LogId: &protocommon.LogId{ + KeyId: func() []byte { + b, _ := hex.DecodeString("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d") + return b + }(), + }, + }, + expectedPairs: map[string]string{ + "Log Index": "456", + "Log URL": "https://rekor.sigstore.dev", + }, + }, + { + name: "with kind/version", + entry: &rekorv1.TransparencyLogEntry{ + LogIndex: 777, + KindVersion: &rekorv1.KindVersion{ + Kind: "hashedrekord", + Version: "0.0.1", + }, + }, + expectedPairs: map[string]string{ + "Log Index": "777", + "Kind": "hashedrekord", + "Version": "0.0.1", + }, + }, + { + name: "with canonicalized body", + entry: &rekorv1.TransparencyLogEntry{ + LogIndex: 888, + CanonicalizedBody: []byte(`{"body":"json"}`), + }, + expectedPairs: map[string]string{ + "Log Index": "888", + "Canonicalized Body": "Present (15 bytes)", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + n := &node{} + populateTlogEntrySummary(n, tc.entry, tr) + + for expectedLabel, expectedValue := range tc.expectedPairs { + found := false + for _, child := range n.Children { + if child.Label == expectedLabel && child.Value == expectedValue { + found = true + break + } + } + if !found { + t.Errorf("expected child with label %s and value %s", expectedLabel, expectedValue) + } + } + }) + } +} + +func TestPopulateContentSummary(t *testing.T) { + tests := []struct { + name string + bundleContent *protobundle.Bundle + expectedLabel string + expectedValue string + }{ + { + name: "DSSE Envelope", + bundleContent: &protobundle.Bundle{ + Content: &protobundle.Bundle_DsseEnvelope{ + DsseEnvelope: &protodsse.Envelope{ + PayloadType: "application/vnd.in-toto+json", + Payload: []byte(`{"predicateType":"https://in-toto.io/Attestation/GitHubWorkflow/v0.1","subject":[{"name":"foo"}]}`), + }, + }, + }, + expectedLabel: "Type", + expectedValue: "DSSE Envelope", + }, + { + name: "Message Signature", + bundleContent: &protobundle.Bundle{ + Content: &protobundle.Bundle_MessageSignature{ + MessageSignature: &protocommon.MessageSignature{ + MessageDigest: &protocommon.HashOutput{ + Algorithm: protocommon.HashAlgorithm_SHA2_256, + Digest: []byte("digest"), + }, + Signature: []byte("signature"), + }, + }, + }, + expectedLabel: "Type", + expectedValue: "Message Signature", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + n := &node{} + populateContentSummary(n, tc.bundleContent) + + found := false + for _, child := range n.Children { + if child.Label == "Content" { + for _, grandChild := range child.Children { + if grandChild.Label == tc.expectedLabel && grandChild.Value == tc.expectedValue { + found = true + break + } + } + } + } + if !found { + t.Errorf("expected child with label %s and value %s", tc.expectedLabel, tc.expectedValue) + } + }) + } +} + +func TestPopulateTimestampSummary(t *testing.T) { + payload := []byte{1, 2, 3, 4} + h := sha256.Sum256(payload) + + ecdsaPriv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + signature, err := ecdsaPriv.Sign(rand.Reader, h[:], crypto.SHA256) + if err != nil { + t.Fatal(err) + } + + client, err := tsaMock.NewTSAClient(tsaMock.TSAClientOptions{Time: time.Now()}) + if err != nil { + t.Fatal(err) + } + + tsBytes, err := getTimestampedSignature(signature, client) + if err != nil { + t.Fatal(err) + } + + n := &node{} + tsData := &protobundle.TimestampVerificationData{ + Rfc3161Timestamps: []*protocommon.RFC3161SignedTimestamp{ + {SignedTimestamp: tsBytes}, + }, + } + + populateTimestampSummary(n, tsData) + + if len(n.Children) == 0 { + t.Error("expected children, got none") + } + + found := false + for _, child := range n.Children { + if child.Label == "RFC3161 Timestamps" && child.Value == "1" { + found = true + break + } + } + if !found { + t.Error("expected 'RFC3161 Timestamps' with value '1'") + } +} + +func TestPopulateContentSummary_DsseSignatures(t *testing.T) { + tests := []struct { + name string + bundle *protobundle.Bundle + expectedLabels []string + expectedValues []string + }{ + { + name: "Valid Base64 Key Hint", + bundle: &protobundle.Bundle{ + Content: &protobundle.Bundle_DsseEnvelope{ + DsseEnvelope: &protodsse.Envelope{ + Signatures: []*protodsse.Signature{ + { + Sig: []byte("sig1"), + Keyid: "dGVzdC1oaW50", + }, + }, + }, + }, + }, + expectedLabels: []string{"Key Hint"}, + expectedValues: []string{"746573742d68696e74"}, + }, + { + name: "Invalid Base64 Key Hint", + bundle: &protobundle.Bundle{ + Content: &protobundle.Bundle_DsseEnvelope{ + DsseEnvelope: &protodsse.Envelope{ + Signatures: []*protodsse.Signature{ + { + Sig: []byte("sig2"), + Keyid: "not-base64-!", + }, + }, + }, + }, + }, + expectedLabels: []string{"Key Hint"}, + expectedValues: []string{"not-base64-!"}, + }, + { + name: "Key Hint Mismatch", + bundle: &protobundle.Bundle{ + VerificationMaterial: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_PublicKey{ + PublicKey: &protocommon.PublicKeyIdentifier{ + Hint: "different-hint", + }, + }, + }, + Content: &protobundle.Bundle_DsseEnvelope{ + DsseEnvelope: &protodsse.Envelope{ + Signatures: []*protodsse.Signature{ + { + Sig: []byte("sig3"), + Keyid: "hint-1", + }, + }, + }, + }, + }, + expectedLabels: []string{"Key Hint", "[!] WARNING"}, + expectedValues: []string{"hint-1", "Key hint mismatch with Verification Material"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + n := &node{} + populateContentSummary(n, tc.bundle) + + // Helper function to recursively find nodes in the tree + var findNodes func(n *node, label string) []*node + findNodes = func(n *node, label string) []*node { + var res []*node + if n.Label == label { + res = append(res, n) + } + for i := range n.Children { + res = append(res, findNodes(&n.Children[i], label)...) + } + return res + } + + for i, expectedLabel := range tc.expectedLabels { + expectedValue := tc.expectedValues[i] + nodes := findNodes(n, expectedLabel) + found := false + for _, matchingNode := range nodes { + if matchingNode.Value == expectedValue { + found = true + break + } + } + if !found { + t.Errorf("expected node with label %q and value %q to be present in tree", expectedLabel, expectedValue) + } + } + }) + } +} + +func TestPopulateTlogEntrySummary_Inclusion(t *testing.T) { + t.Run("Inclusion promise", func(t *testing.T) { + n := &node{} + entry := &rekorv1.TransparencyLogEntry{ + InclusionPromise: &rekorv1.InclusionPromise{ + SignedEntryTimestamp: []byte("promise"), + }, + } + populateTlogEntrySummary(n, entry, nil) + + var promiseNode *node + for i := range n.Children { + if n.Children[i].Label == "Inclusion Promise" { + promiseNode = &n.Children[i] + break + } + } + if promiseNode == nil { + t.Fatal("expected 'Inclusion Promise' node") + } + if len(promiseNode.Children) != 1 || promiseNode.Children[0].Label != "Signed Entry Timestamp" || promiseNode.Children[0].Value != "Present (7 bytes)" { + t.Errorf("unexpected Inclusion Promise children: %v", promiseNode.Children) + } + }) + + t.Run("Inclusion proof with invalid checkpoint", func(t *testing.T) { + n := &node{} + entry := &rekorv1.TransparencyLogEntry{ + InclusionProof: &rekorv1.InclusionProof{ + LogIndex: 123, + TreeSize: 456, + Hashes: [][]byte{[]byte("h1")}, + Checkpoint: &rekorv1.Checkpoint{ + Envelope: "invalid", + }, + }, + } + populateTlogEntrySummary(n, entry, nil) + + var proofNode *node + for i := range n.Children { + if n.Children[i].Label == "Inclusion Proof" { + proofNode = &n.Children[i] + break + } + } + if proofNode == nil { + t.Fatal("expected 'Inclusion Proof' node") + } + + expectedProofFields := map[string]string{ + "Log Index": "123", + "Tree Size": "456", + "Hashes": "1", + } + for _, child := range proofNode.Children { + if val, ok := expectedProofFields[child.Label]; ok { + if child.Value != val { + t.Errorf("expected proof field %s to be %s, got %s", child.Label, val, child.Value) + } + } + } + + var checkpointNode *node + for i := range proofNode.Children { + if proofNode.Children[i].Label == "Checkpoint" { + checkpointNode = &proofNode.Children[i] + break + } + } + if checkpointNode == nil { + t.Fatal("expected 'Checkpoint' node under proof") + } + if len(checkpointNode.Children) != 1 || checkpointNode.Children[0].Label != "Envelope" || checkpointNode.Children[0].Value != "Present (7 bytes)" { + t.Errorf("unexpected Checkpoint children: %v", checkpointNode.Children) + } + }) + + t.Run("Inclusion proof with valid checkpoint", func(t *testing.T) { + n := &node{} + validEnvelope := "rekor.sigstore.dev\n123\n47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\n\n— witness1 dGVzdC1zaWc=\n" + entry := &rekorv1.TransparencyLogEntry{ + InclusionProof: &rekorv1.InclusionProof{ + LogIndex: 123, + TreeSize: 456, + Hashes: [][]byte{[]byte("h1")}, + Checkpoint: &rekorv1.Checkpoint{ + Envelope: validEnvelope, + }, + }, + } + populateTlogEntrySummary(n, entry, nil) + + var proofNode *node + for i := range n.Children { + if n.Children[i].Label == "Inclusion Proof" { + proofNode = &n.Children[i] + break + } + } + if proofNode == nil { + t.Fatal("expected 'Inclusion Proof' node") + } + + var checkpointNode *node + for i := range proofNode.Children { + if proofNode.Children[i].Label == "Checkpoint" { + checkpointNode = &proofNode.Children[i] + break + } + } + if checkpointNode == nil { + t.Fatal("expected 'Checkpoint' node under proof") + } + + hasOrigin := false + expectedCheckpointFields := map[string]string{ + "Origin": "rekor.sigstore.dev", + "Tree Size": "123", + } + for _, child := range checkpointNode.Children { + if child.Label == "Origin" { + hasOrigin = true + } + if val, ok := expectedCheckpointFields[child.Label]; ok { + if child.Value != val { + t.Errorf("expected checkpoint field %s to be %s, got %s", child.Label, val, child.Value) + } + } + } + if !hasOrigin { + t.Error("expected 'Origin' node, meaning checkpoint unmarshaling failed and hit fallback") + } + + var witnessesNode *node + for i := range checkpointNode.Children { + if checkpointNode.Children[i].Label == "Witnesses" { + witnessesNode = &checkpointNode.Children[i] + break + } + } + if witnessesNode == nil { + t.Fatal("expected 'Witnesses' node under checkpoint") + } + if len(witnessesNode.Children) != 1 || witnessesNode.Children[0].Label != "witness1" || !strings.Contains(witnessesNode.Children[0].Value, "Signature Present") { + t.Errorf("unexpected Witnesses children: %v", witnessesNode.Children) + } + }) +} + +func getTimestampedSignature(sigBytes []byte, tsaClient *tsaMock.TSAClient) ([]byte, error) { + requestBytes, err := timestamp.CreateRequest(bytes.NewReader(sigBytes), ×tamp.RequestOptions{ + Hash: crypto.SHA256, + Certificates: true, + }) + if err != nil { + return nil, fmt.Errorf("error creating timestamp request: %w", err) + } + + return tsaClient.GetTimestampResponse(requestBytes) +} diff --git a/cmd/cosign/cli/bundle/upgrade.go b/cmd/cosign/cli/bundle/upgrade.go new file mode 100644 index 00000000000..5b254060898 --- /dev/null +++ b/cmd/cosign/cli/bundle/upgrade.go @@ -0,0 +1,156 @@ +// +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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" + "fmt" + "os" + + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + rekor "github.com/sigstore/rekor/pkg/client" + "github.com/sigstore/rekor/pkg/generated/client" + "github.com/sigstore/rekor/pkg/generated/client/entries" + "github.com/sigstore/rekor/pkg/tle" + "google.golang.org/protobuf/encoding/protojson" + + "github.com/sigstore/cosign/v3/internal/ui" +) + +type UpgradeCmd struct { + Out string + RekorURL string +} + +func (c *UpgradeCmd) Exec(ctx context.Context, bundlePath string) error { + data, err := os.ReadFile(bundlePath) + if err != nil { + return fmt.Errorf("reading input file: %w", err) + } + + rekorClient, err := rekor.GetRekorClient(c.RekorURL) + if err != nil { + return fmt.Errorf("creating rekor client: %w", err) + } + + upgradedBundle, err := upgradeBundle(ctx, data, rekorClient) + if err != nil { + return fmt.Errorf("upgrading bundle: %w", err) + } + + if c.Out != "" { + err = os.WriteFile(c.Out, upgradedBundle, 0600) // #nosec G703 -- user-supplied output path is intentional + if err != nil { + return fmt.Errorf("writing upgraded bundle: %w", err) + } + ui.Infof(ctx, "Successfully upgraded bundle written to %s", c.Out) + } else { + fmt.Println(string(upgradedBundle)) + } + + return nil +} + +func upgradeBundle(ctx context.Context, data []byte, rekorClient *client.Rekor) ([]byte, error) { + var bundle protobundle.Bundle + if err := protojson.Unmarshal(data, &bundle); err != nil { + return nil, fmt.Errorf("unmarshaling bundle: %w", err) + } + + if bundle.VerificationMaterial == nil { + return nil, fmt.Errorf("bundle is missing verification material") + } + if bundle.VerificationMaterial.Content == nil { + return nil, fmt.Errorf("bundle verification material is missing content (public key or certificate)") + } + + switch bundle.MediaType { + case "application/vnd.dev.sigstore.bundle.v0.3+json", "application/vnd.dev.sigstore.bundle+json;version=0.3": + ui.Infof(ctx, "Bundle is already at v0.3, no upgrade needed.") + return data, nil + case "application/vnd.dev.sigstore.bundle+json;version=0.1": + ui.Infof(ctx, "Upgrading from v0.1 to v0.3...") + case "application/vnd.dev.sigstore.bundle+json;version=0.2": + ui.Infof(ctx, "Upgrading from v0.2 to v0.3...") + default: + return nil, fmt.Errorf("unsupported bundle version: %s", bundle.MediaType) + } + + if chainContent, ok := bundle.VerificationMaterial.Content.(*protobundle.VerificationMaterial_X509CertificateChain); ok { + certChain := chainContent.X509CertificateChain.Certificates + if len(certChain) > 0 { + ui.Infof(ctx, "Truncating certificate chain to only the leaf certificate...") + for i, cert := range certChain { + parsedCert, err := x509.ParseCertificate(cert.RawBytes) + if err != nil { + ui.Infof(ctx, " Certificate %d: ", i, err) + continue + } + + var identity string + if len(parsedCert.EmailAddresses) > 0 { + identity = parsedCert.EmailAddresses[0] + } else if len(parsedCert.URIs) > 0 { + identity = parsedCert.URIs[0].String() + } else if s := parsedCert.Subject.String(); s != "" { + identity = s + } else { + identity = "" + } + ui.Infof(ctx, " Certificate %d Identity: %s", i, identity) + } + bundle.VerificationMaterial.Content = &protobundle.VerificationMaterial_Certificate{ + Certificate: certChain[0], + } + } + } + + for i, entry := range bundle.VerificationMaterial.TlogEntries { + if entry.InclusionPromise != nil && entry.InclusionProof == nil { + ui.Infof(ctx, "Fetching missing inclusion proof from Rekor for log index %d...", entry.LogIndex) + + params := entries.NewGetLogEntryByIndexParamsWithContext(ctx) + params.SetLogIndex(entry.LogIndex) + + resp, err := rekorClient.Entries.GetLogEntryByIndex(params) + if err != nil { + return nil, fmt.Errorf("fetching log entry by index: %w", err) + } + + if len(resp.Payload) != 1 { + return nil, fmt.Errorf("expected exactly 1 entry from Rekor for index %d, got %d", entry.LogIndex, len(resp.Payload)) + } + + for _, e := range resp.Payload { + protoEntry, err := tle.GenerateTransparencyLogEntry(e) + if err != nil { + return nil, fmt.Errorf("generating proto entry: %w", err) + } + bundle.VerificationMaterial.TlogEntries[i] = protoEntry + } + } + } + + bundle.MediaType = "application/vnd.dev.sigstore.bundle.v0.3+json" + + out, err := protojson.Marshal(&bundle) + if err != nil { + return nil, fmt.Errorf("marshaling bundle: %w", err) + } + + return out, nil +} diff --git a/cmd/cosign/cli/bundle/upgrade_test.go b/cmd/cosign/cli/bundle/upgrade_test.go new file mode 100644 index 00000000000..594715d4637 --- /dev/null +++ b/cmd/cosign/cli/bundle/upgrade_test.go @@ -0,0 +1,447 @@ +// +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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 ( + "bytes" + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/json" + "io" + "math/big" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + "github.com/sigstore/rekor/pkg/generated/client" + "github.com/sigstore/rekor/pkg/generated/client/entries" + "github.com/sigstore/rekor/pkg/generated/models" +) + +type mockEntriesClient struct { + entries.ClientService + getLogEntryByIndexFunc func(params *entries.GetLogEntryByIndexParams, opts ...entries.ClientOption) (*entries.GetLogEntryByIndexOK, error) +} + +func (m *mockEntriesClient) GetLogEntryByIndex(params *entries.GetLogEntryByIndexParams, opts ...entries.ClientOption) (*entries.GetLogEntryByIndexOK, error) { + if m.getLogEntryByIndexFunc != nil { + return m.getLogEntryByIndexFunc(params, opts...) + } + return nil, nil +} + +func TestUpgradeBundle(t *testing.T) { + checkV03 := func(expectedCertB64 string) func(t *testing.T, output []byte) { + return func(t *testing.T, output []byte) { + var m map[string]interface{} + err := json.Unmarshal(output, &m) + if err != nil { + t.Fatalf("unmarshaling output: %v", err) + } + + if m["mediaType"] != "application/vnd.dev.sigstore.bundle.v0.3+json" { + t.Fatalf("expected mediaType to be 'application/vnd.dev.sigstore.bundle.v0.3+json', got %v", m["mediaType"]) + } + + vm, ok := m["verificationMaterial"].(map[string]interface{}) + if !ok { + t.Fatal("missing verificationMaterial") + } + + cert, ok := vm["certificate"].(map[string]interface{}) + if !ok { + t.Fatal("missing certificate field in verificationMaterial") + } + + if cert["rawBytes"] != expectedCertB64 { + t.Fatalf("expected leaf certificate rawBytes to be %v, got %v", expectedCertB64, cert["rawBytes"]) + } + } + } + + genCert := func(cn string, emails []string, uris []string) string { + cert, _, err := selfSignedCertificate(cn, emails, uris) + if err != nil { + t.Fatal(err) + } + return base64.StdEncoding.EncodeToString(cert.Raw) + } + + certEmailB64 := genCert("cert", []string{"foo@bar.com"}, nil) + certURIB64 := genCert("cert", nil, []string{"https://example.com/workflow"}) + certSubjectB64 := genCert("my-identity", nil, nil) + certNoneB64 := genCert("", nil, nil) + + tests := []struct { + name string + input string + expectError bool + checkOutput func(t *testing.T, output []byte) + rekorClient *client.Rekor + }{ + { + name: "Already v0.3", + input: `{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"publicKey":{}}}`, + checkOutput: func(t *testing.T, output []byte) { + expected := `{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"publicKey":{}}}` + if string(output) != expected { + t.Fatal("expected output to match input") + } + }, + }, + { + name: "Upgrade v0.1 to v0.3", + input: `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "bGVhZg=="}, + {"rawBytes": "aW50ZXJtZWRpYXRl"} + ] + } + } + }`, + checkOutput: checkV03("bGVhZg=="), + }, + { + name: "Upgrade v0.2 to v0.3", + input: `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.2", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "bGVhZg=="}, + {"rawBytes": "aW50ZXJtZWRpYXRl"} + ] + } + } + }`, + checkOutput: checkV03("bGVhZg=="), + }, + { + name: "Upgrade with Email SAN", + input: `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "` + certEmailB64 + `"} + ] + } + } + }`, + checkOutput: checkV03(certEmailB64), + }, + { + name: "Upgrade with URI SAN", + input: `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "` + certURIB64 + `"} + ] + } + } + }`, + checkOutput: checkV03(certURIB64), + }, + { + name: "Upgrade with Subject", + input: `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "` + certSubjectB64 + `"} + ] + } + } + }`, + checkOutput: checkV03(certSubjectB64), + }, + { + name: "Upgrade with no identity", + input: `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "` + certNoneB64 + `"} + ] + } + } + }`, + checkOutput: checkV03(certNoneB64), + }, + { + name: "Missing VerificationMaterial", + input: `{"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1"}`, + expectError: true, + }, + { + name: "Missing Content in VerificationMaterial", + input: `{"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", "verificationMaterial": {}}`, + expectError: true, + }, + { + name: "Unsupported version", + input: `{"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.4", "verificationMaterial": {"publicKey": {}}}`, + expectError: true, + }, + { + name: "Upgrade with missing inclusion proof", + input: `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "bGVhZg=="} + ] + }, + "tlogEntries": [ + { + "logIndex": 42, + "inclusionPromise": { + "signedEntryTimestamp": "c2ln" + } + } + ] + } + }`, + rekorClient: &client.Rekor{ + Entries: &mockEntriesClient{ + getLogEntryByIndexFunc: func(_ *entries.GetLogEntryByIndexParams, _ ...entries.ClientOption) (*entries.GetLogEntryByIndexOK, error) { + return &entries.GetLogEntryByIndexOK{ + Payload: models.LogEntry{ + "uuid": createMockLogEntryAnon(), + }, + }, nil + }, + }, + }, + checkOutput: func(t *testing.T, output []byte) { + var m map[string]interface{} + if err := json.Unmarshal(output, &m); err != nil { + t.Fatalf("unmarshaling output: %v", err) + } + vm := m["verificationMaterial"].(map[string]interface{}) + tlogEntries := vm["tlogEntries"].([]interface{}) + entry := tlogEntries[0].(map[string]interface{}) + proof := entry["inclusionProof"].(map[string]interface{}) + if proof["logIndex"].(string) != "42" { + t.Fatalf("expected logIndex 42, got %v", proof["logIndex"]) + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + out, err := upgradeBundle(context.Background(), []byte(tc.input), tc.rekorClient) + if tc.expectError { + if err == nil { + t.Fatal("expected error, got nil") + } + } else { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if tc.checkOutput != nil { + tc.checkOutput(t, out) + } + } + }) + } +} + +func TestUpgradeCmd(t *testing.T) { + ctx := context.Background() + + t.Run("File Output", func(t *testing.T) { + td := t.TempDir() + + inputPath := filepath.Join(td, "input.json") + v01Bundle := `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "bGVhZg=="} + ] + } + } + }` + err := os.WriteFile(inputPath, []byte(v01Bundle), 0600) + if err != nil { + t.Fatal(err) + } + + outputPath := filepath.Join(td, "output.json") + + cmd := UpgradeCmd{ + Out: outputPath, + } + + err = cmd.Exec(ctx, inputPath) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + data, err := os.ReadFile(outputPath) + if err != nil { + t.Fatal(err) + } + + var m map[string]interface{} + if err := json.Unmarshal(data, &m); err != nil { + t.Fatal(err) + } + + if m["mediaType"] != "application/vnd.dev.sigstore.bundle.v0.3+json" { + t.Fatalf("expected mediaType to be 'application/vnd.dev.sigstore.bundle.v0.3+json', got %v", m["mediaType"]) + } + }) + + t.Run("Stdout Fallback", func(t *testing.T) { + td := t.TempDir() + + inputPath := filepath.Join(td, "input.json") + v01Bundle := `{ + "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.1", + "verificationMaterial": { + "x509CertificateChain": { + "certificates": [ + {"rawBytes": "bGVhZg=="} + ] + } + } + }` + err := os.WriteFile(inputPath, []byte(v01Bundle), 0600) + if err != nil { + t.Fatal(err) + } + + cmd := UpgradeCmd{ + Out: "", + } + + reader, writer, err := os.Pipe() + if err != nil { + t.Fatal("failed to create a pipe for testing os.Stdout") + } + stdout := os.Stdout + os.Stdout = writer + + err = cmd.Exec(ctx, inputPath) + + os.Stdout = stdout + writer.Close() + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var buffer bytes.Buffer + _, err = io.Copy(&buffer, reader) + if err != nil { + t.Fatal(err) + } + + output := buffer.Bytes() + var m map[string]interface{} + if err := json.Unmarshal(output, &m); err != nil { + t.Fatalf("unmarshaling stdout output: %v", err) + } + + if m["mediaType"] != "application/vnd.dev.sigstore.bundle.v0.3+json" { + t.Fatalf("expected mediaType to be 'application/vnd.dev.sigstore.bundle.v0.3+json', got %v", m["mediaType"]) + } + }) +} + +func createMockLogEntryAnon() models.LogEntryAnon { + body := "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIyMzBkODM1OGRjOGU4ODkwYjRjNThkZWViNjI5MTJlZTJmMjAzNTdhZTkyYTVjYzg2MWI5OGU2OGZlMzFhY2I1In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUURHcXFXL1Q4YzZqZTltSVFPaUNhZXdldWZpUmJXMC9YZVBJUmMxWVdXUVZBSWdNWjVlbmNvWFdNdjdUK3AxU0k1YUUzdzZOb3Zyb3RYdGVoMlV3V040SUJvPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVSnJha05EUVZScFowRjNTVUpCWjBsQ1FWUkJTMEpuWjNGb2EycFBVRkZSUkVGcVFYVk5VbFYzUlhkWlJGWlJVVXRGZDNoNllWZGtlbVJIT1hrS1dsTTFhMXBZV1hoR1ZFRlVRbWRPVmtKQlRWUkVTRTV3V2pOT01HSXpTbXhNV0U0eFdXcEJaVVozTUhsT2FrRXdUVlJWZUU1RVRUUk9SR2hoUm5jd2VRcE9ha0V3VFZSVmVFNVVUVFZPUkdoaFRVRkJkMWRVUVZSQ1oyTnhhR3RxVDFCUlNVSkNaMmR4YUd0cVQxQlJUVUpDZDA1RFFVRlVMM0JKUVdaWldXNUxDazlHYkUxS00zYzBMMnBxUmxoNFRHNHhTRlZOWkVzMGJteDJiU3RLV0c1WmNUbFlPRlZpTkVWRFJFcFFjRUp1VW1sd1pGQkRUWGxYWTBsRGRIRkVPV1lLVlVGdFpIVnZiMUZLWmpReWJ6TlZkMk42UVU5Q1owNVdTRkU0UWtGbU9FVkNRVTFEUWpSQmQwVjNXVVJXVWpCc1FrRjNkME5uV1VsTGQxbENRbEZWU0FwQmQwMTNTSGRaUkZaU01HcENRbWQzUm05QlZXbHFPVEJ5VFZSeVVGaHlkM2hHYm5kTk9FOUdla3MxUW5Ock1IZEdVVmxFVmxJd1VrRlJTQzlDUVhOM0NrTlpSVWhqTTFacFlXMVdhbVJFUVZWQ1oyOXlRbWRGUlVGWlR5OU5RVVZDUWtGYWNHTXpUakZhV0VsM1EyZFpTVXR2V2tsNmFqQkZRWGRKUkZOQlFYY0tVbEZKWjFSSk4ya3pUWEYwVVRBMlp6QnlNbTlEWXpWb1QwZFBWRVZLYlVKSlZUSTVhRXhqVGxKNFJXeHBTbTlEU1ZGRWNuWk9TSE56U21vd1JqWlVNd293UmpaTU4wRkhiWEpDVlhGQllWSllSV0owWVhOaE5IcFJkWHA0YUdjOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifX19fQ==" + integratedTime := int64(1234567890) + logIndex := int64(42) + treeSize := int64(100) + rootHash := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + logID := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + checkpoint := "checkpoint" + return models.LogEntryAnon{ + Body: body, + IntegratedTime: &integratedTime, + LogIndex: &logIndex, + LogID: &logID, + Verification: &models.LogEntryAnonVerification{ + SignedEntryTimestamp: []byte("sig"), + InclusionProof: &models.InclusionProof{ + LogIndex: &logIndex, + TreeSize: &treeSize, + RootHash: &rootHash, + Hashes: []string{}, + Checkpoint: &checkpoint, + }, + }, + } +} + +func selfSignedCertificate(commonName string, emails []string, uris []string) (*x509.Certificate, *ecdsa.PrivateKey, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err + } + + var parsedURIs []*url.URL + for _, u := range uris { + parsed, _ := url.Parse(u) + parsedURIs = append(parsedURIs, parsed) + } + + var subject pkix.Name + if commonName != "" { + subject.CommonName = commonName + subject.Organization = []string{"dev"} + } + + ct := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: subject, + EmailAddresses: emails, + URIs: parsedURIs, + NotBefore: time.Now().Add(-1 * time.Minute), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + } + certBytes, err := x509.CreateCertificate(rand.Reader, ct, ct, &priv.PublicKey, priv) + if err != nil { + return nil, nil, err + } + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, nil, err + } + return cert, priv, nil +} diff --git a/cmd/cosign/cli/clean.go b/cmd/cosign/cli/clean.go index 32b68385700..0d631fd5fda 100644 --- a/cmd/cosign/cli/clean.go +++ b/cmd/cosign/cli/clean.go @@ -25,9 +25,10 @@ import ( "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/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/internal/ui" - ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/spf13/cobra" ) @@ -36,7 +37,7 @@ func Clean() *cobra.Command { cmd := &cobra.Command{ Use: "clean", - Short: "Remove all signatures from an image.", + Short: "Remove all signatures from an image", Example: " cosign clean ", Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, @@ -62,34 +63,98 @@ func CleanCmd(ctx context.Context, regOpts options.RegistryOptions, cleanType op } remoteOpts := regOpts.GetRegistryClientOpts(ctx) + ociRemoteOpts := ociremote.WithRemoteOptions(remoteOpts...) - sigRef, err := ociremote.SignatureTag(ref, ociremote.WithRemoteOptions(remoteOpts...)) + sigRef, err := ociremote.SignatureTag(ref, ociRemoteOpts) if err != nil { return err } - attRef, err := ociremote.AttestationTag(ref, ociremote.WithRemoteOptions(remoteOpts...)) + attRef, err := ociremote.AttestationTag(ref, ociRemoteOpts) if err != nil { return err } - sbomRef, err := ociremote.SBOMTag(ref, ociremote.WithRemoteOptions(remoteOpts...)) + sbomRef, err := ociremote.SBOMTag(ref, ociRemoteOpts) if err != nil { return err } - var cleanTags []name.Tag + referrerRefs := []name.Reference{} + digest, ok := ref.(name.Digest) + if !ok { + var err error + digest, err = ociremote.ResolveDigest(ref, ociRemoteOpts) + if err != nil { + return fmt.Errorf("resolving digest: %w", err) + } + } + idx, err := remote.Referrers(digest, remoteOpts...) + if err != nil { + return err + } + if idx != nil { + // Delete manifest + imgDigest, err := idx.Digest() + if err != nil { + return err + } + referrerDigestStr := fmt.Sprintf("%s@%s", ref.Context().Name(), imgDigest.String()) + referrerDigest, err := name.NewDigest(referrerDigestStr) + if err != nil { + return err + } + referrerRefs = append(referrerRefs, referrerDigest) + + // Delete layers in the manifest + idxManifest, err := idx.IndexManifest() + if err != nil { + return err + } + if idxManifest != nil { + for _, manifest := range idxManifest.Manifests { + layerDigestStr := fmt.Sprintf("%s@%s", ref.Context().Name(), manifest.Digest.String()) + layerDigest, err := name.NewDigest(layerDigestStr) + if err != nil { + return err + } + layerImage, err := remote.Image(layerDigest, remoteOpts...) + if err != nil { + return err + } + layerManifest, err := layerImage.Manifest() + if err != nil { + return err + } + if layerManifest != nil { + if layerManifest.Config.ArtifactType == bundle.BundleV03MediaType { + referrerRefs = append(referrerRefs, layerDigest) + } + } + } + } + } + + var cleanTags []name.Reference switch cleanType { case options.CleanTypeSignature: - cleanTags = []name.Tag{sigRef} + cleanTags = []name.Reference{sigRef} + if len(referrerRefs) > 0 { + ui.Warnf(ctx, "image has referrers, consider using --referrer") + } case options.CleanTypeSbom: - cleanTags = []name.Tag{sbomRef} + cleanTags = []name.Reference{sbomRef} case options.CleanTypeAttestation: - cleanTags = []name.Tag{attRef} + cleanTags = []name.Reference{attRef} + if len(referrerRefs) > 0 { + ui.Warnf(ctx, "image has referrers, consider using --referrer") + } + case options.CleanTypeReferrer: + cleanTags = referrerRefs case options.CleanTypeAll: - cleanTags = []name.Tag{sigRef, attRef, sbomRef} + cleanTags = append([]name.Reference{sigRef, attRef, sbomRef}, referrerRefs...) default: - panic("invalid CleanType value") + return errors.New("invalid CleanType value") } for _, t := range cleanTags { @@ -103,13 +168,16 @@ func CleanCmd(ctx context.Context, regOpts options.RegistryOptions, cleanType op 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 + tTag, ok := t.(name.Tag) + if ok { + if err := deleteByDigest(tTag, 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, "could not delete %s by digest from %s:\n%v\n", t, imageRef, err) + fmt.Fprintf(os.Stderr, "Removed %s from %s\n", t, imageRef) } - } 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) @@ -138,6 +206,8 @@ func prompt(cleanType options.CleanType) string { return "this will remove all SBOMs from the image" case options.CleanTypeAttestation: return "this will remove all attestations from the image" + case options.CleanTypeReferrer: + return "this will remove all referrer attestations and/or signatures from the image" case options.CleanTypeAll: return "this will remove all signatures, SBOMs and attestations from the image" } diff --git a/cmd/cosign/cli/commands.go b/cmd/cosign/cli/commands.go index b4d5e5887c5..bf1720b651e 100644 --- a/cmd/cosign/cli/commands.go +++ b/cmd/cosign/cli/commands.go @@ -21,8 +21,8 @@ import ( cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd" "github.com/google/go-containerregistry/pkg/logs" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/templates" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/templates" "github.com/spf13/cobra" "github.com/spf13/pflag" cobracompletefig "github.com/withfig/autocomplete-tools/integrations/cobra" @@ -58,7 +58,7 @@ func New() *cobra.Command { cmd := &cobra.Command{ Use: "cosign", - Short: "A tool for Container Signing, Verification and Storage in an OCI registry.", + 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 { diff --git a/cmd/cosign/cli/copy.go b/cmd/cosign/cli/copy.go index 62e487a508c..5d8f503e04f 100644 --- a/cmd/cosign/cli/copy.go +++ b/cmd/cosign/cli/copy.go @@ -16,8 +16,8 @@ package cli import ( - "github.com/sigstore/cosign/v2/cmd/cosign/cli/copy" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/copy" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" "github.com/spf13/cobra" ) @@ -25,8 +25,9 @@ func Copy() *cobra.Command { o := &options.CopyOptions{} cmd := &cobra.Command{ - Use: "copy", - Short: "Copy the supplied container image and signatures.", + Deprecated: "there are several options you can use instead:\n - \"oras copy -r\" will copy images and referring artifacts\n - \"cosign save\" to write to disk, followed by \"cosign load\" for a new registry\n - \"cosign download\" followed by \"cosign attach\" to just copy the bundle", + Use: "copy", + Short: "Copy the supplied container image and signatures", Example: ` cosign copy # copy a container image and its signatures diff --git a/cmd/cosign/cli/copy/copy.go b/cmd/cosign/cli/copy/copy.go index fefe8d3bc88..bcc4c2b1c51 100644 --- a/cmd/cosign/cli/copy/copy.go +++ b/cmd/cosign/cli/copy/copy.go @@ -26,11 +26,11 @@ import ( 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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/oci" + ociplatform "github.com/sigstore/cosign/v3/pkg/oci/platform" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/oci/walk" "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/util/sets" ) diff --git a/cmd/cosign/cli/copy/copy_test.go b/cmd/cosign/cli/copy/copy_test.go index 737e0df9dd6..3e7360d8019 100644 --- a/cmd/cosign/cli/copy/copy_test.go +++ b/cmd/cosign/cli/copy/copy_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 the Sigstore Authors. +// 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. @@ -19,8 +19,8 @@ import ( "reflect" "testing" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" ) func TestCopyAttachmentTagPrefix(t *testing.T) { diff --git a/cmd/cosign/cli/debug.go b/cmd/cosign/cli/debug.go index 277c85a2830..52808743e37 100644 --- a/cmd/cosign/cli/debug.go +++ b/cmd/cosign/cli/debug.go @@ -15,7 +15,7 @@ package cli import ( - "github.com/sigstore/cosign/v2/cmd/cosign/cli/debug" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/debug" "github.com/spf13/cobra" ) @@ -34,7 +34,7 @@ func Debug() *cobra.Command { func debugProviders() *cobra.Command { cmd := &cobra.Command{ Use: "providers", - Short: "Show enabled/disabled OIDC providers.", + Short: "Show enabled/disabled OIDC providers", RunE: func(cmd *cobra.Command, _ []string) error { return debug.ProviderCmd(cmd.Context(), cmd.OutOrStdout()) }, diff --git a/cmd/cosign/cli/debug/provider.go b/cmd/cosign/cli/debug/provider.go index debf57d1bd1..8249a9af513 100644 --- a/cmd/cosign/cli/debug/provider.go +++ b/cmd/cosign/cli/debug/provider.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package debug +package debug // nolint:revive import ( "context" "fmt" "io" - "github.com/sigstore/cosign/v2/pkg/providers" + "github.com/sigstore/cosign/v3/pkg/providers" ) func ProviderCmd(ctx context.Context, w io.Writer) error { diff --git a/cmd/cosign/cli/dockerfile.go b/cmd/cosign/cli/dockerfile.go index 5f207af2cc9..9ef2f9c35ac 100644 --- a/cmd/cosign/cli/dockerfile.go +++ b/cmd/cosign/cli/dockerfile.go @@ -18,16 +18,17 @@ package cli import ( "fmt" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/dockerfile" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/dockerfile" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" "github.com/spf13/cobra" ) func Dockerfile() *cobra.Command { cmd := &cobra.Command{ - Use: "dockerfile", - Short: "Provides utilities for discovering images in and performing operations on Dockerfiles", + Use: "dockerfile", + Short: "Provides utilities for discovering images in and performing operations on Dockerfiles", + Deprecated: "dockerfile will be removed in v4.0.0 (see https://github.com/sigstore/cosign/issues/4696). Instead, please use `cosign verify` for each image in your Dockerfile", } cmd.AddCommand( diff --git a/cmd/cosign/cli/dockerfile/verify.go b/cmd/cosign/cli/dockerfile/verify.go index e0ffb8851ee..a6d51e6e16b 100644 --- a/cmd/cosign/cli/dockerfile/verify.go +++ b/cmd/cosign/cli/dockerfile/verify.go @@ -24,8 +24,8 @@ import ( "os" "strings" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" - "github.com/sigstore/cosign/v2/internal/ui" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v3/internal/ui" ) // VerifyCommand verifies a signature on a supplied container image diff --git a/cmd/cosign/cli/download.go b/cmd/cosign/cli/download.go index 61d0b9eca2a..a38f2e966fc 100644 --- a/cmd/cosign/cli/download.go +++ b/cmd/cosign/cli/download.go @@ -19,8 +19,8 @@ import ( "fmt" "os" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/download" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/download" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" "github.com/spf13/cobra" ) @@ -49,7 +49,7 @@ func downloadSignature() *cobra.Command { Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { - return download.SignatureCmd(cmd.Context(), *o, args[0]) + return download.SignatureCmd(cmd.Context(), *o, args[0], cmd.OutOrStdout()) }, } @@ -94,7 +94,7 @@ func downloadAttestation() *cobra.Command { Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { - return download.AttestationCmd(cmd.Context(), *o, *ao, args[0]) + return download.AttestationCmd(cmd.Context(), *o, *ao, args[0], cmd.OutOrStdout()) }, } diff --git a/cmd/cosign/cli/download/attestation.go b/cmd/cosign/cli/download/attestation.go index 152e934103d..cbbb965a7c4 100644 --- a/cmd/cosign/cli/download/attestation.go +++ b/cmd/cosign/cli/download/attestation.go @@ -19,16 +19,16 @@ import ( "context" "encoding/json" "errors" - "fmt" + "io" "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/oci/platform" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" ) -func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOptions options.AttestationDownloadOptions, imageRef string) error { +func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOptions options.AttestationDownloadOptions, imageRef string, out io.Writer) error { ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err @@ -46,6 +46,35 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOpt } } + // Try bundles first + newBundles, _, err := cosign.GetBundles(ctx, ref, ociremoteOpts) + if err == nil && len(newBundles) > 0 { + for _, eachBundle := range newBundles { + if predicateType != "" { + envelope, err := eachBundle.Envelope() + if err != nil || envelope == nil { + continue + } + statement, err := envelope.Statement() + if err != nil || statement == nil { + continue + } + if statement.PredicateType != predicateType { + continue + } + } + b, err := json.Marshal(eachBundle) + if err != nil { + return err + } + _, err = out.Write(append(b, byte('\n'))) + if err != nil { + return err + } + } + return nil + } + se, err := ociremote.SignedEntity(ref, ociremoteOpts...) var entityNotFoundError *ociremote.EntityNotFoundError if err != nil { @@ -76,7 +105,10 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOpt if err != nil { return err } - fmt.Println(string(b)) + _, err = out.Write(append(b, byte('\n'))) + if err != nil { + return err + } } return nil } diff --git a/cmd/cosign/cli/download/sbom.go b/cmd/cosign/cli/download/sbom.go index 66ff4257259..4191d517dd1 100644 --- a/cmd/cosign/cli/download/sbom.go +++ b/cmd/cosign/cli/download/sbom.go @@ -23,10 +23,10 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/platform" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" ) func SBOMCmd( diff --git a/cmd/cosign/cli/download/signature.go b/cmd/cosign/cli/download/signature.go index 64639c3232f..deb3d67c3be 100644 --- a/cmd/cosign/cli/download/signature.go +++ b/cmd/cosign/cli/download/signature.go @@ -18,14 +18,14 @@ package download import ( "context" "encoding/json" - "fmt" + "io" "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/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/cosign" ) -func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string) error { +func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string, out io.Writer) error { ref, err := name.ParseReference(imageRef, regOpts.NameOptions()...) if err != nil { return err @@ -34,6 +34,23 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef if err != nil { return err } + + // Try bundles first + newBundles, _, err := cosign.GetBundles(ctx, ref, ociremoteOpts) + if err == nil && len(newBundles) > 0 { + for _, eachBundle := range newBundles { + b, err := json.Marshal(eachBundle) + if err != nil { + return err + } + _, err = out.Write(append(b, byte('\n'))) + if err != nil { + return err + } + } + return nil + } + signatures, err := cosign.FetchSignaturesForReference(ctx, ref, ociremoteOpts...) if err != nil { return err @@ -43,7 +60,10 @@ func SignatureCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef if err != nil { return err } - fmt.Println(string(b)) + _, err = out.Write(append(b, byte('\n'))) + if err != nil { + return err + } } return nil } diff --git a/cmd/cosign/cli/env.go b/cmd/cosign/cli/env.go index 981a7bbb785..32db4eb41f5 100644 --- a/cmd/cosign/cli/env.go +++ b/cmd/cosign/cli/env.go @@ -21,8 +21,8 @@ import ( "sort" "strings" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/cosign/env" "github.com/spf13/cobra" ) @@ -32,7 +32,14 @@ func Env() *cobra.Command { cmd := &cobra.Command{ Use: "env", Short: "Prints Cosign environment variables", - Args: cobra.NoArgs, + Example: ` cosign env + + # show environment variables with descriptions + cosign env --show-descriptions + + # show environment variables including sensitive values + cosign env --show-descriptions --show-sensitive-values`, + Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { envVars := env.EnvironmentVariables() printEnv(envVars, getEnv(), getEnviron(), o.ShowDescriptions, o.ShowSensitiveValues) @@ -132,7 +139,7 @@ func printEnv(envVars map[env.Variable]env.VariableOpts, } func sortEnvKeys(envVars map[env.Variable]env.VariableOpts) []env.Variable { - keys := []env.Variable{} + keys := make([]env.Variable, 0, len(envVars)) for k := range envVars { keys = append(keys, k) } diff --git a/cmd/cosign/cli/env_test.go b/cmd/cosign/cli/env_test.go index 7cb64c9206d..910f5bf34a3 100644 --- a/cmd/cosign/cli/env_test.go +++ b/cmd/cosign/cli/env_test.go @@ -21,7 +21,7 @@ import ( "os" "testing" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) const ( @@ -109,7 +109,7 @@ func tGetEnv() envGetter { } func tGetEnviron() environGetter { return func() []string { - var s []string + s := make([]string, 0, len(testingEnvVars)) for k, v := range testingEnvVars { s = append(s, fmt.Sprintf("%s=%s", k, v)) diff --git a/cmd/cosign/cli/fulcio/depcheck_test.go b/cmd/cosign/cli/fulcio/depcheck_test.go index 0a4562559b7..6a1ef76a769 100644 --- a/cmd/cosign/cli/fulcio/depcheck_test.go +++ b/cmd/cosign/cli/fulcio/depcheck_test.go @@ -23,7 +23,7 @@ import ( func TestNoDeps(t *testing.T) { depcheck.AssertNoDependency(t, map[string][]string{ - "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio": { + "github.com/sigstore/cosign/v3/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", diff --git a/cmd/cosign/cli/fulcio/fulcio.go b/cmd/cosign/cli/fulcio/fulcio.go index 4a6d753cc8e..19205443e9f 100644 --- a/cmd/cosign/cli/fulcio/fulcio.go +++ b/cmd/cosign/cli/fulcio/fulcio.go @@ -16,178 +16,11 @@ 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" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/fulcio/fulcioroots" ) -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() } @@ -195,25 +28,3 @@ func GetRoots() (*x509.CertPool, error) { 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 -} diff --git a/cmd/cosign/cli/fulcio/fulcio_test.go b/cmd/cosign/cli/fulcio/fulcio_test.go deleted file mode 100644 index a4a8783a4fe..00000000000 --- a/cmd/cosign/cli/fulcio/fulcio_test.go +++ /dev/null @@ -1,229 +0,0 @@ -// -// 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") - } -} diff --git a/cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier.go b/cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier.go deleted file mode 100644 index 076a763c536..00000000000 --- a/cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier.go +++ /dev/null @@ -1,70 +0,0 @@ -// -// 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" - "crypto/x509" - "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-go/pkg/verify" - "github.com/sigstore/sigstore/pkg/cryptoutils" - "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 - } - - if ko.TrustedMaterial != nil && len(fs.SCT) == 0 { - // Detached SCTs cannot be verified with this function. - chain, err := cryptoutils.UnmarshalCertificatesFromPEM(fs.Chain) - if err != nil { - return nil, fmt.Errorf("unmarshalling cert chain from PEM for SCT verification: %w", err) - } - certs, err := cryptoutils.UnmarshalCertificatesFromPEM(fs.Cert) - if err != nil || len(certs) < 1 { - return nil, fmt.Errorf("unmarshalling cert from PEM for SCT verification: %w", err) - } - chain = append(certs, chain...) - chains := make([][]*x509.Certificate, 1) - chains[0] = chain - if err := verify.VerifySignedCertificateTimestamp(chains, 1, ko.TrustedMaterial); err != nil { - return nil, fmt.Errorf("verifying SCT using trusted root: %w", err) - } - ui.Infof(ctx, "Successfully verified SCT...") - return fs, nil - } - - // There was no trusted_root.json or we need to verify a detached SCT, so 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 -} diff --git a/cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier_test.go b/cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier_test.go deleted file mode 100644 index c0b11006ec1..00000000000 --- a/cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier_test.go +++ /dev/null @@ -1,419 +0,0 @@ -// 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 fulcioverifier - -import ( - "context" - "crypto" - "crypto/ed25519" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/base64" - "encoding/hex" - "encoding/json" - "encoding/pem" - "math/big" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - "time" - - ct "github.com/google/certificate-transparency-go" - "github.com/google/certificate-transparency-go/tls" - "github.com/google/certificate-transparency-go/trillian/ctfe" - ctx509 "github.com/google/certificate-transparency-go/x509" - ctx509util "github.com/google/certificate-transparency-go/x509util" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/initialize" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/fulcio/pkg/ctl" - "github.com/sigstore/sigstore-go/pkg/root" - "github.com/sigstore/sigstore/pkg/cryptoutils" - "github.com/sigstore/sigstore/pkg/signature" - "github.com/stretchr/testify/assert" - "github.com/theupdateframework/go-tuf/v2/metadata" -) - -func TestNewSigner(t *testing.T) { - td := t.TempDir() - t.Setenv("TUF_ROOT", td) - tufRepo := t.TempDir() - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatal(err) - } - skid, err := cryptoutils.SKID(&privateKey.PublicKey) - if err != nil { - t.Fatal(err) - } - caCert := createBaseCert(t, privateKey, skid, big.NewInt(1)) - logID, err := ctfe.GetCTLogID(&privateKey.PublicKey) - if err != nil { - t.Fatal(err) - } - sct := ct.SignedCertificateTimestamp{ - SCTVersion: ct.V1, - Timestamp: 12345, - LogID: ct.LogID{KeyID: logID}, - } - preCert := createBaseCert(t, privateKey, skid, big.NewInt(1)) - if err != nil { - t.Fatal(err) - } - pubBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) - if err != nil { - t.Fatal(err) - } - pubPEM := pem.EncodeToMemory(&pem.Block{ - Type: "PUBLIC KEY", - Bytes: pubBytes, - }) - err = newTUF(tufRepo, map[string][]byte{"ctfe.pub": pubPEM}) - if err != nil { - t.Fatal(err) - } - tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.FileServer(http.Dir(tufRepo)).ServeHTTP(w, r) - })) - err = initialize.DoInitialize(context.Background(), filepath.Join(tufRepo, "1.root.json"), tufServer.URL) - if err != nil { - t.Fatal(err) - } - - tests := []struct { - name string - embeddedSCT bool - trustedMaterial root.TrustedMaterial - }{ - { - name: "detached SCT", - embeddedSCT: false, - trustedMaterial: nil, - }, - { - name: "embedded SCT with legacy TUF metadata", - embeddedSCT: true, - trustedMaterial: nil, - }, - { - name: "embedded SCT with trusted root", - embeddedSCT: true, - trustedMaterial: &fakeTrustedMaterial{ - transparencyLog: map[string]*root.TransparencyLog{ - hex.EncodeToString(logID[:]): { - PublicKey: &privateKey.PublicKey, - }, - }, - cas: []root.CertificateAuthority{ - &root.FulcioCertificateAuthority{ - Root: caCert, - }, - }, - }, - }, - { - name: "detached SCT with trusted root uses legacy TUF client", - embeddedSCT: false, - trustedMaterial: &fakeTrustedMaterial{ - transparencyLog: map[string]*root.TransparencyLog{ - hex.EncodeToString(logID[:]): { - PublicKey: &privateKey.PublicKey, - }, - }, - cas: []root.CertificateAuthority{ - &root.FulcioCertificateAuthority{ - Root: caCert, - }, - }, - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - leafCert := preCert - sctHeader := "" - if test.embeddedSCT { - leafCert = embedSCT(t, privateKey, skid, preCert, sct) - } else { - sctHeader = detachedSCT(t, privateKey, preCert, sct) - } - pemChain, _ := cryptoutils.MarshalCertificatesToPEM([]*x509.Certificate{leafCert, caCert}) - testServer := httptest.NewServer(http.HandlerFunc( - func(w http.ResponseWriter, _ *http.Request) { - if sctHeader != "" { - w.Header().Set("SCT", sctHeader) - } - w.WriteHeader(http.StatusCreated) - _, _ = w.Write(pemChain) - })) - defer testServer.Close() - - ctx := context.Background() - 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) - } - - fs, err := NewSigner(ctx, ko, sv) - if test.embeddedSCT { - assert.Empty(t, fs.SCT) - } else { - assert.NotEmpty(t, fs.SCT) - } - assert.NoError(t, err) - }) - } -} - -func getSCT(t *testing.T, privateKey *rsa.PrivateKey, preCert *x509.Certificate, sctInput ct.SignedCertificateTimestamp, embedded bool) ct.SignedCertificateTimestamp { - logEntry := ct.LogEntry{ - Leaf: ct.MerkleTreeLeaf{ - Version: ct.V1, - LeafType: ct.TimestampedEntryLeafType, - TimestampedEntry: &ct.TimestampedEntry{ - Timestamp: sctInput.Timestamp, - }, - }, - } - if embedded { - logEntry.Leaf.TimestampedEntry.EntryType = ct.PrecertLogEntryType - logEntry.Leaf.TimestampedEntry.PrecertEntry = &ct.PreCert{ - IssuerKeyHash: sha256.Sum256(preCert.RawSubjectPublicKeyInfo), - TBSCertificate: preCert.RawTBSCertificate, - } - } else { - logEntry.Leaf.TimestampedEntry.EntryType = ct.X509LogEntryType - logEntry.Leaf.TimestampedEntry.X509Entry = &ct.ASN1Cert{Data: preCert.Raw} - } - data, err := ct.SerializeSCTSignatureInput(sctInput, logEntry) - if err != nil { - t.Fatal(err) - } - h := sha256.Sum256(data) - signature, err := privateKey.Sign(rand.Reader, h[:], crypto.SHA256) - if err != nil { - t.Fatal(err) - } - sct := ct.SignedCertificateTimestamp{ - SCTVersion: sctInput.SCTVersion, - LogID: sctInput.LogID, - Timestamp: sctInput.Timestamp, - Signature: ct.DigitallySigned{ - Algorithm: tls.SignatureAndHashAlgorithm{ - Hash: tls.SHA256, - Signature: tls.RSA, - }, - Signature: signature, - }, - } - return sct -} - -func detachedSCT(t *testing.T, privateKey *rsa.PrivateKey, preCert *x509.Certificate, sctInput ct.SignedCertificateTimestamp) string { - sct := getSCT(t, privateKey, preCert, sctInput, false) - addChainResp, err := ctl.ToAddChainResponse(&sct) - if err != nil { - t.Fatal(err) - } - sctBytes, err := json.Marshal(addChainResp) - if err != nil { - t.Fatal(err) - } - - return base64.StdEncoding.EncodeToString(sctBytes) -} - -func embedSCT(t *testing.T, privateKey *rsa.PrivateKey, skid []byte, preCert *x509.Certificate, sctInput ct.SignedCertificateTimestamp) *x509.Certificate { - sct := getSCT(t, privateKey, preCert, sctInput, true) - sctList, err := ctx509util.MarshalSCTsIntoSCTList([]*ct.SignedCertificateTimestamp{&sct}) - if err != nil { - t.Fatal(err) - } - sctBytes, err := tls.Marshal(*sctList) - if err != nil { - t.Fatal(err) - } - asnSCT, err := asn1.Marshal(sctBytes) - if err != nil { - t.Fatal(err) - } - cert := &x509.Certificate{ - SerialNumber: preCert.SerialNumber, - SubjectKeyId: skid, - ExtraExtensions: []pkix.Extension{ - { - Id: asn1.ObjectIdentifier(ctx509.OIDExtensionCTSCT), - Value: asnSCT, - }, - }, - } - certDERBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &privateKey.PublicKey, privateKey) - if err != nil { - t.Fatal(err) - } - parsedCert, err := x509.ParseCertificate(certDERBytes) - if err != nil { - t.Fatal(err) - } - return parsedCert -} - -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 map[string][]byte) error { - expiration := time.Now().AddDate(0, 0, 1).UTC() - targets := metadata.Targets(expiration) - targetsDir := filepath.Join(td, "targets") - err := os.Mkdir(targetsDir, 0700) - if err != nil { - return err - } - for name, content := range targetList { - targetPath := filepath.Join(targetsDir, name) - err := os.WriteFile(targetPath, content, 0600) - if err != nil { - return err - } - targetFileInfo, err := metadata.TargetFile().FromFile(targetPath, "sha256") - if err != nil { - return err - } - targets.Signed.Targets[name] = targetFileInfo - } - snapshot := metadata.Snapshot(expiration) - timestamp := metadata.Timestamp(expiration) - root := metadata.Root(expiration) - root.Signed.ConsistentSnapshot = false - public, signer, err := newKey() - if err != nil { - return err - } - for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { - err := root.Signed.AddKey(public, name) - if err != nil { - return err - } - switch name { - case "targets": - _, err = targets.Sign(signer) - case "snapshot": - _, err = snapshot.Sign(signer) - case "timestamp": - _, err = timestamp.Sign(signer) - case "root": - _, err = root.Sign(signer) - } - if err != nil { - return err - } - } - err = targets.ToFile(filepath.Join(td, "targets.json"), false) - if err != nil { - return err - } - err = snapshot.ToFile(filepath.Join(td, "snapshot.json"), false) - if err != nil { - return err - } - err = timestamp.ToFile(filepath.Join(td, "timestamp.json"), false) - if err != nil { - return err - } - err = root.ToFile(filepath.Join(td, "1.root.json"), false) - if err != nil { - return err - } - err = root.VerifyDelegate("root", root) - if err != nil { - return err - } - err = root.VerifyDelegate("targets", targets) - if err != nil { - return err - } - err = root.VerifyDelegate("snapshot", snapshot) - if err != nil { - return err - } - err = root.VerifyDelegate("timestamp", timestamp) - return err -} - -func createBaseCert(t *testing.T, privateKey *rsa.PrivateKey, skid []byte, serialNumber *big.Int) *x509.Certificate { - cert := &x509.Certificate{ - SerialNumber: serialNumber, - SubjectKeyId: skid, - } - certDERBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &privateKey.PublicKey, privateKey) - if err != nil { - t.Fatal(err) - } - parsedCert, err := x509.ParseCertificate(certDERBytes) - if err != nil { - t.Fatal(err) - } - return parsedCert -} - -type fakeTrustedMaterial struct { - transparencyLog map[string]*root.TransparencyLog - cas []root.CertificateAuthority -} - -func (t *fakeTrustedMaterial) CTLogs() map[string]*root.TransparencyLog { - return t.transparencyLog -} - -func (t *fakeTrustedMaterial) FulcioCertificateAuthorities() []root.CertificateAuthority { - return t.cas -} - -func (t *fakeTrustedMaterial) TimestampingAuthorities() []root.TimestampingAuthority { - panic("not implemented") -} -func (t *fakeTrustedMaterial) RekorLogs() map[string]*root.TransparencyLog { panic("not implemented") } -func (t *fakeTrustedMaterial) PublicKeyVerifier(string) (root.TimeConstrainedVerifier, error) { - panic("not implemented") -} diff --git a/cmd/cosign/cli/generate.go b/cmd/cosign/cli/generate.go index 4560ce7693c..7ab4c2b5e4c 100644 --- a/cmd/cosign/cli/generate.go +++ b/cmd/cosign/cli/generate.go @@ -16,8 +16,8 @@ package cli import ( - "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" "github.com/spf13/cobra" ) @@ -26,10 +26,11 @@ func Generate() *cobra.Command { cmd := &cobra.Command{ Use: "generate", - Short: "Generates (unsigned) signature payloads from the supplied container image.", + 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.`, + Deprecated: "generate will be removed in v4.0.0 (see https://github.com/sigstore/cosign/issues/4696). Instead, please use in-toto tooling", Example: ` cosign generate [--a key=value] # Generate a simple payload for an image diff --git a/cmd/cosign/cli/generate/generate.go b/cmd/cosign/cli/generate/generate.go index ee2e6b459a5..0e29d82f47f 100644 --- a/cmd/cosign/cli/generate/generate.go +++ b/cmd/cosign/cli/generate/generate.go @@ -20,8 +20,8 @@ import ( "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/cosign/v3/cmd/cosign/cli/options" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/sigstore/sigstore/pkg/signature/payload" ) @@ -48,6 +48,6 @@ func GenerateCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef if err != nil { return err } - w.Write(json) - return nil + _, err = w.Write(json) + return err } diff --git a/cmd/cosign/cli/generate/generate_key_pair.go b/cmd/cosign/cli/generate/generate_key_pair.go index 2329f51b819..3e729eb9aec 100644 --- a/cmd/cosign/cli/generate/generate_key_pair.go +++ b/cmd/cosign/cli/generate/generate_key_pair.go @@ -24,15 +24,15 @@ import ( "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/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/git" + "github.com/sigstore/cosign/v3/pkg/cosign/git/github" + "github.com/sigstore/cosign/v3/pkg/cosign/git/gitlab" + + icos "github.com/sigstore/cosign/v3/internal/pkg/cosign" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/kubernetes" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature/kms" ) diff --git a/cmd/cosign/cli/generate/generate_key_pair_test.go b/cmd/cosign/cli/generate/generate_key_pair_test.go index f860382ea4e..13ef47a5f74 100644 --- a/cmd/cosign/cli/generate/generate_key_pair_test.go +++ b/cmd/cosign/cli/generate/generate_key_pair_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" - icos "github.com/sigstore/cosign/v2/internal/pkg/cosign" + icos "github.com/sigstore/cosign/v3/internal/pkg/cosign" ) func TestReadPasswordFn_env(t *testing.T) { diff --git a/cmd/cosign/cli/generate_key_pair.go b/cmd/cosign/cli/generate_key_pair.go index 9e64f7b5981..00725d4b334 100644 --- a/cmd/cosign/cli/generate_key_pair.go +++ b/cmd/cosign/cli/generate_key_pair.go @@ -16,8 +16,8 @@ package cli import ( - "github.com/sigstore/cosign/v2/cmd/cosign/cli/generate" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" "github.com/spf13/cobra" ) @@ -26,7 +26,7 @@ func GenerateKeyPair() *cobra.Command { cmd := &cobra.Command{ Use: "generate-key-pair", - Short: "Generates a key-pair.", + Short: "Generates a key-pair", Long: "Generates a key-pair for signing.", Example: ` cosign generate-key-pair [--kms KMSPATH] @@ -60,6 +60,12 @@ func GenerateKeyPair() *cobra.Command { # generate a key-pair in GitLab with project id cosign generate-key-pair gitlab://[PROJECT_ID] + # generate a key-pair in GitLab with group name (accessible to all projects in the group) + cosign generate-key-pair gitlab://[GROUP_NAME] + + # generate a key-pair in GitLab with subgroup name + cosign generate-key-pair gitlab://[GROUP_NAME]/[SUBGROUP_NAME] + CAVEATS: This command interactively prompts for a password. You can use the COSIGN_PASSWORD environment variable to provide one.`, diff --git a/cmd/cosign/cli/import_key_pair.go b/cmd/cosign/cli/import_key_pair.go index 3ba62aa68ec..3fb7ab0b207 100644 --- a/cmd/cosign/cli/import_key_pair.go +++ b/cmd/cosign/cli/import_key_pair.go @@ -18,8 +18,8 @@ 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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/importkeypair" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" ) func ImportKeyPair() *cobra.Command { @@ -27,7 +27,7 @@ func ImportKeyPair() *cobra.Command { cmd := &cobra.Command{ Use: "import-key-pair", - Short: "Imports a PEM-encoded RSA or EC private key.", + 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 diff --git a/cmd/cosign/cli/importkeypair/import_key_pair.go b/cmd/cosign/cli/importkeypair/import_key_pair.go index b2d03628e62..3079648185f 100644 --- a/cmd/cosign/cli/importkeypair/import_key_pair.go +++ b/cmd/cosign/cli/importkeypair/import_key_pair.go @@ -21,11 +21,11 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + icos "github.com/sigstore/cosign/v3/internal/pkg/cosign" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) var ( diff --git a/cmd/cosign/cli/importkeypair/import_key_pair_test.go b/cmd/cosign/cli/importkeypair/import_key_pair_test.go index bb1519cf17e..82e8e1e8119 100644 --- a/cmd/cosign/cli/importkeypair/import_key_pair_test.go +++ b/cmd/cosign/cli/importkeypair/import_key_pair_test.go @@ -25,8 +25,8 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + icos "github.com/sigstore/cosign/v3/internal/pkg/cosign" ) func TestReadPasswordFn_env(t *testing.T) { diff --git a/cmd/cosign/cli/initialize.go b/cmd/cosign/cli/initialize.go index 59b537147f8..fd7512e587a 100644 --- a/cmd/cosign/cli/initialize.go +++ b/cmd/cosign/cli/initialize.go @@ -16,8 +16,8 @@ package cli import ( - "github.com/sigstore/cosign/v2/cmd/cosign/cli/initialize" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/initialize" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" "github.com/spf13/cobra" ) @@ -26,7 +26,7 @@ func Initialize() *cobra.Command { cmd := &cobra.Command{ Use: "initialize", - Short: "Initializes SigStore root to retrieve trusted certificate and key targets for verification.", + 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: @@ -39,12 +39,15 @@ 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 +with Fulcio root CA) are pulled from the trusted metadata.`, + Example: `cosign initialize --mirror -# initialize root with distributed root keys, default mirror, and default out path. +# initialize root with distributed root keys, using the default mirror. cosign initialize +# initialize root with distributed root keys, using the staging mirror. +cosign initialize --staging + # initialize with an out-of-band root key file, using the default mirror. cosign initialize --root @@ -55,6 +58,9 @@ cosign initialize --mirror --root cosign initialize --mirror --root --root-checksum `, PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, _ []string) error { + if o.Staging { + return initialize.DoInitializeStaging(cmd.Context()) + } return initialize.DoInitializeWithRootChecksum(cmd.Context(), o.Root, o.Mirror, o.RootChecksum) }, } diff --git a/cmd/cosign/cli/initialize/init.go b/cmd/cosign/cli/initialize/init.go index fc726cd0d59..c8974a82203 100644 --- a/cmd/cosign/cli/initialize/init.go +++ b/cmd/cosign/cli/initialize/init.go @@ -17,17 +17,16 @@ package initialize import ( "context" - _ "embed" // To enable the `go:embed` directive. "encoding/json" "fmt" "os" "path/filepath" "strings" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/internal/ui" - "github.com/sigstore/cosign/v2/pkg/blob" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/blob" + "github.com/sigstore/cosign/v3/pkg/cosign/env" tufroot "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/tuf" tufv1 "github.com/sigstore/sigstore/pkg/tuf" @@ -41,6 +40,10 @@ func DoInitializeWithRootChecksum(ctx context.Context, root, mirror, rootChecksu return doInitialize(ctx, root, mirror, rootChecksum, false) } +func DoInitializeStaging(ctx context.Context) error { + return doInitialize(ctx, "", tuf.StagingMirror, "", true) +} + func doInitialize(ctx context.Context, root, mirror, rootChecksum string, forceSkipChecksumValidation bool) error { // Get the initial trusted root contents. var rootFileBytes []byte @@ -68,6 +71,9 @@ func doInitialize(ctx context.Context, root, mirror, rootChecksum string, forceS } if mirror != "" { opts.RepositoryBaseURL = mirror + if mirror == tuf.StagingMirror { + opts.Root = tuf.StagingRoot() + } } if tufCacheDir := env.Getenv(env.VariableTUFRootDir); tufCacheDir != "" { //nolint:forbidigo opts.CachePath = tufCacheDir @@ -79,6 +85,9 @@ func doInitialize(ctx context.Context, root, mirror, rootChecksum string, forceS if err != nil { return err } + if err := os.RemoveAll(opts.CachePath); err != nil { + return fmt.Errorf("clearing cache directory: %w", err) + } if err := os.MkdirAll(opts.CachePath, 0o700); err != nil { return fmt.Errorf("creating cache directory: %w", err) } @@ -86,6 +95,12 @@ func doInitialize(ctx context.Context, root, mirror, rootChecksum string, forceS return fmt.Errorf("storing remote: %w", err) } + // Cache the signing config from the TUF repository + _, err = tufroot.FetchSigningConfigWithOptions(opts) + if err != nil { + ui.Warnf(ctx, "Could not fetch signing_config.json from the TUF mirror (encountered error: %v). It is recommended to use a signing config file rather than provide service URLs when signing.", err) + } + // Cache the trusted root from the TUF repository trustedRoot, err := tufroot.NewLiveTrustedRoot(opts) if err != nil { ui.Warnf(ctx, "Could not fetch trusted_root.json from the TUF mirror (encountered error: %v), falling back to individual targets. It is recommended to update your TUF metadata repository to include trusted_root.json.", err) diff --git a/cmd/cosign/cli/initialize/init_test.go b/cmd/cosign/cli/initialize/init_test.go index 0586a93e5d8..e2932a329ca 100644 --- a/cmd/cosign/cli/initialize/init_test.go +++ b/cmd/cosign/cli/initialize/init_test.go @@ -155,13 +155,16 @@ func TestDoInitialize(t *testing.T) { expectV2 bool }{ { - name: "tuf v2 with trusted root", - targets: map[string][]byte{"trusted_root.json": []byte(`{"mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1"}`)}, + name: "tuf v2 with trusted root and signing config", + targets: map[string][]byte{ + "trusted_root.json": []byte(`{"mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1"}`), + "signing_config.v0.2.json": []byte(`{"mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json"}`), + }, root: "1.root.json", wantStdOut: "", wantStdErr: "", wantErr: false, - wantFiles: []string{filepath.Join("targets", "trusted_root.json")}, + wantFiles: []string{filepath.Join("targets", "trusted_root.json"), filepath.Join("targets", "signing_config.v0.2.json")}, expectV2: true, }, { diff --git a/cmd/cosign/cli/load.go b/cmd/cosign/cli/load.go index 80eb9ec7d0a..e08ba0c59f3 100644 --- a/cmd/cosign/cli/load.go +++ b/cmd/cosign/cli/load.go @@ -20,9 +20,9 @@ import ( "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/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/oci/layout" + "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/spf13/cobra" ) @@ -46,7 +46,7 @@ func Load() *cobra.Command { } func LoadCmd(ctx context.Context, opts options.LoadOptions, imageRef string) error { - ref, err := name.ParseReference(imageRef) + ref, err := name.ParseReference(imageRef, opts.Registry.NameOptions()...) if err != nil { return fmt.Errorf("parsing image name %s: %w", imageRef, err) } @@ -62,5 +62,5 @@ func LoadCmd(ctx context.Context, opts options.LoadOptions, imageRef string) err return err } - return remote.WriteSignedImageIndexImages(ref, sii, ociremoteOpts...) + return remote.WriteSignedImageIndexImages(ref, sii, opts.Directory, ociremoteOpts...) } diff --git a/cmd/cosign/cli/load_test.go b/cmd/cosign/cli/load_test.go new file mode 100644 index 00000000000..f73b8faef2a --- /dev/null +++ b/cmd/cosign/cli/load_test.go @@ -0,0 +1,75 @@ +// 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" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/google/go-containerregistry/pkg/registry" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" +) + +// TestLoadCmdAllowHTTPRegistry verifies that LoadCmd respects the +// --allow-http-registry flag and connects to plain-HTTP registries +// without upgrading to HTTPS. +// +// Regression test for https://github.com/sigstore/cosign/issues/4134. +func TestLoadCmdAllowHTTPRegistry(t *testing.T) { + // Start an in-process HTTP registry. + reg := registry.New() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + reg.ServeHTTP(w, r) + })) + defer srv.Close() + + // Strip the leading "http://" — the registry address is host:port. + addr := strings.TrimPrefix(srv.URL, "http://") + imageRef := addr + "/test/image:latest" + + t.Run("AllowHTTPRegistry=true reaches the HTTP registry", func(t *testing.T) { + opts := options.LoadOptions{ + Directory: t.TempDir(), + Registry: options.RegistryOptions{ + AllowHTTPRegistry: true, + }, + } + // We expect a failure about the directory content (empty image + // index), not about TLS — proving that the HTTP connection was + // attempted rather than being rejected at the name-parsing level. + err := LoadCmd(context.Background(), opts, imageRef) + if err != nil && strings.Contains(err.Error(), "http: server gave HTTP response to HTTPS client") { + t.Errorf("LoadCmd with AllowHTTPRegistry=true still attempted TLS: %v", err) + } + }) + + t.Run("AllowHTTPRegistry=false fails to connect to HTTP-only registry", func(t *testing.T) { + opts := options.LoadOptions{ + Directory: t.TempDir(), + Registry: options.RegistryOptions{ + AllowHTTPRegistry: false, + }, + } + // Without the flag the connection to a plain-HTTP server should + // fail with a TLS error (or similar transport error), not succeed. + err := LoadCmd(context.Background(), opts, imageRef) + if err == nil { + t.Error("LoadCmd without AllowHTTPRegistry unexpectedly succeeded against an HTTP-only registry") + } + }) +} diff --git a/cmd/cosign/cli/manifest.go b/cmd/cosign/cli/manifest.go index 02bf8881f3c..9a852ee3ac8 100644 --- a/cmd/cosign/cli/manifest.go +++ b/cmd/cosign/cli/manifest.go @@ -18,16 +18,17 @@ package cli import ( "fmt" - "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/verify" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/manifest" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" "github.com/spf13/cobra" ) func Manifest() *cobra.Command { cmd := &cobra.Command{ - Use: "manifest", - Short: "Provides utilities for discovering images in and performing operations on Kubernetes manifests", + Use: "manifest", + Short: "Provides utilities for discovering images in and performing operations on Kubernetes manifests", + Deprecated: "manifest will be removed in v4.0.0 (see https://github.com/sigstore/cosign/issues/4696). Instead, please use `cosign verify` for each image in your Kubernetes manifest", } cmd.AddCommand( diff --git a/cmd/cosign/cli/manifest/verify.go b/cmd/cosign/cli/manifest/verify.go index 9f179b2d8d2..dac0afba31a 100644 --- a/cmd/cosign/cli/manifest/verify.go +++ b/cmd/cosign/cli/manifest/verify.go @@ -27,7 +27,7 @@ import ( "k8s.io/apimachinery/pkg/util/yaml" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" ) // VerifyManifestCommand verifies all image signatures on a supplied k8s resource diff --git a/cmd/cosign/cli/options/annotations.go b/cmd/cosign/cli/options/annotations.go index 5da30a3c910..601e2af657a 100644 --- a/cmd/cosign/cli/options/annotations.go +++ b/cmd/cosign/cli/options/annotations.go @@ -19,7 +19,7 @@ import ( "fmt" "strings" - sigs "github.com/sigstore/cosign/v2/pkg/signature" + sigs "github.com/sigstore/cosign/v3/pkg/signature" "github.com/spf13/cobra" ) diff --git a/cmd/cosign/cli/options/annotations_test.go b/cmd/cosign/cli/options/annotations_test.go index c575a397358..01a5faba8b7 100644 --- a/cmd/cosign/cli/options/annotations_test.go +++ b/cmd/cosign/cli/options/annotations_test.go @@ -20,7 +20,7 @@ import ( "github.com/google/go-cmp/cmp" - "github.com/sigstore/cosign/v2/pkg/signature" + "github.com/sigstore/cosign/v3/pkg/signature" ) func TestAnnotationOptions_AnnotationsMap(t *testing.T) { diff --git a/cmd/cosign/cli/options/attach.go b/cmd/cosign/cli/options/attach.go index 4e51d527f08..ee55697fc88 100644 --- a/cmd/cosign/cli/options/attach.go +++ b/cmd/cosign/cli/options/attach.go @@ -20,7 +20,7 @@ import ( "strings" "github.com/google/go-containerregistry/pkg/v1/types" - ctypes "github.com/sigstore/cosign/v2/pkg/types" + ctypes "github.com/sigstore/cosign/v3/pkg/types" "github.com/spf13/cobra" ) @@ -47,6 +47,9 @@ func (o *AttachSignatureOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Payload, "payload", "", "path to the payload covered by the signature") + cmd.Flags().StringVar(&o.Payload, "bundle", "", + "path to bundle containing signature (alias for payload)") + cmd.Flags().StringVar(&o.Cert, "certificate", "", "path to the X.509 certificate in PEM format to include in the OCI Signature") diff --git a/cmd/cosign/cli/options/attest.go b/cmd/cosign/cli/options/attest.go index 7b67e4ee563..6e5841ce625 100644 --- a/cmd/cosign/cli/options/attest.go +++ b/cmd/cosign/cli/options/attest.go @@ -26,6 +26,7 @@ type AttestOptions struct { Key string Cert string CertChain string + IssueCertificate bool NoUpload bool Replace bool SkipConfirmation bool @@ -37,7 +38,11 @@ type AttestOptions struct { TSAServerURL string RekorEntryType string RecordCreationTimestamp bool + BundlePath string NewBundleFormat bool + UseSigningConfig bool + SigningConfigPath string + TrustedRootPath string Rekor RekorOptions Fulcio FulcioOptions @@ -77,16 +82,19 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) { "do not upload the generated attestation, but send the attestation output to STDOUT") cmd.Flags().BoolVarP(&o.Replace, "replace", "", false, "") + _ = cmd.Flags().MarkDeprecated("replace", "not needed when OCI referrers become the default behavior") 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().MarkDeprecated("tlog-upload", "prefer using a --signing-config file with no transparency log services") 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().MarkDeprecated("rekor-entry-type", "support for this flag will be removed in the future. it is strongly discouraged to rely on Rekor for attestation storage, and in future releases of Rekor, this functionality will be removed.") 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") @@ -103,9 +111,31 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) { 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().MarkDeprecated("timestamp-server-url", "please use a signing config to specify a timestamp server url; see `cosign signing-config --help`") 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().MarkDeprecated("record-creation-timestamp", "not used with the new bundle format") - cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "attach a Sigstore bundle using OCI referrers API") + cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false, + "issue a code signing certificate from Fulcio, even if a key is provided") + _ = cmd.Flags().MarkDeprecated("issue-certificate", "support for this flag will be removed in the future") + + cmd.Flags().StringVar(&o.BundlePath, "bundle", "", + "write everything required to verify the blob to a FILE") + _ = cmd.MarkFlagFilename("bundle", bundleExts...) + + cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true, "attach a Sigstore bundle using OCI referrers API") + _ = cmd.Flags().MarkDeprecated("new-bundle-format", "this will be the only supported format in future versions") + + cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", true, + "whether to use a TUF-provided signing config for the service URLs") + + cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "", + "path to a signing config file") + + cmd.MarkFlagsMutuallyExclusive("use-signing-config", "signing-config") + + cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "", + "optional path to a TrustedRoot JSON file to verify a signature after signing") } diff --git a/cmd/cosign/cli/options/attest_blob.go b/cmd/cosign/cli/options/attest_blob.go index 5b1b8135694..ebdb5b55bab 100644 --- a/cmd/cosign/cli/options/attest_blob.go +++ b/cmd/cosign/cli/options/attest_blob.go @@ -22,9 +22,10 @@ import ( // AttestOptions is the top level wrapper for the attest command. type AttestBlobOptions struct { - Key string - Cert string - CertChain string + Key string + Cert string + CertChain string + IssueCertificate bool SkipConfirmation bool TlogUpload bool @@ -50,6 +51,10 @@ type AttestBlobOptions struct { Fulcio FulcioOptions OIDC OIDCOptions SecurityKey SecurityKeyOptions + + UseSigningConfig bool + SigningConfigPath string + TrustedRootPath string } var _ Interface = (*AttestOptions)(nil) @@ -67,19 +72,20 @@ func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) { _ = 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") + "path to the X.509 certificate for signing attestation") _ = 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. "+ + "when building the certificate chain for the signed attestation. "+ "Must start with the parent intermediate CA certificate of the "+ - "signing certificate and end with the root certificate. Included in the OCI Signature") + "signing certificate and end with the root certificate.") _ = cmd.MarkFlagFilename("certificate-chain", certificateExts...) cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", "write the signature to FILE") _ = cmd.MarkFlagFilename("output-signature", signatureExts...) + _ = cmd.Flags().MarkDeprecated("output-signature", "please use --bundle to provide the output bundle location, which will include the signature") cmd.Flags().StringVar(&o.OutputAttestation, "output-attestation", "", "write the attestation to FILE") @@ -88,14 +94,26 @@ func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", "write the certificate to FILE") _ = cmd.MarkFlagFilename("key", certificateExts...) + _ = cmd.Flags().MarkDeprecated("output-certificate", "please use --bundle to provide the output bundle location, which will include the certificate") 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, + cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true, "output bundle in new format that contains all verification material") + _ = cmd.Flags().MarkDeprecated("new-bundle-format", "this will be the only supported format in future versions") + + cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", true, + "whether to use a TUF-provided signing config for the service URLs. Must provide --bundle, which will output verification material in the new format") + + cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "", + "path to a signing config file. Must provide --bundle, which will output verification material in the new format") + + cmd.MarkFlagsMutuallyExclusive("use-signing-config", "signing-config") + + cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "", + "optional path to a TrustedRoot JSON file to verify a signature after signing") 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") @@ -106,10 +124,12 @@ func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true, "whether or not to upload to the tlog") + _ = cmd.Flags().MarkDeprecated("tlog-upload", "prefer using a --signing-config file with no transparency log services") 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().MarkDeprecated("rekor-entry-type", "support for this flag will be removed in the future. it is strongly discouraged to rely on Rekor for attestation storage, and in future releases of Rekor, this functionality will be removed.") 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") @@ -126,8 +146,14 @@ func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) { 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().MarkDeprecated("timestamp-server-url", "please use a signing config to specify a timestamp server url; see `cosign signing-config --help`") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp-bundle", "", "path to an RFC 3161 timestamp bundle FILE") // _ = cmd.MarkFlagFilename("rfc3161-timestamp-bundle") // no typical extensions + _ = cmd.Flags().MarkDeprecated("rfc3161-timestamp-bundle", "please use --bundle to provide the output bundle location, which will include the signed timestamp") + + cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false, + "issue a code signing certificate from Fulcio, even if a key is provided") + _ = cmd.Flags().MarkDeprecated("issue-certificate", "support for this flag will be removed in the future") } diff --git a/cmd/cosign/cli/options/bundle.go b/cmd/cosign/cli/options/bundle.go index 8168b308c4c..fad9c1d075d 100644 --- a/cmd/cosign/cli/options/bundle.go +++ b/cmd/cosign/cli/options/bundle.go @@ -52,7 +52,7 @@ func (o *BundleCreateOptions) AddFlags(cmd *cobra.Command) { _ = cmd.MarkFlagFilename("bundle", bundleExts...) cmd.Flags().StringVar(&o.CertificatePath, "certificate", "", - "path to the signing certificate, likely from Fulco.") + "path to the signing certificate, likely from Fulcio.") _ = cmd.MarkFlagFilename("certificate", certificateExts...) cmd.Flags().BoolVar(&o.IgnoreTlog, "ignore-tlog", false, @@ -69,6 +69,7 @@ func (o *BundleCreateOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.RekorURL, "rekor-url", "https://rekor.sigstore.dev", "address of rekor STL server") _ = cmd.RegisterFlagCompletionFunc("rekor-url", cobra.NoFileCompletions) + _ = cmd.Flags().MarkDeprecated("rekor-url", "please use a signing config to specify a rekor url; see `cosign signing-config --help`") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "path to RFC3161 timestamp FILE") @@ -90,3 +91,18 @@ func (o *BundleCreateOptions) AddFlags(cmd *cobra.Command) { cmd.MarkFlagsMutuallyExclusive("bundle", "certificate") cmd.MarkFlagsMutuallyExclusive("bundle", "signature") } + +type BundleUpgradeOptions struct { + Out string + RekorURL string +} + +var _ Interface = (*BundleUpgradeOptions)(nil) + +func (o *BundleUpgradeOptions) AddFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&o.Out, "out", "", "path to the output upgraded bundle file") + _ = cmd.MarkFlagFilename("out", bundleExts...) + + cmd.Flags().StringVar(&o.RekorURL, "rekor-url", "https://rekor.sigstore.dev", "URL of the transparency log") + _ = cmd.RegisterFlagCompletionFunc("rekor-url", cobra.NoFileCompletions) +} diff --git a/cmd/cosign/cli/options/certificate.go b/cmd/cosign/cli/options/certificate.go index b14d408fe20..215ec0aa557 100644 --- a/cmd/cosign/cli/options/certificate.go +++ b/cmd/cosign/cli/options/certificate.go @@ -17,7 +17,7 @@ package options import ( "errors" - "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign" "github.com/spf13/cobra" ) @@ -47,6 +47,7 @@ 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().MarkDeprecated("certificate", "please use --bundle with --trusted-root to provide the public certificate") 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.") @@ -83,10 +84,12 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { "The flag is optional and must be used together with --ca-roots, conflicts with "+ "--certificate-chain.") _ = cmd.MarkFlagFilename("ca-intermediates", certificateExts...) + _ = cmd.Flags().MarkDeprecated("ca-intermediates", "please use --trusted-root to provide CA certificates") 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().MarkDeprecated("ca-roots", "please use --trusted-root to provide CA certificates") cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "", "path to a list of CA certificates in PEM format which will be needed "+ @@ -96,11 +99,14 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { _ = cmd.MarkFlagFilename("certificate-chain", certificateExts...) cmd.MarkFlagsMutuallyExclusive("ca-roots", "certificate-chain") cmd.MarkFlagsMutuallyExclusive("ca-intermediates", "certificate-chain") + _ = cmd.Flags().MarkDeprecated("certificate-chain", "please use --trusted-root to provide the 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().MarkDeprecated("sct", "a Signed Certificate Timestamp is now provided via a Sigstore bundle") + 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") diff --git a/cmd/cosign/cli/options/clean.go b/cmd/cosign/cli/options/clean.go index 540bc4f1cf5..9470f26566f 100644 --- a/cmd/cosign/cli/options/clean.go +++ b/cmd/cosign/cli/options/clean.go @@ -25,6 +25,7 @@ type CleanType string const ( CleanTypeSignature CleanType = "signature" CleanTypeAttestation CleanType = "attestation" + CleanTypeReferrer CleanType = "referrer" CleanTypeSbom CleanType = "sbom" CleanTypeAll CleanType = "all" ) @@ -41,11 +42,11 @@ func (c *CleanType) String() string { // cleanType implements github.com/spf13/pflag.Value. func (c *CleanType) Set(v string) error { switch v { - case "signature", "attestation", "sbom", "all": + case "signature", "attestation", "referrer", "sbom", "all": *c = CleanType(v) return nil default: - return errors.New(`must be one of "signature", "attestation", "sbom", or "all"`) + return errors.New(`must be one of "signature", "attestation", "referrer", "sbom", or "all"`) } } @@ -65,7 +66,7 @@ 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)") + 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") } diff --git a/cmd/cosign/cli/options/errors.go b/cmd/cosign/cli/options/errors.go index c3c440b35bb..5a5878adf7b 100644 --- a/cmd/cosign/cli/options/errors.go +++ b/cmd/cosign/cli/options/errors.go @@ -23,6 +23,10 @@ type KeyParseError struct{} // flags are parsed by the CLI type PubKeyParseError struct{} +// KeyAndIdentityParseError is an error returned when both +// key and identity flags are parsed by the CLI +type KeyAndIdentityParseError struct{} + func (e *KeyParseError) Error() string { return "exactly one of: key reference (--key), or hardware token (--sk) must be provided" } @@ -30,3 +34,10 @@ func (e *KeyParseError) Error() string { func (e *PubKeyParseError) Error() string { return "exactly one of: key reference (--key), certificate (--cert) or hardware token (--sk) must be provided" } + +func (e *KeyAndIdentityParseError) Error() string { + return "exactly one of: key reference (--key) or certificate identity " + + "(--certificate-identity or --certificate-identity-regexp), must be provided. " + + "To determine which to use, inspect the bundle's 'verificationMaterial' field: " + + "if 'publicKey' is present, use key reference; if 'certificate' or 'x509CertificateChain' is present, use certificate identity" +} diff --git a/cmd/cosign/cli/options/experimental.go b/cmd/cosign/cli/options/experimental.go index 5a982fee45e..fed423e0c1b 100644 --- a/cmd/cosign/cli/options/experimental.go +++ b/cmd/cosign/cli/options/experimental.go @@ -17,7 +17,7 @@ package options import ( "strconv" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) func EnableExperimental() bool { diff --git a/cmd/cosign/cli/options/files.go b/cmd/cosign/cli/options/files.go index 5d3d185f1ee..7ba77a0b860 100644 --- a/cmd/cosign/cli/options/files.go +++ b/cmd/cosign/cli/options/files.go @@ -19,7 +19,7 @@ import ( "fmt" "strings" - cremote "github.com/sigstore/cosign/v2/pkg/cosign/remote" + cremote "github.com/sigstore/cosign/v3/pkg/cosign/remote" "github.com/spf13/cobra" ) diff --git a/cmd/cosign/cli/options/fulcio.go b/cmd/cosign/cli/options/fulcio.go index e30d6b63813..fabdcb9f7f1 100644 --- a/cmd/cosign/cli/options/fulcio.go +++ b/cmd/cosign/cli/options/fulcio.go @@ -26,7 +26,7 @@ type FulcioOptions struct { URL string AuthFlow string IdentityToken string - InsecureSkipFulcioVerify bool + InsecureSkipFulcioVerify bool // Deprecated: SCT verification is no longer performed during signing/attestation. } var _ Interface = (*FulcioOptions)(nil) @@ -36,6 +36,7 @@ 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().MarkDeprecated("fulcio-url", "please use a signing config to specify a fulcio url; see `cosign signing-config --help`") 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.") @@ -46,4 +47,5 @@ func (o *FulcioOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.InsecureSkipFulcioVerify, "insecure-skip-verify", false, "skip verifying fulcio published to the SCT (this should only be used for testing).") + _ = cmd.Flags().MarkDeprecated("insecure-skip-verify", "SCT verification is no longer performed during signing/attestation.") } diff --git a/cmd/cosign/cli/options/initialize.go b/cmd/cosign/cli/options/initialize.go index d5eed5346a6..b13bf2ea45a 100644 --- a/cmd/cosign/cli/options/initialize.go +++ b/cmd/cosign/cli/options/initialize.go @@ -25,6 +25,7 @@ type InitializeOptions struct { Mirror string Root string RootChecksum string + Staging bool } var _ Interface = (*InitializeOptions)(nil) @@ -40,4 +41,7 @@ func (o *InitializeOptions) AddFlags(cmd *cobra.Command) { 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:") + + cmd.Flags().BoolVar(&o.Staging, "staging", false, + "use the staging TUF repository") } diff --git a/cmd/cosign/cli/options/key.go b/cmd/cosign/cli/options/key.go index 2cae8d5cdbe..175f2280269 100644 --- a/cmd/cosign/cli/options/key.go +++ b/cmd/cosign/cli/options/key.go @@ -16,8 +16,9 @@ package options import ( - "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign" "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore/pkg/signature" ) type KeyOpts struct { @@ -49,14 +50,31 @@ type KeyOpts struct { 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 + // Fulcio. See https://pkg.go.dev/github.com/sigstore/cosign/v3/cmd/cosign/cli/fulcio#pkg-constants // for valid values. FulcioAuthFlow string + // Deprecated: SCT verification is no longer performed during signing/attestation. // Modeled after InsecureSkipVerify in tls.Config, this disables // verifying the SCT. InsecureSkipFulcioVerify bool // TrustedMaterial contains trusted metadata for all Sigstore services. It is exclusive with RekorPubKeys, RootCerts, IntermediateCerts, CTLogPubKeys, and the TSA* cert fields. TrustedMaterial root.TrustedMaterial + + // SigningConfig contains the list of service URLs for Sigstore services. + SigningConfig *root.SigningConfig + + // DefaultLoadOptions may be set to control the behaviour of + // `LoadDefaultSigner/Verifier` family of functions. Some public/private key + // types have ambiguities with regards to the signing algorithm to use (e.g. + // RSA can be RSASSA-PSS or RSASSA-PKCS1v15). This is a way to control that. + // + // By default, Ed25519ph is used for ed25519 keys and RSA-PKCS1v15 is used + // for RSA keys. + DefaultLoadOptions *[]signature.LoadOption + + // SigningAlgorithm is the AlgorithmDetails string representation used to + // sign/hash the payload. + SigningAlgorithm string } diff --git a/cmd/cosign/cli/options/oidc.go b/cmd/cosign/cli/options/oidc.go index b62e2d14db2..cf13d39fd6b 100644 --- a/cmd/cosign/cli/options/oidc.go +++ b/cmd/cosign/cli/options/oidc.go @@ -58,6 +58,7 @@ var _ Interface = (*OIDCOptions)(nil) 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().MarkDeprecated("oidc-issuer", "please use a signing config to specify an OIDC issuer; see `cosign signing-config create --help`") cmd.Flags().StringVar(&o.ClientID, "oidc-client-id", "sigstore", "OIDC client ID for application") diff --git a/cmd/cosign/cli/options/pkcs11_tool.go b/cmd/cosign/cli/options/pkcs11_tool.go index c391e9de7b6..ce1523fc94b 100644 --- a/cmd/cosign/cli/options/pkcs11_tool.go +++ b/cmd/cosign/cli/options/pkcs11_tool.go @@ -16,7 +16,7 @@ package options import ( - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" "github.com/spf13/cobra" ) diff --git a/cmd/cosign/cli/options/predicate.go b/cmd/cosign/cli/options/predicate.go index e08325a376e..f33b347acfb 100644 --- a/cmd/cosign/cli/options/predicate.go +++ b/cmd/cosign/cli/options/predicate.go @@ -22,7 +22,7 @@ import ( "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" - "github.com/sigstore/cosign/v2/pkg/cosign/attestation" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" "github.com/spf13/cobra" ) diff --git a/cmd/cosign/cli/options/reference.go b/cmd/cosign/cli/options/reference.go index ed517bad8e1..0a72b307452 100644 --- a/cmd/cosign/cli/options/reference.go +++ b/cmd/cosign/cli/options/reference.go @@ -29,4 +29,5 @@ 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]`") + _ = cmd.Flags().MarkDeprecated("attachment-tag-prefix", "please use OCI referrers") } diff --git a/cmd/cosign/cli/options/registry.go b/cmd/cosign/cli/options/registry.go index 6840d532974..011854d91b0 100644 --- a/cmd/cosign/cli/options/registry.go +++ b/cmd/cosign/cli/options/registry.go @@ -32,7 +32,7 @@ import ( "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" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/spf13/cobra" ) diff --git a/cmd/cosign/cli/options/rekor.go b/cmd/cosign/cli/options/rekor.go index 724d6143e6b..e2a4d8c0b3c 100644 --- a/cmd/cosign/cli/options/rekor.go +++ b/cmd/cosign/cli/options/rekor.go @@ -32,4 +32,5 @@ var _ Interface = (*RekorOptions)(nil) func (o *RekorOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.URL, "rekor-url", DefaultRekorURL, "address of rekor STL server") + _ = cmd.Flags().MarkDeprecated("rekor-url", "please use a signing config to specify a rekor url; see `cosign signing-config --help`") } diff --git a/cmd/cosign/cli/options/sign.go b/cmd/cosign/cli/options/sign.go index bcacfd7e63f..3fdae27df28 100644 --- a/cmd/cosign/cli/options/sign.go +++ b/cmd/cosign/cli/options/sign.go @@ -29,6 +29,7 @@ type SignOptions struct { OutputSignature string // TODO: this should be the root output file arg. OutputPayload string OutputCertificate string + BundlePath string PayloadPath string Recursive bool Attachment string @@ -40,8 +41,12 @@ type SignOptions struct { TSAServerName string TSAServerURL string IssueCertificate bool - SignContainerIdentity string + SignContainerIdentities []string RecordCreationTimestamp bool + NewBundleFormat bool + UseSigningConfig bool + SigningConfigPath string + TrustedRootPath string Rekor RekorOptions Fulcio FulcioOptions @@ -85,13 +90,21 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", "write the signature to FILE") _ = cmd.MarkFlagFilename("output-signature", signatureExts...) + _ = cmd.Flags().MarkDeprecated("output-signature", "please use --bundle to provide the output bundle location, which will include the signature") + cmd.Flags().StringVar(&o.OutputPayload, "output-payload", "", "write the signed payload to FILE") // _ = cmd.MarkFlagFilename("output-payload") // no typical extensions + _ = cmd.Flags().MarkDeprecated("output-payload", "please use --bundle to provide the output bundle location, which will include the payload") cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", "write the certificate to FILE") _ = cmd.MarkFlagFilename("output-certificate", certificateExts...) + _ = cmd.Flags().MarkDeprecated("output-certificate", "please use --bundle to provide the output bundle location, which will include the certificate") + + cmd.Flags().StringVar(&o.BundlePath, "bundle", "", + "write everything required to verify the image to FILE") + _ = cmd.MarkFlagFilename("bundle", bundleExts...) cmd.Flags().StringVar(&o.PayloadPath, "payload", "", "path to a payload file to use rather than generating one") @@ -103,12 +116,14 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Attachment, "attachment", "", "DEPRECATED, related image attachment to sign (sbom), default none") _ = cmd.MarkFlagFilename("attachment", sbomExts...) + _ = cmd.Flags().MarkDeprecated("attachment", "please use OCI referrers for attachments; see `cosign attach sbom --registry-referrers-mode=oci-1-1 --help`") 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().MarkDeprecated("tlog-upload", "prefer using a --signing-config file with no transparency log services") 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") @@ -127,14 +142,32 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) { 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.Flags().MarkDeprecated("timestamp-server-url", "please use a signing config to specify a timestamp server url; see `cosign signing-config --help`") _ = 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().MarkDeprecated("issue-certificate", "support for this flag will be removed in the future") - 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().StringSliceVar(&o.SignContainerIdentities, "sign-container-identity", nil, + "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, this flag is comma delimited. ex: --sign-container-identity=identity1,identity2") + _ = cmd.Flags().MarkDeprecated("sign-container-identity", "not possible when OCI referrers become the default behavior") 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") + _ = cmd.Flags().MarkDeprecated("record-creation-timestamp", "not used with the new bundle format") + + cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true, "expect the signature/attestation to be packaged in a Sigstore bundle") + _ = cmd.Flags().MarkDeprecated("new-bundle-format", "this will be the only supported format in future versions") + + cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", true, + "whether to use a TUF-provided signing config for the service URLs") + + cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "", + "path to a signing config file") + + cmd.MarkFlagsMutuallyExclusive("use-signing-config", "signing-config") + + cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "", + "optional path to a TrustedRoot JSON file to verify a signature after signing") } diff --git a/cmd/cosign/cli/options/signature_digest.go b/cmd/cosign/cli/options/signature_digest.go index dda0ddd0ee9..8c27c9a53d1 100644 --- a/cmd/cosign/cli/options/signature_digest.go +++ b/cmd/cosign/cli/options/signature_digest.go @@ -59,6 +59,7 @@ func (o *SignatureDigestOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.AlgorithmName, "signature-digest-algorithm", "sha256", fmt.Sprintf("digest algorithm to use when processing a signature (%s)", validSignatureDigestAlgorithms)) + _ = cmd.Flags().MarkDeprecated("signature-digest-algorithm", "please use --bundle, which already includes the digest algorithm") } // HashAlgorithm converts the algorithm's name - provided as a string - into a crypto.Hash algorithm. diff --git a/cmd/cosign/cli/options/signblob.go b/cmd/cosign/cli/options/signblob.go index 5afff3c8079..d2bc53bc395 100644 --- a/cmd/cosign/cli/options/signblob.go +++ b/cmd/cosign/cli/options/signblob.go @@ -16,6 +16,12 @@ package options import ( + "fmt" + "strings" + + "github.com/sigstore/cosign/v3/pkg/cosign" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" "github.com/spf13/cobra" ) @@ -23,6 +29,8 @@ import ( // The new output-certificate flag is only in use when COSIGN_EXPERIMENTAL is enabled type SignBlobOptions struct { Key string + Cert string + CertChain 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. @@ -43,6 +51,11 @@ type SignBlobOptions struct { TSAServerURL string RFC3161TimestampPath string IssueCertificate bool + SigningAlgorithm string + + UseSigningConfig bool + SigningConfigPath string + TrustedRootPath string } var _ Interface = (*SignBlobOptions)(nil) @@ -58,34 +71,61 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) { "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 for signing attestation") + _ = 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 signed attestation. "+ + "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().BoolVar(&o.Base64Output, "b64", true, "whether to base64 encode the output") + _ = cmd.Flags().MarkDeprecated("b64", "please use --bundle, which already base64 encodes content as appropriate") cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "", "write the signature to FILE") _ = cmd.MarkFlagFilename("output-signature", signatureExts...) + _ = cmd.Flags().MarkDeprecated("output-signature", "please use --bundle to provide the output bundle location, which will include the signature") // 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().MarkDeprecated("output", "please use --bundle to provide the output bundle location, which will include the signature") cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "", "write the certificate to FILE") _ = cmd.MarkFlagFilename("output-certificate", certificateExts...) + _ = cmd.Flags().MarkDeprecated("output-certificate", "please use --bundle to provide the output bundle location, which will include the certificate") 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, + cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true, "output bundle in new format that contains all verification material") + _ = cmd.Flags().MarkDeprecated("new-bundle-format", "this will be the only supported format in future versions") + + cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", true, + "whether to use a TUF-provided signing config for the service URLs. Must provide --bundle, which will output verification material in the new format") + + cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "", + "path to a signing config file. Must provide --bundle, which will output verification material in the new format") + + cmd.MarkFlagsMutuallyExclusive("use-signing-config", "signing-config") + + cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "", + "optional path to a TrustedRoot JSON file to verify a signature after signing") 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().MarkDeprecated("tlog-upload", "prefer using a --signing-config file with no transparency log services") 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") @@ -106,11 +146,19 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) { 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().MarkDeprecated("timestamp-server-url", "please use a signing config to specify a timestamp server url; see `cosign signing-config --help`") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "write the RFC3161 timestamp to a file") // _ = cmd.MarkFlagFilename("rfc3161-timestamp") // no typical extensions + _ = cmd.Flags().MarkDeprecated("rfc3161-timestamp", "please use --bundle to provide the output bundle location, which will include the signed timestamp") cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false, "issue a code signing certificate from Fulcio, even if a key is provided") + _ = cmd.Flags().MarkDeprecated("issue-certificate", "support for this flag will be removed in the future") + + keyAlgorithmTypes := cosign.GetSupportedAlgorithms() + keyAlgorithmHelp := fmt.Sprintf("signing algorithm to use for signing/hashing (allowed %s)", strings.Join(keyAlgorithmTypes, ", ")) + defaultKeyFlag, _ := signature.FormatSignatureAlgorithmFlag(v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256) + cmd.Flags().StringVar(&o.SigningAlgorithm, "signing-algorithm", defaultKeyFlag, keyAlgorithmHelp) } diff --git a/cmd/cosign/cli/options/signingconfig.go b/cmd/cosign/cli/options/signingconfig.go index 6993c54f351..c779c45d668 100644 --- a/cmd/cosign/cli/options/signingconfig.go +++ b/cmd/cosign/cli/options/signingconfig.go @@ -26,6 +26,13 @@ type SigningConfigCreateOptions struct { TSAConfig string RekorConfig string Out string + + WithDefaultServices bool + NoDefaultFulcio bool + NoDefaultRekor bool + NoDefaultTSA bool + NoDefaultOIDC bool + RekorV2 bool } var _ Interface = (*SigningConfigCreateOptions)(nil) @@ -45,5 +52,12 @@ func (o *SigningConfigCreateOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.RekorConfig, "rekor-config", "", "rekor configuration. Required if --rekor is provided. One of: ANY, ALL, EXACT:") + cmd.Flags().BoolVar(&o.WithDefaultServices, "with-default-services", false, "use the Sigstore TUF root as default values to populate the signing config. Specifying the other service flags will override the default values.") + cmd.Flags().BoolVar(&o.RekorV2, "with-default-rekor-v2", false, "use the Sigstore TUF root, with the Rekor v2 service, as default values to populate the signing config. Specifying the other service flags will override the default values.") + cmd.Flags().BoolVar(&o.NoDefaultFulcio, "no-default-fulcio", false, "removes the default Fulcio URLs from the signing config.") + cmd.Flags().BoolVar(&o.NoDefaultRekor, "no-default-rekor", false, "removes the default Rekor URLs from the signing config.") + cmd.Flags().BoolVar(&o.NoDefaultOIDC, "no-default-oidc", false, "removes the default OIDC provider URLs from the signing config.") + cmd.Flags().BoolVar(&o.NoDefaultTSA, "no-default-tsa", false, "removes the default TSA URLs from the signing config.") + cmd.Flags().StringVar(&o.Out, "out", "", "path to output signing config") } diff --git a/cmd/cosign/cli/options/tree.go b/cmd/cosign/cli/options/tree.go index cbd55967749..5c2937af12e 100644 --- a/cmd/cosign/cli/options/tree.go +++ b/cmd/cosign/cli/options/tree.go @@ -29,6 +29,7 @@ func (c *TreeOptions) AddFlags(cmd *cobra.Command) { c.Registry.AddFlags(cmd) c.RegistryExperimental.AddFlags(cmd) - cmd.Flags().BoolVar(&c.ExperimentalOCI11, "experimental-oci11", false, - "set to true to enable experimental OCI 1.1 behaviour") + cmd.Flags().BoolVar(&c.ExperimentalOCI11, "experimental-oci11", true, + "set to false to ignore OCI 1.1 behavior") + _ = cmd.Flags().MarkDeprecated("experimental-oci11", "OCI referrers will be the default behavior in future versions") } diff --git a/cmd/cosign/cli/options/trustedroot.go b/cmd/cosign/cli/options/trustedroot.go index bc5e0d65971..59fbfa49dd4 100644 --- a/cmd/cosign/cli/options/trustedroot.go +++ b/cmd/cosign/cli/options/trustedroot.go @@ -25,6 +25,13 @@ type TrustedRootCreateOptions struct { TSA []string Rekor []string + WithDefaultServices bool + NoDefaultFulcio bool + NoDefaultCTFE bool + NoDefaultTSA bool + NoDefaultRekor bool + + // Deprecated flags CertChain []string FulcioURI []string CtfeKeyPath []string @@ -46,11 +53,16 @@ func (o *TrustedRootCreateOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringArrayVar(&o.Fulcio, "fulcio", nil, "fulcio service specification, as a comma-separated key-value list.\nRequired keys: url, certificate-chain (path to PEM-encoded certificate chain). Optional keys: start-time, end-time.") cmd.Flags().StringArrayVar(&o.CTFE, "ctfe", nil, - "ctfe service specification, as a comma-separated key-value list.\nRequired keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time.") + "ctfe service specification, as a comma-separated key-value list.\nRequired keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time, origin.") cmd.Flags().StringArrayVar(&o.TSA, "tsa", nil, "timestamping authority specification, as a comma-separated key-value list.\nRequired keys: url, certificate-chain (path to PEM-encoded certificate chain). Optional keys: start-time, end-time.") cmd.Flags().StringArrayVar(&o.Rekor, "rekor", nil, "rekor service specification, as a comma-separated key-value list.\nRequired keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time, origin.") + cmd.Flags().BoolVar(&o.WithDefaultServices, "with-default-services", false, "use the Sigstore TUF root as default values to populate the trusted root. Specifying the other service flags will override the default values.") + cmd.Flags().BoolVar(&o.NoDefaultFulcio, "no-default-fulcio", false, "removes the default Fulcio URLs from the trusted root.") + cmd.Flags().BoolVar(&o.NoDefaultCTFE, "no-default-ctfe", false, "removes the default CTFE URLs from the trusted root.") + cmd.Flags().BoolVar(&o.NoDefaultTSA, "no-default-tsa", false, "removes the default TSA URLs from the trusted root.") + cmd.Flags().BoolVar(&o.NoDefaultRekor, "no-default-rekor", false, "removes the default Rekor URLs from the trusted root.") cmd.Flags().StringArrayVar(&o.CertChain, "certificate-chain", nil, "path to a list of CA certificates in PEM format which will be needed "+ diff --git a/cmd/cosign/cli/options/verify.go b/cmd/cosign/cli/options/verify.go index cb7ac30401a..9c560e9c7b8 100644 --- a/cmd/cosign/cli/options/verify.go +++ b/cmd/cosign/cli/options/verify.go @@ -18,7 +18,7 @@ package options import ( "github.com/spf13/cobra" - "github.com/sigstore/cosign/v2/internal/pkg/cosign" + "github.com/sigstore/cosign/v3/internal/pkg/cosign" ) type CommonVerifyOptions struct { @@ -37,11 +37,13 @@ type CommonVerifyOptions struct { func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.Offline, "offline", false, - "only allow offline verification") + "only verify an artifact's inclusion in a transparency log using a provided proof, rather than querying the log. May still include network requests to retrieve service keys from a TUF repository") + _ = cmd.Flags().MarkDeprecated("offline", "To verify in an airgapped environment, provide a --bundle with the signature and verification material, and a --trusted-root file with the service keys and certificates") 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().MarkDeprecated("timestamp-certificate-chain", "please use --trusted-root to provide the timestamp authority certificate chain") cmd.Flags().BoolVar(&o.UseSignedTimestamps, "use-signed-timestamps", false, "verify rfc3161 timestamps") @@ -52,19 +54,21 @@ func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&o.PrivateInfrastructure, "private-infrastructure", false, "skip transparency log verification when verifying artifacts in a privately deployed infrastructure") + _ = cmd.Flags().MarkDeprecated("private-infrastructure", "please use --insecure-ignore-tlog instead") cmd.Flags().BoolVar(&o.ExperimentalOCI11, "experimental-oci11", false, - "set to true to enable experimental OCI 1.1 behaviour") + "set to true to enable experimental OCI 1.1 behaviour (unrelated to bundle format)") + _ = cmd.Flags().MarkDeprecated("experimental-oci11", "OCI referrers will be the default behavior in future versions") 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.") + "Path to a Sigstore TrustedRoot JSON file") - // TODO: have this default to true as a breaking change - cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, + cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true, "expect the signature/attestation to be packaged in a Sigstore bundle") + _ = cmd.Flags().MarkDeprecated("new-bundle-format", "this will be the only supported format in future versions") } // VerifyOptions is the top level wrapper for the `verify` command. @@ -99,6 +103,8 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) { o.AnnotationOptions.AddFlags(cmd) o.CommonVerifyOptions.AddFlags(cmd) + _ = cmd.Flags().MarkDeprecated("rekor-url", "please use --bundle, which includes the Rekor inclusion proof") + cmd.Flags().StringVar(&o.Key, "key", "", "path to the public key file, KMS URI or Kubernetes Secret") _ = cmd.MarkFlagFilename("key", publicKeyExts...) @@ -109,6 +115,7 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Attachment, "attachment", "", "DEPRECATED, related image attachment to verify (sbom), default none") _ = cmd.MarkFlagFilename("attachment", sbomExts...) + _ = cmd.Flags().MarkDeprecated("attachment", "please use OCI referrers for attachments and verify with `--experimental-oci11`") cmd.Flags().StringVarP(&o.Output, "output", "o", "json", "output format for the signing image information (json|text)") @@ -116,6 +123,7 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.SignatureRef, "signature", "", "signature content or path or remote URL") _ = cmd.MarkFlagFilename("signature", signatureExts...) + _ = cmd.Flags().MarkDeprecated("signature", "signatures are automatically fetched from the OCI registry during image verification") cmd.Flags().StringVar(&o.PayloadRef, "payload", "", "payload path or remote URL") @@ -137,6 +145,7 @@ type VerifyAttestationOptions struct { CertVerify CertVerifyOptions Registry RegistryOptions Predicate PredicateRemoteOptions + SignatureDigest SignatureDigestOptions Policies []string LocalImage bool } @@ -151,6 +160,9 @@ func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) { o.Registry.AddFlags(cmd) o.Predicate.AddFlags(cmd) o.CommonVerifyOptions.AddFlags(cmd) + o.SignatureDigest.AddFlags(cmd) + + _ = cmd.Flags().MarkDeprecated("rekor-url", "please use --bundle, which includes the Rekor inclusion proof") cmd.Flags().StringVar(&o.Key, "key", "", "path to the public key file, KMS URI or Kubernetes Secret") @@ -178,6 +190,7 @@ type VerifyBlobOptions struct { CertVerify CertVerifyOptions Rekor RekorOptions CommonVerifyOptions CommonVerifyOptions + SignatureDigest SignatureDigestOptions RFC3161TimestampPath string } @@ -190,18 +203,23 @@ func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) { o.Rekor.AddFlags(cmd) o.CertVerify.AddFlags(cmd) o.CommonVerifyOptions.AddFlags(cmd) + o.SignatureDigest.AddFlags(cmd) + + _ = cmd.Flags().MarkDeprecated("rekor-url", "please use --bundle, which includes the Rekor inclusion proof") 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().MarkDeprecated("signature", "please use --bundle to provide a signature") cmd.Flags().StringVar(&o.BundlePath, "bundle", "", "path to bundle FILE") cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "path to RFC3161 timestamp FILE") + _ = cmd.Flags().MarkDeprecated("rfc3161-timestamp", "please use --bundle to provide the output bundle location, which will include the signed timestamp") } // VerifyDockerfileOptions is the top level wrapper for the `dockerfile verify` command. @@ -233,6 +251,7 @@ type VerifyBlobAttestationOptions struct { CertVerify CertVerifyOptions Rekor RekorOptions CommonVerifyOptions CommonVerifyOptions + SignatureDigest SignatureDigestOptions RFC3161TimestampPath string @@ -249,12 +268,16 @@ func (o *VerifyBlobAttestationOptions) AddFlags(cmd *cobra.Command) { o.Rekor.AddFlags(cmd) o.CertVerify.AddFlags(cmd) o.CommonVerifyOptions.AddFlags(cmd) + o.SignatureDigest.AddFlags(cmd) + + _ = cmd.Flags().MarkDeprecated("rekor-url", "please use --bundle, which includes the Rekor inclusion proof") 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().MarkDeprecated("signature", "please use --bundle to provide a signature") cmd.Flags().StringVar(&o.BundlePath, "bundle", "", "path to bundle FILE") @@ -264,6 +287,7 @@ func (o *VerifyBlobAttestationOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "", "path to RFC3161 timestamp FILE") + _ = cmd.Flags().MarkDeprecated("rfc3161-timestamp", "please use --bundle to provide the output bundle location, which will include the signed timestamp") cmd.Flags().StringVar(&o.Digest, "digest", "", "Digest to use for verifying in-toto subject (instead of providing a blob)") diff --git a/cmd/cosign/cli/piv_tool.go b/cmd/cosign/cli/piv_tool.go index 855b1a8dead..c30719acfd5 100644 --- a/cmd/cosign/cli/piv_tool.go +++ b/cmd/cosign/cli/piv_tool.go @@ -20,8 +20,8 @@ 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/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/pivcli" "github.com/spf13/cobra" ) diff --git a/cmd/cosign/cli/pivcli/commands.go b/cmd/cosign/cli/pivcli/commands.go index 2f532eaaf79..a40771bbee9 100644 --- a/cmd/cosign/cli/pivcli/commands.go +++ b/cmd/cosign/cli/pivcli/commands.go @@ -32,7 +32,7 @@ import ( "github.com/go-piv/piv-go/v2/piv" "github.com/manifoldco/promptui" - "github.com/sigstore/cosign/v2/pkg/cosign/pivkey" + "github.com/sigstore/cosign/v3/pkg/cosign/pivkey" ) func SetManagementKeyCmd(_ context.Context, oldKey, newKey string, randomKey bool) error { diff --git a/cmd/cosign/cli/pkcs11_tool.go b/cmd/cosign/cli/pkcs11_tool.go index bc8e80a4bdc..004e0521360 100644 --- a/cmd/cosign/cli/pkcs11_tool.go +++ b/cmd/cosign/cli/pkcs11_tool.go @@ -18,8 +18,8 @@ package cli import ( - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/pkcs11cli" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/pkcs11cli" "github.com/spf13/cobra" ) @@ -49,8 +49,10 @@ func pkcs11ToolListTokens() *cobra.Command { cmd := &cobra.Command{ Use: "list-tokens", - Short: "list-tokens lists all PKCS11 tokens linked to a PKCS11 module", - Args: cobra.ExactArgs(0), + Short: "List all PKCS11 tokens linked to a module", + Example: ` # list all tokens for a PKCS11 module + cosign pkcs11-tool list-tokens --module-path /usr/lib/libp11kit.so`, + Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return pkcs11cli.ListTokensCmd(cmd.Context(), o.ModulePath) }, @@ -66,8 +68,10 @@ func PKCS11ToolListKeysUrisOptions() *cobra.Command { cmd := &cobra.Command{ Use: "list-keys-uris", - Short: "list-keys-uris lists URIs of all keys in a PKCS11 token", - Args: cobra.ExactArgs(0), + Short: "List URIs of all keys in a PKCS11 token", + Example: ` # list key URIs in a specific PKCS11 token slot + cosign pkcs11-tool list-keys-uris --module-path /usr/lib/libp11kit.so --slot-id 0`, + Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { return pkcs11cli.ListKeysUrisCmd(cmd.Context(), o.ModulePath, o.SlotID, o.Pin) }, diff --git a/cmd/cosign/cli/pkcs11cli/commands.go b/cmd/cosign/cli/pkcs11cli/commands.go index 6d4609f5161..fdcc895d636 100644 --- a/cmd/cosign/cli/pkcs11cli/commands.go +++ b/cmd/cosign/cli/pkcs11cli/commands.go @@ -28,8 +28,8 @@ import ( "syscall" "github.com/miekg/pkcs11" - "github.com/sigstore/cosign/v2/pkg/cosign/env" - "github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key" + "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key" "golang.org/x/term" ) @@ -98,6 +98,13 @@ func GetKeysInfo(_ context.Context, modulePath string, slotID uint, pin string) defer ctx.Destroy() defer ctx.Finalize() + // YKCS11 (Yubico's pkcs#11 library) requires this to initialize correctly + // See https://github.com/Yubico/yubico-piv-tool/issues/571 + _, err = ctx.GetSlotList(true) + if err != nil { + return nil, fmt.Errorf("error getting slot list %w", err) + } + // Get token Info. var tokenInfo pkcs11.TokenInfo tokenInfo, err = ctx.GetTokenInfo(uint(slotID)) diff --git a/cmd/cosign/cli/public_key.go b/cmd/cosign/cli/public_key.go index 6f1b225082e..d6eb877a0d3 100644 --- a/cmd/cosign/cli/public_key.go +++ b/cmd/cosign/cli/public_key.go @@ -18,9 +18,9 @@ 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/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/publickey" "github.com/spf13/cobra" ) @@ -29,7 +29,7 @@ func PublicKey() *cobra.Command { cmd := &cobra.Command{ Use: "public-key", - Short: "Gets a public key from the key-pair.", + 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. diff --git a/cmd/cosign/cli/publickey/public_key.go b/cmd/cosign/cli/publickey/public_key.go index 36158587912..85060b29597 100644 --- a/cmd/cosign/cli/publickey/public_key.go +++ b/cmd/cosign/cli/publickey/public_key.go @@ -20,11 +20,11 @@ import ( "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/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/pivkey" + "github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key" + sigs "github.com/sigstore/cosign/v3/pkg/signature" "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) diff --git a/cmd/cosign/cli/publickey/public_key_test.go b/cmd/cosign/cli/publickey/public_key_test.go index 12a93439ff5..f7de44a9344 100644 --- a/cmd/cosign/cli/publickey/public_key_test.go +++ b/cmd/cosign/cli/publickey/public_key_test.go @@ -23,7 +23,7 @@ import ( "path/filepath" "testing" - "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign" ) func pass(s string) cosign.PassFunc { diff --git a/cmd/cosign/cli/rekor/rekor.go b/cmd/cosign/cli/rekor/rekor.go index 76d57ad1b08..c06cef5a0d4 100644 --- a/cmd/cosign/cli/rekor/rekor.go +++ b/cmd/cosign/cli/rekor/rekor.go @@ -18,7 +18,7 @@ import ( rekor "github.com/sigstore/rekor/pkg/client" "github.com/sigstore/rekor/pkg/generated/client" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" ) func NewClient(rekorURL string) (*client.Rekor, error) { diff --git a/cmd/cosign/cli/rekor/rekor_test.go b/cmd/cosign/cli/rekor/rekor_test.go index 31b8f4eb999..94555f09348 100644 --- a/cmd/cosign/cli/rekor/rekor_test.go +++ b/cmd/cosign/cli/rekor/rekor_test.go @@ -19,7 +19,7 @@ import ( "net/http/httptest" "testing" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" ) func TestNewClient(t *testing.T) { diff --git a/cmd/cosign/cli/save.go b/cmd/cosign/cli/save.go index 018bd9f76ed..48402ab1f0d 100644 --- a/cmd/cosign/cli/save.go +++ b/cmd/cosign/cli/save.go @@ -21,10 +21,10 @@ import ( "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/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/layout" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/spf13/cobra" ) @@ -33,7 +33,7 @@ func Save() *cobra.Command { cmd := &cobra.Command{ Use: "save", - Short: "Save the container image and associated signatures to disk at the specified directory.", + 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), @@ -59,6 +59,36 @@ func SaveCmd(ctx context.Context, opts options.SaveOptions, imageRef string) err return fmt.Errorf("parsing image name %s: %w", imageRef, err) } + // See if we are using referrers + digest, ok := ref.(name.Digest) + if !ok { + var err error + digest, err = ociremote.ResolveDigest(ref, regClientOpts...) + if err != nil { + return fmt.Errorf("resolving digest: %w", err) + } + } + + indexManifest, err := ociremote.Referrers(digest, "", regClientOpts...) + if err != nil { + return fmt.Errorf("getting referrers: %w", err) + } + + for _, manifest := range indexManifest.Manifests { + if manifest.ArtifactType == "" { + continue + } + artifactRef := ref.Context().Digest(manifest.Digest.String()) + si, err := ociremote.SignedImage(artifactRef, regClientOpts...) + if err != nil { + return fmt.Errorf("getting signed image: %w", err) + } + err = layout.WriteSignedImage(opts.Directory, si) + if err != nil { + return err + } + } + se, err := ociremote.SignedEntity(ref, regClientOpts...) if err != nil { return fmt.Errorf("signed entity: %w", err) diff --git a/cmd/cosign/cli/sign.go b/cmd/cosign/cli/sign.go index 24a659bc828..f32439eb4f9 100644 --- a/cmd/cosign/cli/sign.go +++ b/cmd/cosign/cli/sign.go @@ -16,16 +16,13 @@ package cli import ( - "context" "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/sigstore/cosign/v2/internal/ui" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" "github.com/spf13/cobra" ) @@ -34,7 +31,7 @@ func Sign() *cobra.Command { cmd := &cobra.Command{ Use: "sign", - Short: "Sign the supplied container image.", + 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 @@ -85,15 +82,18 @@ race conditions or (worse) malicious tampering. # 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 { + PreRunE: func(_ *cobra.Command, _ []string) error { + if o.NewBundleFormat && !o.Upload && o.BundlePath == "" { + return fmt.Errorf("must enable upload to the OCI registry or specify a local --bundle path with --new-bundle-format") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { switch o.Attachment { case "sbom": fmt.Fprintln(os.Stderr, options.SBOMAttachmentDeprecation) @@ -131,14 +131,13 @@ race conditions or (worse) malicious tampering. TSAServerURL: o.TSAServerURL, IssueCertificateForExistingKey: o.IssueCertificate, } - if (o.Key == "" || o.IssueCertificate) && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" { - trustedMaterial, err := cosign.TrustedRoot() - if err != nil { - ui.Warnf(context.Background(), "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) - } - ko.TrustedMaterial = trustedMaterial + if err := signcommon.LoadTrustedMaterialAndSigningConfig(cmd.Context(), &ko, o.UseSigningConfig, o.SigningConfigPath, + o.Rekor.URL, o.Fulcio.URL, o.OIDC.Issuer, o.TSAServerURL, o.TrustedRootPath, o.TlogUpload, + o.NewBundleFormat, "", o.Key, o.IssueCertificate, o.Output, "", o.OutputCertificate, o.OutputPayload, o.OutputSignature, ""); err != nil { + return err } - if err := sign.SignCmd(ro, ko, *o, args); err != nil { + + if err := sign.SignCmd(cmd.Context(), ro, ko, *o, args); err != nil { if o.Attachment == "" { return fmt.Errorf("signing %v: %w", args, err) } diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index a6ee88122d1..2bdaaf37ccd 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -18,93 +18,42 @@ package sign import ( "bytes" "context" - "crypto" - "crypto/x509" "encoding/base64" "encoding/json" - "encoding/pem" - "errors" "fmt" + "net/http" "os" "path/filepath" "strings" + "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/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" + intotov1 "github.com/in-toto/attestation/go/v1" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/client" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + cremote "github.com/sigstore/cosign/v3/pkg/cosign/remote" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "github.com/sigstore/cosign/v3/pkg/oci/walk" + "github.com/sigstore/cosign/v3/pkg/types" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + "github.com/sigstore/sigstore-go/pkg/sign" "github.com/sigstore/sigstore/pkg/signature" - signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" sigPayload "github.com/sigstore/sigstore/pkg/signature/payload" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" // Loads OIDC providers - _ "github.com/sigstore/cosign/v2/pkg/providers/all" + _ "github.com/sigstore/cosign/v3/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 @@ -115,35 +64,17 @@ func GetAttachedImageRef(ref name.Reference, attachment string, opts ...ociremot 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 { - ui.Warnf(ctx, ui.TagReferenceMessage, refStr) - } - return ref, nil -} - // nolint -func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignOptions, imgs []string) error { +func SignCmd(ctx context.Context, 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) + ctx, cancel := context.WithTimeout(ctx, 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 + var err error if signOpts.PayloadPath != "" { ui.Infof(ctx, "Using payload from: %s", signOpts.PayloadPath) staticPayload, err = os.ReadFile(filepath.Clean(signOpts.PayloadPath)) @@ -168,7 +99,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO } annotations := am.Annotations for _, inputImg := range imgs { - ref, err := ParseOCIReference(ctx, inputImg, regOpts.NameOptions()...) + ref, err := signcommon.ParseOCIReference(ctx, inputImg, regOpts.NameOptions()...) if err != nil { return err } @@ -184,7 +115,11 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO } else if err != nil { return fmt.Errorf("accessing image: %w", err) } - err = signDigest(ctx, digest, staticPayload, ko, signOpts, annotations, dd, sv, se) + if signOpts.NewBundleFormat { + err = signDigestBundle(ctx, digest, ko, signOpts, annotations) + } else { + err = signDigest(ctx, digest, staticPayload, ko, signOpts, annotations, se) + } if err != nil { return fmt.Errorf("signing digest: %w", err) } @@ -203,7 +138,11 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO 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 signOpts.NewBundleFormat { + err = signDigestBundle(ctx, digest, ko, signOpts, annotations) + } else { + err = signDigest(ctx, digest, staticPayload, ko, signOpts, annotations, se) + } if err != nil { return fmt.Errorf("signing digest: %w", err) } @@ -216,60 +155,197 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO return nil } +func signDigestBundle(ctx context.Context, digest name.Digest, ko options.KeyOpts, signOpts options.SignOptions, annotations map[string]any) error { + digestParts := strings.Split(digest.DigestStr(), ":") + if len(digestParts) != 2 { + return fmt.Errorf("unable to parse digest %s", digest.DigestStr()) + } + + annoStruct, _ := structpb.NewStruct(annotations) + subject := intotov1.ResourceDescriptor{ + Digest: map[string]string{digestParts[0]: digestParts[1]}, + Annotations: annoStruct, + } + + statement := &intotov1.Statement{ + Type: intotov1.StatementTypeUri, + Subject: []*intotov1.ResourceDescriptor{&subject}, + PredicateType: types.CosignSignPredicateType, + Predicate: &structpb.Struct{}, + } + + payload, err := protojson.Marshal(statement) + if err != nil { + return err + } + + regOpts := signOpts.Registry + ociremoteOpts, err := regOpts.ClientOpts(ctx) + if err != nil { + return fmt.Errorf("constructing client options: %w", err) + } + if regOpts.AllowHTTPRegistry || regOpts.AllowInsecure { + ociremoteOpts = append(ociremoteOpts, ociremote.WithNameOptions(name.Insecure)) + } + + bundleOpts := signcommon.CommonBundleOpts{ + Payload: payload, + Digest: digest, + PredicateType: types.CosignSignPredicateType, + BundlePath: signOpts.BundlePath, + Upload: signOpts.Upload, + OCIRemoteOpts: ociremoteOpts, + } + + if ko.SigningConfig == nil { + shouldUpload, err := signcommon.ShouldUploadToTlog(ctx, ko, digest, signOpts.TlogUpload) + if err != nil { + return fmt.Errorf("should upload to tlog: %w", err) + } + ko.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(ko, shouldUpload) + if err != nil { + return fmt.Errorf("creating signing config: %w", err) + } + } + + bundleBytes, _, _, _, err := signcommon.NewAttestationBundle(ctx, ko, signOpts.Cert, signOpts.CertChain, bundleOpts, ko.SigningConfig, ko.TrustedMaterial) + if err != nil { + return err + } + + if signOpts.BundlePath != "" { + if err := os.WriteFile(signOpts.BundlePath, bundleBytes, 0600); err != nil { + return fmt.Errorf("create bundle file: %w", err) + } + ui.Infof(ctx, "Wrote bundle to file %s", signOpts.BundlePath) + } + + if signOpts.Upload { + ui.Infof(ctx, "Pushing signature to: %s", digest.Repository) + if err := ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, bundleOpts.PredicateType, bundleOpts.OCIRemoteOpts...); err != nil { + return 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 { + annotations map[string]interface{}, se oci.SignedEntity) error { var err error + var payloads [][]byte // The payload can be passed to skip generation. if len(payload) == 0 { - payload, err = (&sigPayload.Cosign{ - Image: digest, - ClaimedIdentity: signOpts.SignContainerIdentity, - Annotations: annotations, - }).MarshalJSON() + identities := signOpts.SignContainerIdentities + if len(identities) == 0 { + identities = append(identities, "") + } + for _, identity := range identities { + payload, err = (&sigPayload.Cosign{ + Image: digest, + ClaimedIdentity: identity, + Annotations: annotations, + }).MarshalJSON() + if err != nil { + return fmt.Errorf("payload: %w", err) + } + payloads = append(payloads, payload) + } + } else { + payloads = append(payloads, payload) + } + + if ko.SigningConfig == nil { + shouldUpload, err := signcommon.ShouldUploadToTlog(ctx, ko, digest, signOpts.TlogUpload) + if err != nil { + return fmt.Errorf("should upload to tlog: %w", err) + } + ko.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(ko, shouldUpload) if err != nil { - return fmt.Errorf("payload: %w", err) + return fmt.Errorf("creating signing config: %w", err) } } - var s icos.Signer - s = ipayload.NewSigner(sv) - if sv.Cert != nil { - s = ifulcio.NewSigner(s, sv.Cert, sv.Chain) + keypair, certBytes, idToken, err := signcommon.GetKeypairAndToken(ctx, ko, signOpts.Cert, signOpts.CertChain) + if err != nil { + return fmt.Errorf("getting keypair and token: %w", err) + } + if closer, ok := keypair.(interface{ Close() }); ok { + defer closer.Close() } - 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, - )) + var tsaClientTransport http.RoundTripper + if ko.TSAClientCACert != "" || (ko.TSAClientCert != "" && ko.TSAClientKey != "") { + tsaClientTransport, err = client.GetHTTPTransport(ko.TSAClientCACert, ko.TSAClientCert, ko.TSAClientKey, ko.TSAServerName, 30*time.Second) + if err != nil { + return fmt.Errorf("getting TSA client transport: %w", err) } } - 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) + var certProvider sign.CertificateProvider + if idToken != "" { + certProvider, err = cbundle.NewCachingFulcioProvider(ko.SigningConfig) if err != nil { - return err + return fmt.Errorf("creating caching Fulcio provider: %w", err) } - s = irekor.NewSigner(s, rClient) } - ociSig, _, err := s.Sign(ctx, bytes.NewReader(payload)) - if err != nil { - return err + cbundleOpts := cbundle.SignOptions{ + TSAClientTransport: tsaClientTransport, + CertificateProvider: certProvider, } - b64sig, err := ociSig.Base64Signature() - if err != nil { - return err + ociSigs := make([]oci.Signature, len(payloads)) + b64sigs := make([]string, len(payloads)) + + var leafCertPem []byte + + for i, payload := range payloads { + content := &sign.PlainData{ + Data: payload, + } + + bundleBytes, err := cbundle.SignData(ctx, content, keypair, idToken, certBytes, ko.SigningConfig, ko.TrustedMaterial, cbundleOpts) + if err != nil { + return fmt.Errorf("signing bundle: %w", err) + } + + var pb protobundle.Bundle + if err := protojson.Unmarshal(bundleBytes, &pb); err != nil { + return fmt.Errorf("unmarshalling bundle: %w", err) + } + + bundleComponents, err := signcommon.ExtractComponentsFromProtoBundle(&pb) + if err != nil { + return fmt.Errorf("extracting components from bundle: %w", err) + } + + certPem, chainPem := signcommon.EncodeCertificatesToPEM(bundleComponents.Certificates) + if i == 0 { + leafCertPem = certPem + } + + b64sig := base64.StdEncoding.EncodeToString(bundleComponents.Signature) + b64sigs[i] = b64sig + + var opts []static.Option + if certPem != nil { + opts = append(opts, static.WithCertChain(certPem, chainPem)) + } + + if len(bundleComponents.RFC3161Timestamps) > 0 { + opts = append(opts, static.WithRFC3161Timestamp(cbundle.TimestampToRFC3161Timestamp(bundleComponents.RFC3161Timestamps[0].GetSignedTimestamp()))) + } + + if len(bundleComponents.RekorEntries) > 0 { + opts = append(opts, static.WithBundle(signcommon.RekorBundleFromProtoTlogEntry(bundleComponents.RekorEntries[0]))) + } + + ociSig, err := static.NewSignature(payload, b64sig, opts...) + if err != nil { + return fmt.Errorf("creating signature: %w", err) + } + + ociSigs[i] = ociSig } outputSignature := signOpts.OutputSignature @@ -278,7 +354,7 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti if signOpts.Recursive { outputSignature = fmt.Sprintf("%s-%s", outputSignature, strings.Replace(digest.DigestStr(), ":", "-", 1)) } - if err := os.WriteFile(outputSignature, []byte(b64sig), 0600); err != nil { + if err := os.WriteFile(outputSignature, []byte(strings.Join(b64sigs, "\n")), 0600); err != nil { return fmt.Errorf("create signature file: %w", err) } } @@ -288,18 +364,24 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti if signOpts.Recursive { outputPayload = fmt.Sprintf("%s-%s", outputPayload, strings.Replace(digest.DigestStr(), ":", "-", 1)) } - if err := os.WriteFile(outputPayload, payload, 0600); err != nil { + if err := os.WriteFile(outputPayload, bytes.Join(payloads, []byte("\n")), 0600); err != nil { // #nosec G703 -- user-supplied output path is intentional 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) + var outBytes []byte + if len(leafCertPem) > 0 { + outBytes = leafCertPem + } else { + pubPem, err := keypair.GetPublicKeyPem() + if err != nil { + return fmt.Errorf("getting public key pem: %w", err) + } + outBytes = []byte(pubPem) } - if err := os.WriteFile(signOpts.OutputCertificate, rekorBytes, 0600); err != nil { + if err := os.WriteFile(signOpts.OutputCertificate, outBytes, 0600); err != nil { return fmt.Errorf("create certificate file: %w", err) } // TODO: maybe accept a --b64 flag as well? @@ -307,16 +389,20 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti } if ko.BundlePath != "" { - signedPayload, err := fetchLocalSignedPayload(ociSig) - if err != nil { - return fmt.Errorf("failed to fetch signed payload: %w", err) - } + var contents [][]byte + for _, ociSig := range ociSigs { + 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) + content, err := json.Marshal(signedPayload) + if err != nil { + return fmt.Errorf("failed to marshal signed payload: %w", err) + } + contents = append(contents, content) } - if err := os.WriteFile(ko.BundlePath, contents, 0600); err != nil { + if err := os.WriteFile(ko.BundlePath, bytes.Join(contents, []byte("\n")), 0600); err != nil { return fmt.Errorf("create bundle file: %w", err) } ui.Infof(ctx, "Wrote bundle to file %s", ko.BundlePath) @@ -326,10 +412,21 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti return nil } - // Attach the signature to the entity. - newSE, err := mutate.AttachSignatureToEntity(se, ociSig, mutate.WithDupeDetector(dd), mutate.WithRecordCreationTimestamp(signOpts.RecordCreationTimestamp)) + hashAlgo := signcommon.ProtoHashAlgoToHash(keypair.GetHashAlgorithm()) + ddVerifier, err := signature.LoadVerifier(keypair.GetPublicKey(), hashAlgo) if err != nil { - return err + return fmt.Errorf("loading verifier: %w", err) + } + dd := cremote.NewDupeDetector(ddVerifier) + + // Attach the signature to the entity. + var newSE oci.SignedEntity + for _, ociSig := range ociSigs { + newSE, err = mutate.AttachSignatureToEntity(se, ociSig, mutate.WithDupeDetector(dd), mutate.WithRecordCreationTimestamp(signOpts.RecordCreationTimestamp)) + if err != nil { + return err + } + se = newSE } // Publish the signatures associated with this entity @@ -355,241 +452,6 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko opti 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) - } - 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 diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index c01e7952044..ff7d551062b 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -17,188 +17,143 @@ package sign import ( "context" - "crypto/sha256" - "crypto/x509" + "crypto" "encoding/base64" "encoding/json" "fmt" + "io" "os" "path/filepath" + "time" - "google.golang.org/protobuf/encoding/protojson" + "net/http" - "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" + internal "github.com/sigstore/cosign/v3/internal/pkg/cosign" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/client" + "github.com/sigstore/cosign/v3/internal/ui" + cbundle "github.com/sigstore/cosign/v3/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" + "github.com/sigstore/sigstore-go/pkg/sign" + "github.com/sigstore/sigstore/pkg/signature" + "google.golang.org/protobuf/encoding/protojson" ) +func getPayload(ctx context.Context, payloadPath string, hashFunction crypto.Hash) (internal.HashReader, func() error, error) { + if payloadPath == "-" { + return internal.NewHashReader(os.Stdin, hashFunction), func() error { return nil }, nil + } + ui.Infof(ctx, "Using payload from: %s", payloadPath) + f, err := os.Open(filepath.Clean(payloadPath)) + if err != nil { + return internal.HashReader{}, nil, err + } + return internal.NewHashReader(f, hashFunction), f.Close, nil +} + // nolint -func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string, b64 bool, outputSignature string, outputCertificate string, tlogUpload bool) ([]byte, error) { +func SignBlobCmd(ctx context.Context, ro *options.RootOptions, ko options.KeyOpts, payloadPath, certPath, certChainPath string, b64 bool, outputSignature string, outputCertificate string, tlogUpload bool) ([]byte, error) { var payload internal.HashReader - ctx, cancel := context.WithTimeout(context.Background(), ro.Timeout) + ctx, cancel := context.WithTimeout(ctx, 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)) + var shouldUpload bool + var err error + + if ko.SigningConfig == nil { + shouldUpload, err = signcommon.ShouldUploadToTlog(ctx, ko, nil, tlogUpload) + if err != nil { + return nil, fmt.Errorf("upload to tlog: %w", err) + } + ko.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(ko, shouldUpload) if err != nil { - return nil, err + return nil, fmt.Errorf("creating signing config: %w", err) } - defer f.Close() - payload = internal.NewHashReader(f, sha256.New()) + } else { + shouldUpload = len(ko.SigningConfig.RekorLogURLs()) > 0 } - sv, err := SignerFromKeyOpts(ctx, "", "", ko) - if err != nil { - return nil, err + if !shouldUpload { + // To maintain backwards compatibility with older cosign versions, + // we do not use ed25519ph for ed25519 keys when the signatures are not + // uploaded to the Tlog. + ko.DefaultLoadOptions = &[]signature.LoadOption{} } - defer sv.Close() - sig, err := sv.SignMessage(&payload, signatureoptions.WithContext(ctx)) + keypair, certBytes, idToken, err := signcommon.GetKeypairAndToken(ctx, ko, certPath, certChainPath) if err != nil { - return nil, fmt.Errorf("signing blob: %w", err) + return nil, fmt.Errorf("getting keypair and token: %w", err) + } + if closer, ok := keypair.(interface{ Close() }); ok { + defer closer.Close() } - digest := payload.Sum(nil) - signedPayload := cosign.LocalSignedPayload{} - var rekorEntry *models.LogEntryAnon - var rfc3161Timestamp *cbundle.RFC3161Timestamp - var timestampBytes []byte + hashFunction := signcommon.ProtoHashAlgoToHash(keypair.GetHashAlgorithm()) + payload, closePayload, err := getPayload(ctx, payloadPath, hashFunction) + if err != nil { + return nil, fmt.Errorf("getting payload: %w", err) + } + defer closePayload() - 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 { + if hashFunction != crypto.SHA256 && !ko.NewBundleFormat && (shouldUpload || (!ko.Sk && ko.KeyRef == "")) { + ui.Infof(ctx, "Non SHA256 hash function is not supported for old bundle format. Use --new-bundle-format to use the new bundle format or use different signing key/algorithm.") + if !ko.SkipConfirmation { + if err := ui.ConfirmContinue(ctx); err != nil { return nil, err } } + ui.Infof(ctx, "Continuing with non SHA256 hash function and old bundle format") + } - rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(timestampBytes) - // TODO: Consider uploading RFC3161 TS to Rekor - - if rfc3161Timestamp == nil { - return nil, fmt.Errorf("rfc3161 timestamp is nil") - } + data, err := io.ReadAll(&payload) + if err != nil { + return nil, fmt.Errorf("reading payload: %w", err) + } + content := &sign.PlainData{ + Data: data, + } - 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) + var tsaClientTransport http.RoundTripper + if ko.TSAClientCACert != "" || (ko.TSAClientCert != "" && ko.TSAClientKey != "") { + tsaClientTransport, err = client.GetHTTPTransport(ko.TSAClientCACert, ko.TSAClientCert, ko.TSAClientKey, ko.TSAServerName, 30*time.Second) + if err != nil { + return nil, fmt.Errorf("getting TSA client transport: %w", err) } } - shouldUpload, err := ShouldUploadToTlog(ctx, ko, nil, tlogUpload) + signOpts := cbundle.SignOptions{TSAClientTransport: tsaClientTransport} + bundleBytes, err := cbundle.SignData(ctx, content, keypair, idToken, certBytes, ko.SigningConfig, ko.TrustedMaterial, signOpts) if err != nil { - return nil, fmt.Errorf("upload to tlog: %w", err) + return nil, fmt.Errorf("signing bundle: %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 + + if ko.NewBundleFormat { + if err := os.WriteFile(ko.BundlePath, bundleBytes, 0600); err != nil { + return nil, fmt.Errorf("create bundle file: %w", err) } - ui.Infof(ctx, "tlog entry created with index: %d", *rekorEntry.LogIndex) - signedPayload.Bundle = cbundle.EntryToBundle(rekorEntry) + ui.Infof(ctx, "Wrote bundle to file %s", ko.BundlePath) + return nil, nil } - // 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) + var pb protobundle.Bundle + if err := protojson.Unmarshal(bundleBytes, &pb); err != nil { + return nil, fmt.Errorf("unmarshalling bundle: %w", err) + } - certBytes, err := extractCertificate(ctx, sv) - if err != nil { - return nil, err - } - signedPayload.Cert = base64.StdEncoding.EncodeToString(certBytes) + bundleComponents, err := signcommon.ExtractComponentsFromProtoBundle(&pb) + if err != nil { + return nil, fmt.Errorf("extracting components from bundle: %w", err) + } - contents, err = json.Marshal(signedPayload) - if err != nil { - return nil, err - } + if ko.BundlePath != "" { + pubKeyPem, err := keypair.GetPublicKeyPem() + if err != nil { + return nil, fmt.Errorf("getting public key pem: %w", err) + } + contents, err := signcommon.NewLegacyBundleFromProtoBundleComponents(bundleComponents, pubKeyPem) + if err != nil { + return nil, fmt.Errorf("creating legacy bundle: %w", err) } if err := os.WriteFile(ko.BundlePath, contents, 0600); err != nil { @@ -208,54 +163,54 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string } if outputSignature != "" { - var bts = sig + bts := bundleComponents.Signature if b64 { - bts = []byte(base64.StdEncoding.EncodeToString(sig)) + bts = []byte(base64.StdEncoding.EncodeToString(bundleComponents.Signature)) } 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 { + bts := bundleComponents.Signature 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 + bts = []byte(base64.StdEncoding.EncodeToString(bundleComponents.Signature)) + fmt.Println(string(bts)) + } else { + if _, err := os.Stdout.Write(bts); err != nil { + return nil, err + } } } - if outputCertificate != "" { - certBytes, err := extractCertificate(ctx, sv) - if err != nil { - return nil, err + if outputCertificate != "" && len(bundleComponents.Certificates) > 0 { + certPem, _ := signcommon.EncodeCertificatesToPEM(bundleComponents.Certificates) + var bts []byte + if b64 { + bts = []byte(base64.StdEncoding.EncodeToString(certPem)) + } else { + bts = certPem } - 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) + 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) + if len(bundleComponents.RFC3161Timestamps) > 0 && ko.RFC3161TimestampPath != "" { + legacyTimestamp := cbundle.TimestampToRFC3161Timestamp(bundleComponents.RFC3161Timestamps[0].GetSignedTimestamp()) + ts, err := json.Marshal(legacyTimestamp) + if err != nil { + return nil, fmt.Errorf("marshalling timestamp: %w", err) + } + if err := os.WriteFile(ko.RFC3161TimestampPath, ts, 0600); err != nil { + return nil, fmt.Errorf("create timestamp file: %w", err) + } + ui.Infof(ctx, "Wrote timestamp to file %s", ko.RFC3161TimestampPath) } - cert, err := cryptoutils.UnmarshalCertificatesFromPEM(signer) - // signer is a certificate - if err == nil && len(cert) == 1 { - return signer, nil + + if b64 { + return []byte(base64.StdEncoding.EncodeToString(bundleComponents.Signature)), nil } - return nil, nil + return bundleComponents.Signature, nil } diff --git a/cmd/cosign/cli/sign/sign_blob_test.go b/cmd/cosign/cli/sign/sign_blob_test.go index 2f0853589a4..653eac8230a 100644 --- a/cmd/cosign/cli/sign/sign_blob_test.go +++ b/cmd/cosign/cli/sign/sign_blob_test.go @@ -15,12 +15,21 @@ package sign import ( + "crypto" + "crypto/ed25519" + "crypto/rand" + "crypto/x509" + "encoding/base64" + "encoding/pem" "os" "path/filepath" "testing" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/secure-systems-lab/go-securesystemslib/encrypted" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/sigstore-go/pkg/root" ) func TestSignBlobCmd(t *testing.T) { @@ -37,7 +46,7 @@ func TestSignBlobCmd(t *testing.T) { keyOpts := options.KeyOpts{KeyRef: keyRef, BundlePath: bundlePath} // Test happy path - _, err := SignBlobCmd(rootOpts, keyOpts, blobPath, true, "", "", false) + _, err := SignBlobCmd(t.Context(), rootOpts, keyOpts, blobPath, "", "", true, "", "", false) if err != nil { t.Fatalf("unexpected error %v", err) } @@ -46,16 +55,108 @@ func TestSignBlobCmd(t *testing.T) { keyOpts.NewBundleFormat = true sigPath := filepath.Join(td, "output.sig") certPath := filepath.Join(td, "output.pem") - _, err = SignBlobCmd(rootOpts, keyOpts, blobPath, false, sigPath, certPath, false) + _, err = SignBlobCmd(t.Context(), rootOpts, keyOpts, blobPath, "", "", false, sigPath, certPath, false) if err != nil { t.Fatalf("unexpected error %v", err) } + + // Test signing with a certificate + rootCert, rootKey, _ := test.GenerateRootCa() + cert, certPrivKey, _ := test.GenerateLeafCert("subject", "oidc-issuer", rootCert, rootKey) + certPemBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + signCertPath := writeFile(t, td, string(certPemBytes), "cert.pem") + x509Encoded, err := x509.MarshalPKCS8PrivateKey(certPrivKey) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + encBytes, err := encrypted.Encrypt(x509Encoded, nil) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + pemBytes := pem.EncodeToMemory(&pem.Block{ + Bytes: encBytes, + Type: cosign.SigstorePrivateKeyPemType, + }) + certPrivKeyRef := writeFile(t, td, string(pemBytes), "certkey.pem") + keyOpts.KeyRef = certPrivKeyRef + + _, err = SignBlobCmd(t.Context(), rootOpts, keyOpts, blobPath, signCertPath, "", false, "", "", false) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + x509Encoded, err = x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + t.Fatal(err) + } + encBytes, err = encrypted.Encrypt(x509Encoded, nil) + if err != nil { + t.Fatal(err) + } + pemBytes = pem.EncodeToMemory(&pem.Block{ + Bytes: encBytes, + Type: cosign.SigstorePrivateKeyPemType, + }) + + // Test signing using Ed25519 key with custom signing config and no transparency log upload + edKeyRef := writeFile(t, td, string(pemBytes), "ed_key.pem") + keyOpts = options.KeyOpts{KeyRef: edKeyRef, BundlePath: bundlePath} + sc, err := root.NewSigningConfig( + root.SigningConfigMediaType02, + nil, + nil, + nil, + root.ServiceConfiguration{}, + nil, + root.ServiceConfiguration{}, + ) + if err != nil { + t.Fatal(err) + } + keyOpts.SigningConfig = sc + sigBytes, err := SignBlobCmd(t.Context(), rootOpts, keyOpts, blobPath, "", "", true, "", "", true) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + decodedSig, err := base64.StdEncoding.DecodeString(string(sigBytes)) + if err != nil { + t.Fatalf("failed to decode base64 signature: %v", err) + } + if !ed25519.Verify(pub, blob, decodedSig) { + errString := "expected ed25519 signature" + if ed25519.VerifyWithOptions(pub, blob, decodedSig, &ed25519.Options{Hash: crypto.SHA512}) == nil { + errString += ", received ed25519ph signature" + } + t.Fatal("signature verification failed: " + errString) + } + + // Test signing using Ed25519 key with default signing config and no transparency log upload + keyOpts = options.KeyOpts{KeyRef: edKeyRef, BundlePath: bundlePath} + sigBytes, err = SignBlobCmd(t.Context(), rootOpts, keyOpts, blobPath, "", "", true, "", "", false) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + decodedSig, err = base64.StdEncoding.DecodeString(string(sigBytes)) + if err != nil { + t.Fatalf("failed to decode base64 signature: %v", err) + } + if !ed25519.Verify(pub, blob, decodedSig) { + errString := "expected ed25519 signature" + if ed25519.VerifyWithOptions(pub, blob, decodedSig, &ed25519.Options{Hash: crypto.SHA512}) == nil { + errString += ", received ed25519ph signature" + } + t.Fatal("signature verification failed: " + errString) + } } 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 { + if err := os.WriteFile(blobPath, []byte(blob), 0o644); err != nil { t.Fatal(err) } return blobPath diff --git a/cmd/cosign/cli/sign/sign_test.go b/cmd/cosign/cli/sign/sign_test.go index 0d9de73f790..9e6e92f67ac 100644 --- a/cmd/cosign/cli/sign/sign_test.go +++ b/cmd/cosign/cli/sign/sign_test.go @@ -16,98 +16,18 @@ package sign import ( - "context" - "crypto/ecdsa" - "crypto/x509" - "encoding/pem" + "encoding/json" "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" + intotov1 "github.com/in-toto/attestation/go/v1" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/types" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/structpb" ) -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) { @@ -122,110 +42,48 @@ func TestSignCmdLocalKeyAndSk(t *testing.T) { }, } { so := options.SignOptions{} - err := SignCmd(ro, ko, so, nil) + err := SignCmd(t.Context(), 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) +func TestInTotoStatementHasPredicate(t *testing.T) { + annoStruct, _ := structpb.NewStruct(map[string]any{}) + subject := intotov1.ResourceDescriptor{ + Digest: map[string]string{"sha256": "deadbeef"}, + Annotations: annoStruct, } - // 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) + statement := &intotov1.Statement{ + Type: intotov1.StatementTypeUri, + Subject: []*intotov1.ResourceDescriptor{&subject}, + PredicateType: types.CosignSignPredicateType, + Predicate: &structpb.Struct{}, } -} - -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") + payload, err := protojson.Marshal(statement) if err != nil { - t.Fatalf("failed to create temp chain file: %v", err) + t.Fatalf("failed to marshal statement: %v", err) } - defer tmpChainFile.Close() - if _, err := tmpChainFile.Write([]byte{}); err != nil { - t.Fatalf("failed to write chain file: %v", err) + + var result map[string]interface{} + if err := json.Unmarshal(payload, &result); err != nil { + t.Fatalf("failed to unmarshal payload: %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) + if _, ok := result["predicate"]; !ok { + t.Error("in-toto statement must contain a 'predicate' field to comply with the in-toto specification") } -} -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", ""}, + if _, ok := result["_type"]; !ok { + t.Error("in-toto statement must contain a '_type' field") } - 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") - } + if _, ok := result["subject"]; !ok { + t.Error("in-toto statement must contain a 'subject' field") + } + if _, ok := result["predicateType"]; !ok { + t.Error("in-toto statement must contain a 'predicateType' field") } } diff --git a/cmd/cosign/cli/signblob.go b/cmd/cosign/cli/signblob.go index 8909b9f90ac..634394159b7 100644 --- a/cmd/cosign/cli/signblob.go +++ b/cmd/cosign/cli/signblob.go @@ -16,16 +16,15 @@ package cli import ( - "context" "fmt" "os" + "strings" - "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/internal/ui" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" + "github.com/sigstore/cosign/v3/pkg/cosign" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -36,12 +35,9 @@ func SignBlob() *cobra.Command { cmd := &cobra.Command{ Use: "sign-blob", - Short: "Sign the supplied blob, outputting the base64-encoded signature to stdout.", + 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 @@ -65,9 +61,28 @@ func SignBlob() *cobra.Command { if options.NOf(o.Key, o.SecurityKey.Use) > 1 { return &options.KeyParseError{} } + + if o.NewBundleFormat && o.BundlePath == "" { + return fmt.Errorf("must specify --bundle with --new-bundle-format") + } + + // Check if the algorithm is in the list of supported algorithms + supportedAlgorithms := cosign.GetSupportedAlgorithms() + isValid := false + for _, algo := range supportedAlgorithms { + if algo == o.SigningAlgorithm { + isValid = true + break + } + } + if !isValid { + return fmt.Errorf("invalid signing algorithm: %s. Supported algorithms are: %s", + o.SigningAlgorithm, strings.Join(supportedAlgorithms, ", ")) + } + return nil }, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { oidcClientSecret, err := o.OIDC.ClientSecret() if err != nil { return err @@ -98,13 +113,13 @@ func SignBlob() *cobra.Command { TSAServerURL: o.TSAServerURL, RFC3161TimestampPath: o.RFC3161TimestampPath, IssueCertificateForExistingKey: o.IssueCertificate, + SigningAlgorithm: o.SigningAlgorithm, } - if (o.Key == "" || o.IssueCertificate) && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" { - trustedMaterial, err := cosign.TrustedRoot() - if err != nil { - ui.Warnf(context.Background(), "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) - } - ko.TrustedMaterial = trustedMaterial + if err := signcommon.LoadTrustedMaterialAndSigningConfig(cmd.Context(), &ko, o.UseSigningConfig, o.SigningConfigPath, + o.Rekor.URL, o.Fulcio.URL, o.OIDC.Issuer, o.TSAServerURL, o.TrustedRootPath, o.TlogUpload, + o.NewBundleFormat, o.BundlePath, o.Key, o.IssueCertificate, + o.Output, "", o.OutputCertificate, "", o.OutputSignature, o.RFC3161TimestampPath); err != nil { + return err } for _, blob := range args { @@ -114,7 +129,7 @@ func SignBlob() *cobra.Command { o.OutputSignature = o.Output } - if _, err := sign.SignBlobCmd(ro, ko, blob, o.Base64Output, o.OutputSignature, o.OutputCertificate, o.TlogUpload); err != nil { + if _, err := sign.SignBlobCmd(cmd.Context(), ro, ko, blob, o.Cert, o.CertChain, o.Base64Output, o.OutputSignature, o.OutputCertificate, o.TlogUpload); err != nil { return fmt.Errorf("signing %s: %w", blob, err) } } diff --git a/cmd/cosign/cli/signcommon/common.go b/cmd/cosign/cli/signcommon/common.go new file mode 100644 index 00000000000..38d5e8e71ac --- /dev/null +++ b/cmd/cosign/cli/signcommon/common.go @@ -0,0 +1,680 @@ +// 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 signcommon + +import ( + "bytes" + "context" + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "os" + + "net/http" + "time" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign/privacy" + "github.com/sigstore/cosign/v3/internal/auth" + "github.com/sigstore/cosign/v3/internal/key" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/client" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/pivkey" + "github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + sigs "github.com/sigstore/cosign/v3/pkg/signature" + + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + pb_go_v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + protorekor "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" + prototrustroot "github.com/sigstore/protobuf-specs/gen/pb-go/trustroot/v1" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/sign" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" +) + +// SignerVerifier contains keys or certs to sign and verify. +type SignerVerifier struct { + Cert []byte + Chain []byte + signature.SignerVerifier + close func() +} + +// Close closes the key context if there is one. +func (c *SignerVerifier) Close() { + if c.close != nil { + c.close() + } +} + +// GetKeypairAndToken creates a keypair object from provided key or cert flags or generates an ephemeral key. +// For an ephemeral key, it also uses the key to fetch an OIDC token, the pair of which are later used to get a Fulcio cert. +// +// Ensure the returned SignerVerifier is closed via calling SignerVerifier.Close. +func GetKeypairAndToken(ctx context.Context, ko options.KeyOpts, cert, certChain string) (sign.Keypair, []byte, string, error) { + var keypair sign.Keypair + var ephemeralKeypair bool + var idToken string + var sv *SignerVerifier + var certBytes []byte + var err error + + sv, ephemeralKeypair, err = signerFromKeyOpts(ctx, cert, certChain, ko) + if err != nil { + return nil, nil, "", fmt.Errorf("getting signer: %w", err) + } + keypair, err = key.NewSignerVerifierKeypair(sv, ko.DefaultLoadOptions) + if err != nil { + sv.Close() + return nil, nil, "", fmt.Errorf("creating signerverifier keypair: %w", err) + } + certBytes = sv.Cert + + if ephemeralKeypair || ko.IssueCertificateForExistingKey { + idToken, err = auth.RetrieveIDToken(ctx, auth.IDTokenConfig{ + TokenOrPath: ko.IDToken, + DisableProviders: ko.OIDCDisableProviders, + Provider: ko.OIDCProvider, + AuthFlow: ko.FulcioAuthFlow, + SkipConfirm: ko.SkipConfirmation, + OIDCServices: ko.SigningConfig.OIDCProviderURLs(), + ClientID: ko.OIDCClientID, + ClientSecret: ko.OIDCClientSecret, + RedirectURL: ko.OIDCRedirectURL, + }) + if err != nil { + sv.Close() + return nil, nil, "", fmt.Errorf("retrieving ID token: %w", err) + } + } + + return keypair, certBytes, idToken, nil +} + +// ShouldUploadToTlog determines whether the user wants to upload the entry to Rekor. +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 signerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, bool, 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, ko.DefaultLoadOptions) + default: + genKey = true + ui.Infof(ctx, "Generating ephemeral keys...") + sv, err = signerFromNewKey(ko.SigningAlgorithm, ko.DefaultLoadOptions) + } + if err != nil { + return nil, false, err + } + return sv, genKey, nil +} + +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, defaultLoadOptions *[]signature.LoadOption) (*SignerVerifier, error) { + k, err := sigs.SignerVerifierFromKeyRef(ctx, keyRef, passFunc, defaultLoadOptions) + 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) + } + certSigner.Chain = certChainBytes + + return certSigner, nil +} + +func signerFromNewKey(signingAlgorithm string, defaultLoadOptions *[]signature.LoadOption) (*SignerVerifier, error) { + keyDetails, err := ParseSignatureAlgorithmFlag(signingAlgorithm) + if err != nil { + return nil, fmt.Errorf("parsing signature algorithm: %w", err) + } + algo, err := signature.GetAlgorithmDetails(keyDetails) + if err != nil { + return nil, fmt.Errorf("getting algorithm details: %w", err) + } + + privKey, err := cosign.GeneratePrivateKeyWithAlgorithm(&algo) + if err != nil { + return nil, fmt.Errorf("generating cert: %w", err) + } + + defaultLoadOptions = cosign.GetDefaultLoadOptions(defaultLoadOptions) + sv, err := signature.LoadSignerVerifierFromAlgorithmDetails(privKey, algo, *defaultLoadOptions...) + if err != nil { + return nil, err + } + + return &SignerVerifier{ + SignerVerifier: sv, + }, nil +} + +type CommonBundleOpts struct { + Payload []byte + Digest name.Digest + PredicateType string + BundlePath string + Upload bool + OCIRemoteOpts []ociremote.Option +} + +// NewAttestationBundle uses signing config and trusted root to sign an attestation and create a bundle. +func NewAttestationBundle(ctx context.Context, ko options.KeyOpts, cert, certChain string, bundleOpts CommonBundleOpts, signingConfig *root.SigningConfig, trustedMaterial root.TrustedMaterial) ([]byte, crypto.PublicKey, string, pb_go_v1.HashAlgorithm, error) { + keypair, certBytes, idToken, err := GetKeypairAndToken(ctx, ko, cert, certChain) + if err != nil { + return nil, nil, "", pb_go_v1.HashAlgorithm_HASH_ALGORITHM_UNSPECIFIED, fmt.Errorf("getting keypair and token: %w", err) + } + if closer, ok := keypair.(interface{ Close() }); ok { + defer closer.Close() + } + + content := &sign.DSSEData{ + Data: bundleOpts.Payload, + PayloadType: "application/vnd.in-toto+json", + } + + var tsaClientTransport http.RoundTripper + if ko.TSAClientCACert != "" || (ko.TSAClientCert != "" && ko.TSAClientKey != "") { + tsaClientTransport, err = client.GetHTTPTransport(ko.TSAClientCACert, ko.TSAClientCert, ko.TSAClientKey, ko.TSAServerName, 30*time.Second) + if err != nil { + return nil, nil, "", pb_go_v1.HashAlgorithm_HASH_ALGORITHM_UNSPECIFIED, fmt.Errorf("getting TSA client transport: %w", err) + } + } + signOpts := cbundle.SignOptions{TSAClientTransport: tsaClientTransport} + + bundle, err := cbundle.SignData(ctx, content, keypair, idToken, certBytes, signingConfig, trustedMaterial, signOpts) + if err != nil { + return nil, nil, "", pb_go_v1.HashAlgorithm_HASH_ALGORITHM_UNSPECIFIED, fmt.Errorf("signing bundle: %w", err) + } + + pubKeyPem, err := keypair.GetPublicKeyPem() + if err != nil { + return nil, nil, "", pb_go_v1.HashAlgorithm_HASH_ALGORITHM_UNSPECIFIED, fmt.Errorf("getting public key pem: %w", err) + } + + return bundle, keypair.GetPublicKey(), pubKeyPem, keypair.GetHashAlgorithm(), nil +} + +type BundleComponents struct { + Signature []byte + Certificates []*pb_go_v1.X509Certificate + RekorEntries []*protorekor.TransparencyLogEntry + RFC3161Timestamps []*pb_go_v1.RFC3161SignedTimestamp +} + +// 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 { + ui.Warnf(ctx, ui.TagReferenceMessage, refStr) + } + return ref, nil +} + +func ParseSignatureAlgorithmFlag(signingAlgorithm string) (pb_go_v1.PublicKeyDetails, error) { + if signingAlgorithm == "" { + var err error + signingAlgorithm, err = signature.FormatSignatureAlgorithmFlag(pb_go_v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256) + if err != nil { + return pb_go_v1.PublicKeyDetails_PUBLIC_KEY_DETAILS_UNSPECIFIED, fmt.Errorf("formatting signature algorithm: %w", err) + } + } + return signature.ParseSignatureAlgorithmFlag(signingAlgorithm) +} + +// LoadTrustedMaterialAndSigningConfig loads the trusted material and signing config from the given options. +func LoadTrustedMaterialAndSigningConfig(ctx context.Context, ko *options.KeyOpts, useSigningConfig bool, signingConfigPath string, + rekorURL, fulcioURL, oidcIssuer, tsaServerURL, trustedRootPath string, + tlogUpload bool, newBundleFormat bool, bundlePath string, keyRef string, issueCertificate bool, + output, outputAttestation, outputCertificate, outputPayload, outputSignature, outputTimestamp string) error { + var err error + // If a signing config is used, then service URLs cannot be specified + if (useSigningConfig || signingConfigPath != "") && + ((rekorURL != "" && rekorURL != options.DefaultRekorURL) || + (fulcioURL != "" && fulcioURL != options.DefaultFulcioURL) || + (oidcIssuer != "" && oidcIssuer != options.DefaultOIDCIssuerURL) || + tsaServerURL != "") { + return fmt.Errorf("cannot specify service URLs and use signing config") + } + if (useSigningConfig || signingConfigPath != "") && !tlogUpload { + return fmt.Errorf("--tlog-upload=false is not supported with --signing-config or --use-signing-config. Provide a signing config with --signing-config without a transparency log service, which can be created with `cosign signing-config create` or `curl https://raw.githubusercontent.com/sigstore/root-signing/refs/heads/main/targets/signing_config.v0.2.json | jq 'del(.rekorTlogUrls)'` for the public instance") + } + // Signing config requires a bundle as output for verification materials since sigstore-go is used + if (useSigningConfig || signingConfigPath != "") && !newBundleFormat && bundlePath == "" { + return fmt.Errorf("must provide --new-bundle-format or --bundle where applicable with --signing-config or --use-signing-config") + } + // Fetch a trusted root when: + // * requesting a certificate and no CT log key is provided to verify an SCT + // * using a signing config + if ((keyRef == "" || issueCertificate) && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "") || + (useSigningConfig || signingConfigPath != "") { + if trustedRootPath != "" { + ko.TrustedMaterial, err = root.NewTrustedRootFromPath(trustedRootPath) + if err != nil { + return fmt.Errorf("loading trusted root: %w", err) + } + } else { + ko.TrustedMaterial, err = cosign.TrustedRoot() + if err != nil { + ui.Warnf(ctx, "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) + } + } + } + if signingConfigPath != "" { + ko.SigningConfig, err = root.NewSigningConfigFromPath(signingConfigPath) + if err != nil { + return fmt.Errorf("error reading signing config from file: %w", err) + } + } else if useSigningConfig { + ko.SigningConfig, err = cosign.SigningConfig() + if err != nil { + return fmt.Errorf("error getting signing config from TUF: %w", err) + } + } + + // TODO: Remove deprecated output flags warning in a future release (when flags are removed) + if newBundleFormat && outputSignature != "" { + ui.Warnf(context.Background(), "--output-signature is deprecated when using --new-bundle-format and will be ignored") + } + if newBundleFormat && outputAttestation != "" { + ui.Warnf(context.Background(), "--output-attestation is deprecated when using --new-bundle-format and will be ignored") + } + if newBundleFormat && outputCertificate != "" { + ui.Warnf(context.Background(), "--output-certificate is deprecated when using --new-bundle-format and will be ignored") + } + if newBundleFormat && outputPayload != "" { + ui.Warnf(context.Background(), "--output-payload is deprecated when using --new-bundle-format and will be ignored") + } + if newBundleFormat && outputTimestamp != "" { + ui.Warnf(context.Background(), "--rfc3161-timestamp is deprecated when using --new-bundle-format and will be ignored") + } + if newBundleFormat && output != "" { + ui.Warnf(context.Background(), "--output is deprecated when using --new-bundle-format and will be ignored") + } + + return nil +} + +// ExtractComponentsFromProtoBundle extracts the components from a protobuf bundle. +func ExtractComponentsFromProtoBundle(bundle *protobundle.Bundle) (*BundleComponents, error) { + if bundle == nil { + return nil, fmt.Errorf("bundle is nil") + } + + var sig []byte + if dsseEnv := bundle.GetDsseEnvelope(); dsseEnv != nil { + var err error + sig, err = json.Marshal(dsseEnv) + if err != nil { + return nil, fmt.Errorf("marshalling dsse envelope: %w", err) + } + } else if ms := bundle.GetMessageSignature(); ms != nil { + sig = ms.GetSignature() + } + + if sig == nil { + return nil, fmt.Errorf("bundle does not contain a message signature or dsse envelope") + } + + bc := &BundleComponents{ + Signature: sig, + } + + if vm := bundle.GetVerificationMaterial(); vm != nil { + if chain := vm.GetX509CertificateChain(); chain != nil && len(chain.GetCertificates()) > 0 { + bc.Certificates = chain.GetCertificates() + } else if cert := vm.GetCertificate(); cert != nil { + bc.Certificates = []*pb_go_v1.X509Certificate{cert} + } + if tlogEntries := vm.GetTlogEntries(); len(tlogEntries) > 0 { + bc.RekorEntries = tlogEntries + } + if tvd := vm.GetTimestampVerificationData(); tvd != nil { + if timestamps := tvd.GetRfc3161Timestamps(); len(timestamps) > 0 { + bc.RFC3161Timestamps = timestamps + } + } + } + + return bc, nil +} + +// EncodeCertificatesToPEM encodes certificates to PEM format. +func EncodeCertificatesToPEM(certs []*pb_go_v1.X509Certificate) ([]byte, []byte) { + if len(certs) == 0 { + return nil, nil + } + + var parsedCerts []*x509.Certificate + for _, cert := range certs { + c, err := x509.ParseCertificate(cert.GetRawBytes()) + if err != nil { + continue + } + parsedCerts = append(parsedCerts, c) + } + + if len(parsedCerts) == 0 { + return nil, nil + } + + certPem, _ := cryptoutils.MarshalCertificateToPEM(parsedCerts[0]) + var chainPem []byte + if len(parsedCerts) > 1 { + chainPem, _ = cryptoutils.MarshalCertificatesToPEM(parsedCerts[1:]) + } + return certPem, chainPem +} + +// RekorBundleFromProtoTlogEntry creates a RekorBundle from a protobuf TransparencyLogEntry. +func RekorBundleFromProtoTlogEntry(entry *protorekor.TransparencyLogEntry) *cbundle.RekorBundle { + return &cbundle.RekorBundle{ + SignedEntryTimestamp: entry.GetInclusionPromise().GetSignedEntryTimestamp(), + Payload: cbundle.RekorPayload{ + Body: entry.GetCanonicalizedBody(), + IntegratedTime: entry.GetIntegratedTime(), + LogIndex: entry.GetLogIndex(), + LogID: hex.EncodeToString(entry.GetLogId().GetKeyId()), + }, + } +} + +// NewLegacyBundleFromProtoBundleComponents creates a legacy bundle from a protobuf bundle. +func NewLegacyBundleFromProtoBundleComponents(bc *BundleComponents, pubKeyPem string) ([]byte, error) { + signedPayload := cosign.LocalSignedPayload{ + Base64Signature: base64.StdEncoding.EncodeToString(bc.Signature), + } + + if len(bc.Certificates) > 0 { + certPem, _ := EncodeCertificatesToPEM(bc.Certificates) + signedPayload.Cert = base64.StdEncoding.EncodeToString(certPem) + } else if pubKeyPem != "" { + signedPayload.Cert = base64.StdEncoding.EncodeToString([]byte(pubKeyPem)) + } + + if len(bc.RekorEntries) > 0 { + signedPayload.Bundle = RekorBundleFromProtoTlogEntry(bc.RekorEntries[0]) + } + + return json.Marshal(signedPayload) +} + +// NewSigningConfigFromKeyOpts creates a signing config from key options. +// This only supports Rekor v1. Rekor v2 requires a user-provided signing config. +func NewSigningConfigFromKeyOpts(ko options.KeyOpts, tlogUpload bool) (*root.SigningConfig, error) { + var fulcioServices []root.Service + if ko.FulcioURL != "" { + fulcioServices = append(fulcioServices, root.Service{ + URL: ko.FulcioURL, + MajorAPIVersion: 1, + ValidityPeriodStart: time.Now(), + }) + } + + var oidcServices []root.Service + if ko.OIDCIssuer != "" { + oidcServices = append(oidcServices, root.Service{ + URL: ko.OIDCIssuer, + MajorAPIVersion: 1, + ValidityPeriodStart: time.Now(), + }) + } + + var rekorServices []root.Service + var rekorConfig root.ServiceConfiguration + if ko.RekorURL != "" && tlogUpload { + rekorServices = append(rekorServices, root.Service{ + URL: ko.RekorURL, + MajorAPIVersion: 1, + ValidityPeriodStart: time.Now(), + }) + rekorConfig = root.ServiceConfiguration{ + Selector: prototrustroot.ServiceSelector_ANY, + Count: 1, + } + } + + var tsaServices []root.Service + var tsaConfig root.ServiceConfiguration + if ko.TSAServerURL != "" { + tsaServices = append(tsaServices, root.Service{ + URL: ko.TSAServerURL, + MajorAPIVersion: 1, + ValidityPeriodStart: time.Now(), + }) + tsaConfig = root.ServiceConfiguration{ + Selector: prototrustroot.ServiceSelector_ANY, + Count: 1, + } + } + + return root.NewSigningConfig( + root.SigningConfigMediaType02, + fulcioServices, + oidcServices, + rekorServices, + rekorConfig, + tsaServices, + tsaConfig, + ) +} + +// ProtoHashAlgoToHash converts a protobuf HashAlgorithm to a crypto.Hash. +func ProtoHashAlgoToHash(ha pb_go_v1.HashAlgorithm) crypto.Hash { + switch ha { + case pb_go_v1.HashAlgorithm_SHA2_256: + return crypto.SHA256 + case pb_go_v1.HashAlgorithm_SHA2_384: + return crypto.SHA384 + case pb_go_v1.HashAlgorithm_SHA2_512: + return crypto.SHA512 + default: + return crypto.Hash(0) + } +} diff --git a/cmd/cosign/cli/signcommon/common_test.go b/cmd/cosign/cli/signcommon/common_test.go new file mode 100644 index 00000000000..3c17baed0a0 --- /dev/null +++ b/cmd/cosign/cli/signcommon/common_test.go @@ -0,0 +1,205 @@ +// 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 signcommon + +import ( + "context" + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "os" + "reflect" + "strings" + "testing" + + "github.com/secure-systems-lab/go-securesystemslib/encrypted" + "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/stretchr/testify/assert" +) + +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} +} + +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"), nil) + 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"), nil) + 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"), nil) + 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"), nil) + 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"), nil) + 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") + } + } +} diff --git a/cmd/cosign/cli/signingconfig.go b/cmd/cosign/cli/signingconfig.go index 088d2d53bfc..036a2b3cd7a 100644 --- a/cmd/cosign/cli/signingconfig.go +++ b/cmd/cosign/cli/signingconfig.go @@ -17,8 +17,8 @@ package cli import ( "context" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/signingconfig" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signingconfig" "github.com/spf13/cobra" ) @@ -52,13 +52,19 @@ Each service is specified via a repeatable flag (--fulcio, --rekor, --oidc-provi --out signing-config.json`, RunE: func(cmd *cobra.Command, _ []string) error { scCreateCmd := &signingconfig.CreateCmd{ - FulcioSpecs: o.Fulcio, - RekorSpecs: o.Rekor, - OIDCProviderSpecs: o.OIDCProvider, - TSASpecs: o.TSA, - TSAConfig: o.TSAConfig, - RekorConfig: o.RekorConfig, - Out: o.Out, + FulcioSpecs: o.Fulcio, + RekorSpecs: o.Rekor, + OIDCProviderSpecs: o.OIDCProvider, + TSASpecs: o.TSA, + TSAConfig: o.TSAConfig, + RekorConfig: o.RekorConfig, + WithDefaultServices: o.WithDefaultServices || o.RekorV2, + NoDefaultFulcio: o.NoDefaultFulcio, + NoDefaultRekor: o.NoDefaultRekor, + NoDefaultOIDC: o.NoDefaultOIDC, + NoDefaultTSA: o.NoDefaultTSA, + RekorV2: o.RekorV2, + Out: o.Out, } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) diff --git a/cmd/cosign/cli/signingconfig/signingconfig.go b/cmd/cosign/cli/signingconfig/signingconfig.go index f8910dc699b..491f3d5e5b0 100644 --- a/cmd/cosign/cli/signingconfig/signingconfig.go +++ b/cmd/cosign/cli/signingconfig/signingconfig.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "github.com/sigstore/cosign/v3/pkg/cosign" prototrustroot "github.com/sigstore/protobuf-specs/gen/pb-go/trustroot/v1" "github.com/sigstore/sigstore-go/pkg/root" ) @@ -34,6 +35,13 @@ type CreateCmd struct { TSAConfig string RekorConfig string Out string + + WithDefaultServices bool + NoDefaultFulcio bool + NoDefaultRekor bool + NoDefaultOIDC bool + NoDefaultTSA bool + RekorV2 bool } func (c *CreateCmd) Exec(_ context.Context) error { @@ -48,15 +56,48 @@ func (c *CreateCmd) Exec(_ context.Context) error { rekorServices := make([]root.Service, 0, len(c.RekorSpecs)) oidcProviders := make([]root.Service, 0, len(c.OIDCProviderSpecs)) tsaServices := make([]root.Service, 0, len(c.TSASpecs)) + var rekorConfig root.ServiceConfiguration + var tsaConfig root.ServiceConfiguration + var err error + + if c.WithDefaultServices { + var sc *root.SigningConfig + if c.RekorV2 { + sc, err = cosign.SigningConfigRekorV2() + } else { + sc, err = cosign.SigningConfig() + } + if err != nil { + return fmt.Errorf("getting default trusted root: %w", err) + } + if !c.NoDefaultFulcio { + fulcioServices = append(fulcioServices, sc.FulcioCertificateAuthorityURLs()...) + } + if !c.NoDefaultRekor { + rekorServices = append(rekorServices, sc.RekorLogURLs()...) + rekorConfig = sc.RekorLogURLsConfig() + } + if !c.NoDefaultOIDC { + oidcProviders = append(oidcProviders, sc.OIDCProviderURLs()...) + } + if !c.NoDefaultTSA { + tsaServices = append(tsaServices, sc.TimestampAuthorityURLs()...) + tsaConfig = sc.TimestampAuthorityURLsConfig() + } + } - rekorConfig, err := parseServiceConfig(c.RekorConfig) - if err != nil { - return fmt.Errorf("parsing rekor-config: %w", err) + if c.RekorConfig != "" { + rekorConfig, err = parseServiceConfig(c.RekorConfig) + if err != nil { + return fmt.Errorf("parsing rekor-config: %w", err) + } } - tsaConfig, err := parseServiceConfig(c.TSAConfig) - if err != nil { - return fmt.Errorf("parsing tsa-config: %w", err) + if c.TSAConfig != "" { + tsaConfig, err = parseServiceConfig(c.TSAConfig) + if err != nil { + return fmt.Errorf("parsing tsa-config: %w", err) + } } for _, spec := range c.FulcioSpecs { diff --git a/cmd/cosign/cli/signingconfig/signingconfig_test.go b/cmd/cosign/cli/signingconfig/signingconfig_test.go index f1bc3356ba1..adfca8a41a1 100644 --- a/cmd/cosign/cli/signingconfig/signingconfig_test.go +++ b/cmd/cosign/cli/signingconfig/signingconfig_test.go @@ -16,7 +16,11 @@ package signingconfig import ( "context" + "crypto" + "crypto/ed25519" "fmt" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" @@ -25,6 +29,8 @@ import ( prototrustroot "github.com/sigstore/protobuf-specs/gen/pb-go/trustroot/v1" "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/theupdateframework/go-tuf/v2/metadata" "google.golang.org/protobuf/encoding/protojson" ) @@ -165,7 +171,7 @@ func TestCreateCmd(t *testing.T) { } delete(specMap, key) - var pairs []string + pairs := make([]string, 0, len(specMap)) for k, v := range specMap { pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) } @@ -197,6 +203,31 @@ func TestCreateCmd(t *testing.T) { t.Fatal("expected error for missing tsa-config, but got none") } signingConfigCreate.TSAConfig = "ANY" // reset + + // Test RekorV2 + tufRepo := t.TempDir() + err = newTUF(tufRepo, map[string][]byte{ + "signing_config_rekor_v2.v0.2.json": []byte(`{ + "mediaType": "application/vnd.dev.sigstore.signingconfig.v0.2+json", + "rekorTlogConfig": {"selector": "EXACT", "count": 1}, + "tsaConfig": {"selector": "ANY"} + }`), + }) + checkErr(t, err) + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufRepo)).ServeHTTP(w, r) + })) + defer tufServer.Close() + t.Setenv("TUF_MIRROR", tufServer.URL) + t.Setenv("TUF_ROOT_JSON", filepath.Join(tufRepo, "1.root.json")) + t.Setenv("TUF_ROOT", t.TempDir()) + signingConfigCreateRekorV2 := CreateCmd{ + WithDefaultServices: true, + RekorV2: true, + Out: filepath.Join(td, "signingconfig_rekor_v2.json"), + } + err = signingConfigCreateRekorV2.Exec(ctx) + checkErr(t, err) } func checkErr(t *testing.T, err error) { @@ -204,3 +235,98 @@ func checkErr(t *testing.T, err error) { t.Fatal(err) } } + +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 map[string][]byte) error { + expiration := time.Now().AddDate(0, 0, 1).UTC() + targets := metadata.Targets(expiration) + targetsDir := filepath.Join(td, "targets") + err := os.Mkdir(targetsDir, 0700) + if err != nil { + return err + } + for name, content := range targetList { + targetPath := filepath.Join(targetsDir, name) + err := os.WriteFile(targetPath, content, 0600) + if err != nil { + return err + } + targetFileInfo, err := metadata.TargetFile().FromFile(targetPath, "sha256") + if err != nil { + return err + } + targets.Signed.Targets[name] = targetFileInfo + } + snapshot := metadata.Snapshot(expiration) + timestamp := metadata.Timestamp(expiration) + root := metadata.Root(expiration) + root.Signed.ConsistentSnapshot = false + public, signer, err := newKey() + if err != nil { + return err + } + for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { + err := root.Signed.AddKey(public, name) + if err != nil { + return err + } + switch name { + case "targets": + _, err = targets.Sign(signer) + case "snapshot": + _, err = snapshot.Sign(signer) + case "timestamp": + _, err = timestamp.Sign(signer) + case "root": + _, err = root.Sign(signer) + } + if err != nil { + return err + } + } + err = targets.ToFile(filepath.Join(td, "targets.json"), false) + if err != nil { + return err + } + err = snapshot.ToFile(filepath.Join(td, "snapshot.json"), false) + if err != nil { + return err + } + err = timestamp.ToFile(filepath.Join(td, "timestamp.json"), false) + if err != nil { + return err + } + err = root.ToFile(filepath.Join(td, "1.root.json"), false) + if err != nil { + return err + } + err = root.VerifyDelegate("root", root) + if err != nil { + return err + } + err = root.VerifyDelegate("targets", targets) + if err != nil { + return err + } + err = root.VerifyDelegate("snapshot", snapshot) + if err != nil { + return err + } + err = root.VerifyDelegate("timestamp", timestamp) + return err +} diff --git a/cmd/cosign/cli/templates/templater.go b/cmd/cosign/cli/templates/templater.go index f2aa7f183ca..d86f43378c8 100644 --- a/cmd/cosign/cli/templates/templater.go +++ b/cmd/cosign/cli/templates/templater.go @@ -21,7 +21,7 @@ import ( "text/template" "unicode" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/templates/term" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/templates/term" "github.com/spf13/cobra" flag "github.com/spf13/pflag" ) diff --git a/cmd/cosign/cli/tree.go b/cmd/cosign/cli/tree.go index 521edffc8af..bd02e7df4cd 100644 --- a/cmd/cosign/cli/tree.go +++ b/cmd/cosign/cli/tree.go @@ -18,12 +18,13 @@ package cli import ( "context" "fmt" + "io" "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/sigstore/cosign/v3/cmd/cosign/cli/options" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/spf13/cobra" ) @@ -37,7 +38,7 @@ func Tree() *cobra.Command { Args: cobra.ExactArgs(1), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { - return TreeCmd(cmd.Context(), c.Registry, c.RegistryExperimental, c.ExperimentalOCI11, args[0]) + return TreeCmd(cmd.Context(), c.Registry, c.RegistryExperimental, c.ExperimentalOCI11, args[0], cmd.OutOrStdout()) }, } @@ -50,7 +51,7 @@ type OCIRelationsKey struct { artifactDigest name.Digest } -func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts options.RegistryExperimentalOptions, experimentalOCI11 bool, imageRef string) error { +func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts options.RegistryExperimentalOptions, experimentalOCI11 bool, imageRef string, out io.Writer) error { scsaMap := map[name.Tag][]v1.Layer{} ociRelationsMap := map[OCIRelationsKey][]v1.Layer{} @@ -63,15 +64,23 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op if err != nil { return err } - fmt.Fprintf(os.Stdout, "📦 Supply Chain Security Related artifacts for an image: %s\n", ref.String()) + fmt.Fprintf(out, "📦 Supply Chain Security Related artifacts for an image: %s\n", ref.String()) simg, err := ociremote.SignedEntity(ref, remoteOpts...) if err != nil { return err } + // Get the digest from the signed entity to avoid redundant manifest fetches + // in subsequent tag resolution calls + digest, err := simg.Digest() + if err != nil { + return err + } + digestRef := ref.Context().Digest(digest.String()) + // Handle the legacy mode first, always - attRef, err := ociremote.AttestationTag(ref, remoteOpts...) + attRef, err := ociremote.AttestationTag(digestRef, remoteOpts...) if err != nil { return err } @@ -87,7 +96,7 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op } } - sigRef, err := ociremote.SignatureTag(ref, remoteOpts...) + sigRef, err := ociremote.SignatureTag(digestRef, remoteOpts...) if err != nil { return err } @@ -103,7 +112,7 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op } } - sbomRef, err := ociremote.SBOMTag(ref, remoteOpts...) + sbomRef, err := ociremote.SBOMTag(digestRef, remoteOpts...) if err != nil { return err } @@ -122,17 +131,11 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op // Handle the experimental OCI 1.1 mode if regExpOpts.RegistryReferrersMode == options.RegistryReferrersModeOCI11 || experimentalOCI11 { // Handle OCI 1.1 mode - digest, ok := ref.(name.Digest) - if !ok { - var err error - digest, err = ociremote.ResolveDigest(ref, remoteOpts...) - if err != nil { - return fmt.Errorf("resolving digest: %w", err) - } - } + // We already have the digest from the SignedEntity, so reuse it + // instead of calling ResolveDigest again // Get all referrers - indexManifest, err := ociremote.Referrers(digest, "", remoteOpts...) + indexManifest, err := ociremote.Referrers(digestRef, "", remoteOpts...) if err != nil { return fmt.Errorf("getting referrers: %w", err) } @@ -151,6 +154,17 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op continue } + artifactType := manifest.ArtifactType + // Check if we are using protobuf bundle, + // and if so update artifactType to the bundle predicate + imageManifest, err := artifactImage.Manifest() + if err == nil { + val, ok := imageManifest.Annotations[ociremote.BundlePredicateType] + if ok { + artifactType = val + } + } + // Get layers for this artifact layers, err := artifactImage.Layers() if err != nil { @@ -159,13 +173,13 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op } // Add to the map - key := OCIRelationsKey{manifest.ArtifactType, artifactRef} + key := OCIRelationsKey{artifactType, artifactRef} ociRelationsMap[key] = append(ociRelationsMap[key], layers...) } } if len(scsaMap) == 0 && len(ociRelationsMap) == 0 { - fmt.Fprintf(os.Stdout, "No Supply Chain Security Related Artifacts found for image %s,\n start creating one with simply running"+ + fmt.Fprintf(out, "No Supply Chain Security Related Artifacts found for image %s,\n start creating one with simply running"+ "$ cosign sign ", ref.String()) return nil } @@ -173,14 +187,14 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op for t, k := range scsaMap { switch t { case sigRef: - fmt.Fprintf(os.Stdout, "└── 🔐 Signatures for an image tag: %s\n", t.String()) + fmt.Fprintf(out, "└── 🔐 Signatures for an image tag: %s\n", t.String()) case sbomRef: - fmt.Fprintf(os.Stdout, "└── 📦 SBOMs for an image tag: %s\n", t.String()) + fmt.Fprintf(out, "└── 📦 SBOMs for an image tag: %s\n", t.String()) case attRef: - fmt.Fprintf(os.Stdout, "└── 💾 Attestations for an image tag: %s\n", t.String()) + fmt.Fprintf(out, "└── 💾 Attestations for an image tag: %s\n", t.String()) } - if err := printLayers(k); err != nil { + if err := printLayers(k, out); err != nil { return err } } @@ -190,8 +204,8 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op // TODO - We could apply different emojis here for different values of key.artifactType - fmt.Fprintf(os.Stdout, "└── %s %s artifacts via OCI referrer: %s\n", emoji, key.artifactType, key.artifactDigest) - if err := printLayers(layers); err != nil { + fmt.Fprintf(out, "└── %s %s artifacts via OCI referrer: %s\n", emoji, key.artifactType, key.artifactDigest) + if err := printLayers(layers, out); err != nil { return err } } @@ -199,7 +213,7 @@ func TreeCmd(ctx context.Context, regOpts options.RegistryOptions, regExpOpts op return nil } -func printLayers(layers []v1.Layer) error { +func printLayers(layers []v1.Layer, out io.Writer) error { for i, l := range layers { last := i == len(layers)-1 var sym string @@ -212,7 +226,7 @@ func printLayers(layers []v1.Layer) error { if err != nil { return err } - fmt.Printf("%s 🍒 %s\n", sym, digest) + fmt.Fprintf(out, "%s 🍒 %s\n", sym, digest) } return nil } diff --git a/cmd/cosign/cli/triangulate.go b/cmd/cosign/cli/triangulate.go index 711b564d02e..1c32280b8a0 100644 --- a/cmd/cosign/cli/triangulate.go +++ b/cmd/cosign/cli/triangulate.go @@ -18,8 +18,8 @@ package cli import ( "flag" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/triangulate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/triangulate" "github.com/spf13/cobra" ) @@ -27,8 +27,9 @@ func Triangulate() *cobra.Command { o := &options.TriangulateOptions{} cmd := &cobra.Command{ + Deprecated: "triangulate will be removed in v4.0.0 (see https://github.com/sigstore/cosign/issues/4696). Instead, please use `oras discover` or `cosign tree` to show referring artifacts", Use: "triangulate", - Short: "Outputs the located cosign image reference. This is the location where cosign stores the specified artifact type.", + 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 { diff --git a/cmd/cosign/cli/triangulate/triangulate.go b/cmd/cosign/cli/triangulate/triangulate.go index 006c94277e6..3a5e558235e 100644 --- a/cmd/cosign/cli/triangulate/triangulate.go +++ b/cmd/cosign/cli/triangulate/triangulate.go @@ -21,9 +21,9 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/cosign" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" ) func MungeCmd(ctx context.Context, regOpts options.RegistryOptions, imageRef string, attachmentType string) error { diff --git a/cmd/cosign/cli/trustedroot.go b/cmd/cosign/cli/trustedroot.go index 70fa04bc53f..be837d8d429 100644 --- a/cmd/cosign/cli/trustedroot.go +++ b/cmd/cosign/cli/trustedroot.go @@ -18,8 +18,8 @@ package cli import ( "context" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/trustedroot" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/trustedroot" "github.com/spf13/cobra" ) @@ -52,23 +52,28 @@ Each service is specified via a repeatable flag (--fulcio, --rekor, --ctfe, --ts --out trusted-root.json`, RunE: func(cmd *cobra.Command, _ []string) error { trCreateCmd := &trustedroot.CreateCmd{ - FulcioSpecs: o.Fulcio, - RekorSpecs: o.Rekor, - CTFESpecs: o.CTFE, - TSASpecs: o.TSA, - CertChain: o.CertChain, - FulcioURI: o.FulcioURI, - CtfeKeyPath: o.CtfeKeyPath, - CtfeStartTime: o.CtfeStartTime, - CtfeEndTime: o.CtfeEndTime, - CtfeURL: o.CtfeURL, - Out: o.Out, - RekorKeyPath: o.RekorKeyPath, - RekorStartTime: o.RekorStartTime, - RekorEndTime: o.RekorEndTime, - RekorURL: o.RekorURL, - TSACertChainPath: o.TSACertChainPath, - TSAURI: o.TSAURI, + FulcioSpecs: o.Fulcio, + RekorSpecs: o.Rekor, + CTFESpecs: o.CTFE, + TSASpecs: o.TSA, + CertChain: o.CertChain, + FulcioURI: o.FulcioURI, + CtfeKeyPath: o.CtfeKeyPath, + CtfeStartTime: o.CtfeStartTime, + CtfeEndTime: o.CtfeEndTime, + CtfeURL: o.CtfeURL, + Out: o.Out, + RekorKeyPath: o.RekorKeyPath, + RekorStartTime: o.RekorStartTime, + RekorEndTime: o.RekorEndTime, + RekorURL: o.RekorURL, + TSACertChainPath: o.TSACertChainPath, + TSAURI: o.TSAURI, + WithDefaultServices: o.WithDefaultServices, + NoDefaultFulcio: o.NoDefaultFulcio, + NoDefaultCTFE: o.NoDefaultCTFE, + NoDefaultTSA: o.NoDefaultTSA, + NoDefaultRekor: o.NoDefaultRekor, } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) diff --git a/cmd/cosign/cli/trustedroot/trustedroot.go b/cmd/cosign/cli/trustedroot/trustedroot.go index 762433cf3e2..62517cc1ab7 100644 --- a/cmd/cosign/cli/trustedroot/trustedroot.go +++ b/cmd/cosign/cli/trustedroot/trustedroot.go @@ -26,12 +26,13 @@ import ( "encoding/hex" "encoding/pem" "fmt" + "maps" "os" "strings" "time" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/rekor-tiles/pkg/note" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/rekor-tiles/v2/pkg/note" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore/pkg/cryptoutils" ) @@ -42,6 +43,13 @@ type CreateCmd struct { CTFESpecs []string TSASpecs []string + WithDefaultServices bool + NoDefaultFulcio bool + NoDefaultCTFE bool + NoDefaultTSA bool + NoDefaultRekor bool + + // Deprecated flags CertChain []string FulcioURI []string CtfeKeyPath []string @@ -89,6 +97,25 @@ func (c *CreateCmd) Exec(_ context.Context) error { return fmt.Errorf("cannot use --tsa and old tsa flags at the same time") } + if c.WithDefaultServices { + tr, err := cosign.TrustedRoot() + if err != nil { + return fmt.Errorf("getting default trusted root: %w", err) + } + if !c.NoDefaultFulcio { + fulcioCertAuthorities = append(fulcioCertAuthorities, tr.FulcioCertificateAuthorities()...) + } + if !c.NoDefaultCTFE { + maps.Copy(ctLogs, tr.CTLogs()) + } + if !c.NoDefaultRekor { + maps.Copy(rekorTransparencyLogs, tr.RekorLogs()) + } + if !c.NoDefaultTSA { + timestampAuthorities = append(timestampAuthorities, tr.TimestampingAuthorities()...) + } + } + if fulcioSpecUsed { for _, spec := range c.FulcioSpecs { fulcioAuthority, err := parseFulcioSpec(spec) @@ -97,7 +124,7 @@ func (c *CreateCmd) Exec(_ context.Context) error { } fulcioCertAuthorities = append(fulcioCertAuthorities, fulcioAuthority) } - } else { + } else if deprecatedFulcioFlagsUsed { for i := 0; i < len(c.CertChain); i++ { var fulcioURI string if i < len(c.FulcioURI) { @@ -117,9 +144,17 @@ func (c *CreateCmd) Exec(_ context.Context) error { if err != nil { return fmt.Errorf("parsing ctfe spec: %w", err) } + // Static CT needs origin for checkpoint ID + kvs, _ := parseKVs(spec) + if origin, ok := kvs["origin"]; ok { + id, ctLog.ID, err = getCheckpointID(origin, ctLog.PublicKey) + if err != nil { + return err + } + } ctLogs[id] = ctLog } - } else { + } else if deprecatedCTFEFlagsUsed { for i := 0; i < len(c.CtfeKeyPath); i++ { ctLogPubKey, id, idBytes, err := getPubKey(c.CtfeKeyPath[i]) // #nosec G601 if err != nil { @@ -176,7 +211,7 @@ func (c *CreateCmd) Exec(_ context.Context) error { } rekorTransparencyLogs[id] = rekorLog } - } else { + } else if deprecatedRekorFlagsUsed { for i := 0; i < len(c.RekorKeyPath); i++ { keyParts := strings.SplitN(c.RekorKeyPath[i], ",", 2) // #nosec G601 keyPath := keyParts[0] @@ -236,7 +271,7 @@ func (c *CreateCmd) Exec(_ context.Context) error { } timestampAuthorities = append(timestampAuthorities, tsa) } - } else { + } else if deprecatedTSAFlagsUsed { for i := 0; i < len(c.TSACertChainPath); i++ { var tsaURI string // #nosec G601 if i < len(c.TSAURI) { diff --git a/cmd/cosign/cli/trustedroot/trustedroot_test.go b/cmd/cosign/cli/trustedroot/trustedroot_test.go index 1882fadc659..857fa337979 100644 --- a/cmd/cosign/cli/trustedroot/trustedroot_test.go +++ b/cmd/cosign/cli/trustedroot/trustedroot_test.go @@ -151,12 +151,13 @@ func TestCreateCmd(t *testing.T) { rekorV1Spec := fmt.Sprintf("url=https://rekor.sigstore.example,public-key=%s,start-time=%s,end-time=%s", rekorV1KeyPath, startTime, endTime) rekorV2Spec := fmt.Sprintf("url=https://rekor.sigstore.example,public-key=%s,start-time=%s,origin=rekor-v2.sigstore.example", rekorV2KeyPath, startTime) ctfeSpec := fmt.Sprintf("url=https://ctfe.sigstore.example,public-key=%s,start-time=%s", ctfeKeyPath, startTime) + ctfeV2Spec := fmt.Sprintf("url=https://ctfe.sigstore.example,public-key=%s,start-time=%s,origin=ctfe.sigstore.example", ctfeKeyPath, startTime) trustedrootCreate := CreateCmd{ FulcioSpecs: []string{fulcioSpec}, RekorSpecs: []string{rekorV1Spec, rekorV2Spec}, TSASpecs: []string{tsaSpec}, - CTFESpecs: []string{ctfeSpec}, + CTFESpecs: []string{ctfeSpec, ctfeV2Spec}, Out: outPath, } @@ -186,7 +187,7 @@ func TestCreateCmd(t *testing.T) { } ctfeLogs := tr.CTLogs() - if len(ctfeLogs) != 1 { + if len(ctfeLogs) != 2 { t.Fatalf("unexpected number of ctfe logs: %d", len(ctfeLogs)) } @@ -237,7 +238,7 @@ func TestCreateCmd(t *testing.T) { "certificate-chain": fulcioChainPath, } delete(specMap, key) - var pairs []string + pairs := make([]string, 0, len(specMap)) for k, v := range specMap { pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) } @@ -262,7 +263,7 @@ func TestCreateCmd(t *testing.T) { "start-time": startTime, } delete(specMap, key) - var pairs []string + pairs := make([]string, 0, len(specMap)) for k, v := range specMap { pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) } @@ -286,7 +287,7 @@ func TestCreateCmd(t *testing.T) { "certificate-chain": tsaChainPath, } delete(specMap, key) - var pairs []string + pairs := make([]string, 0, len(specMap)) for k, v := range specMap { pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) } @@ -311,7 +312,7 @@ func TestCreateCmd(t *testing.T) { "start-time": startTime, } delete(specMap, key) - var pairs []string + pairs := make([]string, 0, len(specMap)) for k, v := range specMap { pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) } diff --git a/cmd/cosign/cli/upload.go b/cmd/cosign/cli/upload.go index 69c8530d036..b7946a44048 100644 --- a/cmd/cosign/cli/upload.go +++ b/cmd/cosign/cli/upload.go @@ -18,15 +18,16 @@ package cli import ( "flag" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/upload" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/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", + Use: "upload", + Short: "Provides utilities for uploading artifacts to a registry", + Deprecated: "upload will be removed in v4.0.0 (see https://github.com/sigstore/cosign/issues/4696). Instead, please use `oras blob push` for uploading artifacts to a registry", } cmd.AddCommand( @@ -42,7 +43,7 @@ func uploadBlob() *cobra.Command { cmd := &cobra.Command{ Use: "blob", - Short: "Upload one or more blobs to the supplied container image address.", + 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 diff --git a/cmd/cosign/cli/upload/blob.go b/cmd/cosign/cli/upload/blob.go index 901775a9230..0f64ef92990 100644 --- a/cmd/cosign/cli/upload/blob.go +++ b/cmd/cosign/cli/upload/blob.go @@ -24,8 +24,8 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + cremote "github.com/sigstore/cosign/v3/pkg/cosign/remote" ) func BlobCmd(ctx context.Context, regOpts options.RegistryOptions, files []cremote.File, annotations map[string]string, contentType, imageRef string) error { diff --git a/cmd/cosign/cli/upload/wasm.go b/cmd/cosign/cli/upload/wasm.go index d1b8c0d2de6..b515f5afdb9 100644 --- a/cmd/cosign/cli/upload/wasm.go +++ b/cmd/cosign/cli/upload/wasm.go @@ -22,9 +22,9 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "github.com/sigstore/cosign/v3/pkg/types" ) func WasmCmd(ctx context.Context, regOpts options.RegistryOptions, wasmPath, imageRef string) error { diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index a0e4ca7e20f..27b2d219fc6 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -20,9 +20,9 @@ import ( "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/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v3/internal/ui" "github.com/spf13/cobra" ) @@ -50,23 +50,11 @@ against the transparency log.`, # 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 a trusted root + cosign verify --trusted-root trusted_root.json # verify image with public key provided by URL cosign verify --key https://host.for/[FILE] @@ -109,6 +97,7 @@ against the transparency log.`, v := &verify.VerifyCommand{ RegistryOptions: o.Registry, CertVerifyOptions: o.CertVerify, + CommonVerifyOptions: o.CommonVerifyOptions, CheckClaims: o.CheckClaims, KeyRef: o.Key, CertRef: o.CertVerify.Cert, @@ -138,6 +127,7 @@ against the transparency log.`, MaxWorkers: o.CommonVerifyOptions.MaxWorkers, ExperimentalOCI11: o.CommonVerifyOptions.ExperimentalOCI11, UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps, + NewBundleFormat: o.CommonVerifyOptions.NewBundleFormat, } if o.CommonVerifyOptions.MaxWorkers == 0 { @@ -216,6 +206,11 @@ against the transparency log.`, o.CommonVerifyOptions.IgnoreTlog = true } + hashAlgorithm, err := o.SignatureDigest.HashAlgorithm() + if err != nil { + return err + } + v := &verify.VerifyAttestationCommand{ RegistryOptions: o.Registry, CommonVerifyOptions: o.CommonVerifyOptions, @@ -245,6 +240,7 @@ against the transparency log.`, TSACertChainPath: o.CommonVerifyOptions.TSACertChainPath, IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog, MaxWorkers: o.CommonVerifyOptions.MaxWorkers, + HashAlgorithm: hashAlgorithm, UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps, } @@ -274,51 +270,40 @@ func VerifyBlob() *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. +You may specify either a key, a bundle (optionally with trusted root), or a kms reference to verify against. + If you use a key, bundle, or trusted root, 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 preferred way to provide verification material is via a Sigstore bundle using --bundle, +which contains the signature, certificate, and transparency log proof. 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 + Example: ` cosign verify-blob --bundle --certificate-identity --certificate-oidc-issuer - # 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 with a bundle and trusted root + cosign verify-blob --bundle artifact.sigstore.json --trusted-root trusted_root.json - # Verify a signature from an environment variable - cosign verify-blob --key cosign.pub --signature $sig msg + # Verify a blob (keyless) + cosign verify-blob --bundle artifact.sigstore.json --certificate-identity foo@example.com --certificate-oidc-issuer https://accounts.google.com - # verify a signature with public key provided by URL - cosign verify-blob --key https://host.for/ --signature $sig msg + # Verify a blob with an on-disk public key + cosign verify-blob --bundle artifact.sigstore.json --key cosign.pub - # verify a signature with signature and key provided by URL - cosign verify-blob --key https://host.for/ --signature https://example.com/ + # Verify a blob against Azure Key Vault + cosign verify-blob --bundle artifact.sigstore.json --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] - # Verify a signature against Azure Key Vault - cosign verify-blob --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] --signature $sig + # Verify a blob against AWS KMS + cosign verify-blob --bundle artifact.sigstore.json --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] - # Verify a signature against AWS KMS - cosign verify-blob --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] --signature $sig + # Verify a blob against Google Cloud KMS + cosign verify-blob --bundle artifact.sigstore.json --key gcpkms://projects/[PROJECT ID]/locations/[LOCATION]/keyRings/[KEYRING]/cryptoKeys/[KEY] - # 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 blob against Hashicorp Vault + cosign verify-blob --bundle artifact.sigstore.json --key hashivault://[KEY] - # Verify a signature against Hashicorp Vault - cosign verify-blob --key hashivault://[KEY] --signature $sig + # Verify a blob against GitLab with project name + cosign verify-blob --bundle artifact.sigstore.json --key gitlab://[OWNER]/[PROJECT_NAME] - # 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 + # Verify a blob against GitLab with project id + cosign verify-blob --bundle artifact.sigstore.json --key gitlab://[PROJECT_ID] `, Args: cobra.ExactArgs(1), @@ -328,6 +313,11 @@ The blob may be specified as a path to a file or - for stdin.`, o.CommonVerifyOptions.IgnoreTlog = true } + hashAlgorithm, err := o.SignatureDigest.HashAlgorithm() + if err != nil { + return err + } + ko := options.KeyOpts{ KeyRef: o.Key, Sk: o.SecurityKey.Use, @@ -357,6 +347,7 @@ The blob may be specified as a path to a file or - for stdin.`, IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog, UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps, TrustedRootPath: o.CommonVerifyOptions.TrustedRootPath, + HashAlgorithm: hashAlgorithm, } ctx, cancel := context.WithTimeout(cmd.Context(), ro.Timeout) @@ -383,12 +374,27 @@ func VerifyBlobAttestation() *cobra.Command { 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. +Signed material is provided with the --bundle flag. The blob may be specified as a path to a file.`, - Example: ` cosign verify-blob-attestation (--key ||) --signature [path to BLOB] + Example: ` cosign verify-blob-attestation --bundle --certificate-identity --certificate-oidc-issuer + + # Verify a blob attestation (keyless) + cosign verify-blob-attestation --bundle artifact.sigstore.json --certificate-identity foo@example.com --certificate-oidc-issuer https://accounts.google.com + + # Verify a blob attestation with a public key + cosign verify-blob-attestation --bundle artifact.sigstore.json --key cosign.pub + + # Verify a blob attestation with Azure KMS + cosign verify-blob-attestation --bundle artifact.sigstore.json --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] + + # Verify a blob attestation with AWS KMS + cosign verify-blob-attestation --bundle artifact.sigstore.json --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] - # Verify a simple blob attestation with a DSSE style signature - cosign verify-blob-attestation --key cosign.pub (--signature |)[path to BLOB] + # Verify a blob attestation with GCP KMS + cosign verify-blob-attestation --bundle artifact.sigstore.json --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] + + # Verify a blob attestation with Hashicorp Vault + cosign verify-blob-attestation --bundle artifact.sigstore.json --key hashivault://[KEY] `, @@ -399,6 +405,11 @@ The blob may be specified as a path to a file.`, o.CommonVerifyOptions.IgnoreTlog = true } + hashAlgorithm, err := o.SignatureDigest.HashAlgorithm() + if err != nil { + return err + } + ko := options.KeyOpts{ KeyRef: o.Key, Sk: o.SecurityKey.Use, @@ -432,6 +443,7 @@ The blob may be specified as a path to a file.`, TrustedRootPath: o.CommonVerifyOptions.TrustedRootPath, Digest: o.Digest, DigestAlg: o.DigestAlg, + HashAlgorithm: hashAlgorithm, } // We only use the blob if we are checking claims. if o.CheckClaims && len(args) == 0 && (o.Digest == "" || o.DigestAlg == "") { diff --git a/cmd/cosign/cli/verify/common.go b/cmd/cosign/cli/verify/common.go new file mode 100644 index 00000000000..507fc0611a4 --- /dev/null +++ b/cmd/cosign/cli/verify/common.go @@ -0,0 +1,478 @@ +// 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 verify + +import ( + "bytes" + "context" + "crypto" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "os" + "reflect" + + "github.com/sigstore/cosign/v3/cmd/cosign/cli/fulcio" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/rekor" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/blob" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/pivkey" + "github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key" + "github.com/sigstore/cosign/v3/pkg/oci" + csignature "github.com/sigstore/cosign/v3/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" +) + +// CheckSigstoreBundleUnsupportedOptions checks for incompatible settings on any Verify* command struct when NewBundleFormat is used. +func CheckSigstoreBundleUnsupportedOptions(cmd any, verifyOfflineWithKey bool, co *cosign.CheckOpts) error { + if !co.NewBundleFormat { + return nil + } + fieldToErr := map[string]string{ + "CertRef": "certificate must be in bundle and may not be provided using --certificate", + "CertChain": "certificate chain must be in bundle and may not be provided using --certificate-chain", + "CARoots": "CA roots/intermediates must be provided using --trusted-root", + "CAIntermediates": "CA roots/intermediates must be provided using --trusted-root", + "TSACertChainPath": "TSA certificate chain path may only be provided using --trusted-root", + "RFC3161TimestampPath": "RFC3161 timestamp may not be provided using --rfc3161-timestamp", + "SigRef": "signature may not be provided using --signature", + "SCTRef": "SCT may not be provided using --sct", + } + v := reflect.ValueOf(cmd) + for f, e := range fieldToErr { + if field := v.FieldByName(f); field.IsValid() && field.String() != "" { + return fmt.Errorf("unsupported: %s when using --new-bundle-format", e) + } + } + if co.TrustedMaterial == nil && !verifyOfflineWithKey { + return fmt.Errorf("trusted root is required when using new bundle format") + } + return nil +} + +// LoadVerifierFromKeyOrCert returns either a signature.Verifier or a certificate from the provided flags to use for verifying an artifact. +// In the case of certain types of keys, it returns a close function that must be called by the calling method. +func LoadVerifierFromKeyOrCert(ctx context.Context, keyRef, slot, certRef, certChain string, hashAlgorithm crypto.Hash, sk, withGetCert bool, co *cosign.CheckOpts) (signature.Verifier, *x509.Certificate, func(), error) { + var sigVerifier signature.Verifier + var err error + switch { + case keyRef != "": + sigVerifier, err = csignature.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm) + if err != nil { + return nil, nil, nil, fmt.Errorf("loading public key: %w", err) + } + pkcs11Key, ok := sigVerifier.(*pkcs11key.Key) + closeSV := func() {} + if ok { + closeSV = pkcs11Key.Close + } + return sigVerifier, nil, closeSV, nil + case sk: + sk, err := pivkey.GetKeyWithSlot(slot) + if err != nil { + return nil, nil, nil, fmt.Errorf("opening piv token: %w", err) + } + sigVerifier, err = sk.Verifier() + if err != nil { + sk.Close() + return nil, nil, nil, fmt.Errorf("initializing piv token verifier: %w", err) + } + return sigVerifier, nil, sk.Close, nil + case certRef != "": + cert, err := loadCertFromFileOrURL(certRef) + if err != nil { + return nil, nil, nil, fmt.Errorf("loading cert: %w", err) + } + if withGetCert { + return nil, cert, func() {}, nil + } + if certChain == "" { + sigVerifier, err = cosign.ValidateAndUnpackCert(cert, co) + if err != nil { + return nil, nil, nil, fmt.Errorf("validating cert: %w", err) + } + return sigVerifier, nil, func() {}, nil + } + chain, err := loadCertChainFromFileOrURL(certChain) + if err != nil { + return nil, nil, nil, fmt.Errorf("loading cert chain: %w", err) + } + sigVerifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) + if err != nil { + return nil, nil, nil, fmt.Errorf("validating cert with chain: %w", err) + } + return sigVerifier, nil, func() {}, nil + } + return nil, nil, func() {}, nil +} + +// SetLegacyClientsAndKeys sets up TSA and rekor clients and keys for TSA, rekor, and CT log. +// It may perform an online fetch of keys, so using trusted root instead of these TUF v1 methods is recommended. +// It takes a CheckOpts as input and modifies it. +func SetLegacyClientsAndKeys(ctx context.Context, ignoreTlog, shouldVerifySCT, keylessVerification bool, rekorURL, tsaCertChain, certChain, caRoots, caIntermediates string, co *cosign.CheckOpts) error { + var err error + if !ignoreTlog && !co.NewBundleFormat && rekorURL != "" { + co.RekorClient, err = rekor.NewClient(rekorURL) + if err != nil { + return fmt.Errorf("creating rekor client: %w", err) + } + } + // If trusted material is set, we don't need to fetch disparate keys. + if co.TrustedMaterial != nil { + return nil + } + if co.UseSignedTimestamps { + tsaCertificates, err := cosign.GetTSACerts(ctx, tsaCertChain, cosign.GetTufTargets) + if err != nil { + return fmt.Errorf("loading TSA certificates: %w", err) + } + co.TSACertificate = tsaCertificates.LeafCert + co.TSARootCertificates = tsaCertificates.RootCert + co.TSAIntermediateCertificates = tsaCertificates.IntermediateCerts + } + if !ignoreTlog { + co.RekorPubKeys, err = cosign.GetRekorPubs(ctx) + if err != nil { + return fmt.Errorf("getting rekor public keys: %w", err) + } + } + if shouldVerifySCT { + co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) + if err != nil { + return fmt.Errorf("getting ctlog public keys: %w", err) + } + } + if keylessVerification { + if err := loadCertsKeylessVerification(certChain, caRoots, caIntermediates, co); err != nil { + return fmt.Errorf("loading certs for keyless verification: %w", err) + } + } + return nil +} + +// SetTrustedMaterial sets TrustedMaterial on CheckOpts, either from the provided trusted root path or from TUF. +// It does not set TrustedMaterial if the user provided trusted material via other flags or environment variables. +func SetTrustedMaterial(ctx context.Context, trustedRootPath, certChain, caRoots, caIntermediates, tsaCertChainPath string, verifyOnlyWithKey bool, co *cosign.CheckOpts) error { + var err error + if trustedRootPath != "" { + co.TrustedMaterial, err = root.NewTrustedRootFromPath(trustedRootPath) + if err != nil { + return fmt.Errorf("loading trusted root: %w", err) + } + return nil + } + if verifyOnlyWithKey { + return nil + } + if options.NOf(certChain, caRoots, caIntermediates, tsaCertChainPath) == 0 && + env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" && + env.Getenv(env.VariableSigstoreRootFile) == "" && + env.Getenv(env.VariableSigstoreRekorPublicKey) == "" && + env.Getenv(env.VariableSigstoreTSACertificateFile) == "" { + co.TrustedMaterial, err = cosign.TrustedRoot() + if err != nil { + if co.NewBundleFormat { + return fmt.Errorf("getting trusted root from TUF for new bundle verification: %w", err) + } + ui.Warnf(ctx, "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) + } + } + return nil +} + +// PrintVerificationHeader prints boilerplate information after successful verification. +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 +} + +// No trusted root is needed if verification doesn't require Rekor or +// signed timestamps, and a key is explicitly provided instead of using +// a Fulcio certificate either via a key or certificate reference or security key. +func verifyOfflineWithKey(keyRef, certRef string, sk bool, co *cosign.CheckOpts) bool { + return (keyRef != "" || certRef != "" || sk) && co.IgnoreTlog && !co.UseSignedTimestamps +} + +// 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 +} diff --git a/cmd/cosign/cli/verify/common_test.go b/cmd/cosign/cli/verify/common_test.go new file mode 100644 index 00000000000..490d5b04126 --- /dev/null +++ b/cmd/cosign/cli/verify/common_test.go @@ -0,0 +1,82 @@ +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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" + "path/filepath" + "strings" + "testing" + + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/env" +) + +func TestSetTrustedMaterialNewBundleTUFError(t *testing.T) { + setBrokenTrustedRootTUFEnv(t) + + co := &cosign.CheckOpts{NewBundleFormat: true} + var err error + stderr := ui.RunWithTestCtx(func(ctx context.Context, _ ui.WriteFunc) { + err = SetTrustedMaterial(ctx, "", "", "", "", "", false, co) + }) + + if err == nil { + t.Fatal("expected trusted root TUF error") + } + if !strings.Contains(err.Error(), "getting trusted root from TUF for new bundle verification") { + t.Fatalf("expected new bundle trusted root error, got %v", err) + } + if !strings.Contains(err.Error(), "error reading root.json given by TUF_ROOT_JSON") { + t.Fatalf("expected underlying TUF error, got %v", err) + } + if co.TrustedMaterial != nil { + t.Fatal("expected TrustedMaterial to remain unset") + } + if stderr != "" { + t.Fatalf("expected no warning when returning new bundle error, got %q", stderr) + } +} + +func TestSetTrustedMaterialLegacyTUFFallback(t *testing.T) { + setBrokenTrustedRootTUFEnv(t) + + co := &cosign.CheckOpts{} + var err error + stderr := ui.RunWithTestCtx(func(ctx context.Context, _ ui.WriteFunc) { + err = SetTrustedMaterial(ctx, "", "", "", "", "", false, co) + }) + + if err != nil { + t.Fatalf("expected legacy trusted material fallback, got %v", err) + } + if co.TrustedMaterial != nil { + t.Fatal("expected TrustedMaterial to remain unset") + } + if !strings.Contains(stderr, "Could not fetch trusted_root.json from the TUF repository") { + t.Fatalf("expected legacy fallback warning, got %q", stderr) + } +} + +func setBrokenTrustedRootTUFEnv(t *testing.T) { + t.Helper() + t.Setenv(env.VariableTUFRootDir.String(), t.TempDir()) + t.Setenv(env.VariableTUFMirror.String(), "https://example.com/tuf") + t.Setenv(env.VariableTUFRootJSON.String(), filepath.Join(t.TempDir(), "missing-root.json")) + t.Setenv(env.VariableSigstoreCTLogPublicKeyFile.String(), "") + t.Setenv(env.VariableSigstoreRootFile.String(), "") + t.Setenv(env.VariableSigstoreRekorPublicKey.String(), "") + t.Setenv(env.VariableSigstoreTSACertificateFile.String(), "") +} diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 01a3ea60067..ef6b1596ee9 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -16,35 +16,25 @@ 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/env" - "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/in-toto/in-toto-golang/in_toto" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" + cosignError "github.com/sigstore/cosign/v3/cmd/cosign/errors" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/static" + sigs "github.com/sigstore/cosign/v3/pkg/signature" + "github.com/sigstore/protobuf-specs/gen/pb-go/dsse" "github.com/sigstore/sigstore/pkg/signature/payload" ) @@ -85,6 +75,7 @@ type VerifyCommand struct { IgnoreTlog bool MaxWorkers int ExperimentalOCI11 bool + NewBundleFormat bool } // Exec runs the verification command @@ -107,6 +98,11 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { c.HashAlgorithm = crypto.SHA256 } + // key and cert identity are mutually exclusive + if options.NOf(c.KeyRef, c.CertIdentity, c.CertIdentityRegexp) > 1 { + return &options.KeyAndIdentityParseError{} + } + var identities []cosign.Identity if c.KeyRef == "" { identities, err = c.Identities() @@ -119,6 +115,9 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if err != nil { return fmt.Errorf("constructing client options: %w", err) } + if c.AllowHTTPRegistry || c.AllowInsecure { + c.NameOptions = append(c.NameOptions, name.Insecure) + } co := &cosign.CheckOpts{ Annotations: c.Annotations.Annotations, @@ -139,149 +138,64 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps, NewBundleFormat: c.NewBundleFormat, } + vOfflineKey := verifyOfflineWithKey(c.KeyRef, c.CertRef, c.Sk, co) - if c.TrustedRootPath != "" { - co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) + // Auto-detect bundle format for local images + if c.LocalImage { + hasBundles, err := cosign.HasLocalBundles(images[0]) if err != nil { - return fmt.Errorf("loading trusted root: %w", err) + return fmt.Errorf("checking local image format: %w", err) } - } else if options.NOf(c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath) == 0 && - env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" && - env.Getenv(env.VariableSigstoreRootFile) == "" && - env.Getenv(env.VariableSigstoreRekorPublicKey) == "" && - env.Getenv(env.VariableSigstoreTSACertificateFile) == "" { - // don't overrule the user's intentions if they provided their own keys - co.TrustedMaterial, err = cosign.TrustedRoot() - if err != nil { - ui.Warnf(ctx, "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) + co.NewBundleFormat = hasBundles + } else { + ref, err := name.ParseReference(images[0], c.NameOptions...) + if err == nil && c.NewBundleFormat { + newBundles, _, err := cosign.GetBundles(ctx, ref, co.RegistryClientOpts, c.NameOptions...) + if len(newBundles) == 0 || err != nil { + co.NewBundleFormat = false + } } } - if c.CheckClaims { - co.ClaimVerifier = cosign.SimpleClaimVerifier + err = SetTrustedMaterial(ctx, c.TrustedRootPath, c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath, vOfflineKey, co) + if err != nil { + return fmt.Errorf("setting trusted material: %w", err) } - // If we are using signed timestamps and there is no trusted root, we need to load the TSA certificates - if co.UseSignedTimestamps && co.TrustedMaterial == nil { - 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 err = CheckSigstoreBundleUnsupportedOptions(*c, vOfflineKey, co); err != nil { + return err } - 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 - } - if co.TrustedMaterial == nil { - // 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 co.TrustedMaterial == nil && keylessVerification(c.KeyRef, c.Sk) { - if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { - return err + if c.CheckClaims { + if co.NewBundleFormat { + co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier + } else { + co.ClaimVerifier = cosign.SimpleClaimVerifier } } - keyRef := c.KeyRef - certRef := c.CertRef + err = SetLegacyClientsAndKeys(ctx, c.IgnoreTlog, shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk), keylessVerification(c.KeyRef, c.Sk), c.RekorURL, c.TSACertChainPath, c.CertChain, c.CARoots, c.CAIntermediates, co) + if err != nil { + return fmt.Errorf("setting up clients and keys: %w", err) + } - // Ignore Signed Certificate Timestamp if the flag is set or a key is provided - if co.TrustedMaterial == nil && 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) - } + // User provides a key or certificate. Otherwise, verification requires a Fulcio certificate + // provided in an attached bundle or OCI annotation. LoadVerifierFromKeyOrCert must be called + // after initializing trust material in order to verify certificate chain. + var closeSV func() + co.SigVerifier, _, closeSV, err = LoadVerifierFromKeyOrCert(ctx, c.KeyRef, c.Slot, c.CertRef, c.CertChain, c.HashAlgorithm, c.Sk, false, co) + if err != nil { + return fmt.Errorf("loading verifier from key opts: %w", err) } + defer closeSV() - // 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 c.CertRef != "" && c.SCTRef != "" { + sct, err := os.ReadFile(filepath.Clean(c.SCTRef)) 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 - if co.TrustedMaterial == nil { - 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 + return fmt.Errorf("reading sct from file: %w", err) } - 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.SCT = sct } - 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, @@ -292,10 +206,20 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { fulcioVerified := (co.SigVerifier == nil) for _, img := range images { + var verified []oci.Signature + var bundleVerified bool + if c.LocalImage { - verified, bundleVerified, err := cosign.VerifyLocalImageSignatures(ctx, img, co) - if err != nil { - return err + if co.NewBundleFormat { + verified, bundleVerified, err = cosign.VerifyLocalImageAttestations(ctx, img, co) + if err != nil { + return err + } + } else { + verified, bundleVerified, err = cosign.VerifyLocalImageSignatures(ctx, img, co) + if err != nil { + return err + } } PrintVerificationHeader(ctx, img, co, bundleVerified, fulcioVerified) PrintVerification(ctx, verified, c.Output) @@ -304,290 +228,89 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { 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 co.NewBundleFormat { + // OCI bundle always contains attestation + verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co, c.NameOptions...) + if err != nil { + return err } - 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 + verifiedOutput, err := transformOutput(verified, ref.Name()) + if err == nil { + verified = verifiedOutput } - - if githubWorkflowRepository := ce.GetCertExtensionGithubWorkflowRepository(); githubWorkflowRepository != "" { - ss.Optional[cosign.CertExtensionMap[cosign.CertExtensionGithubWorkflowRepository]] = githubWorkflowRepository - ss.Optional[cosign.CertExtensionGithubWorkflowRepository] = githubWorkflowRepository + } else { + 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) } - 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{}) + verified, bundleVerified, err = cosign.VerifyImageSignatures(ctx, ref, co) + if err != nil { + return cosignError.WrapError(err) } - 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 + PrintVerificationHeader(ctx, ref.Name(), co, bundleVerified, fulcioVerified) + PrintVerification(ctx, verified, c.Output) } - - 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 + return nil } -// 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) +func transformOutput(verified []oci.Signature, name string) (verifiedOutput []oci.Signature, err error) { + for _, v := range verified { + dssePayload, err := v.Payload() if err != nil { - return err + return nil, 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) - } + var dsseEnvelope dsse.Envelope + err = json.Unmarshal(dssePayload, &dsseEnvelope) + if err != nil { + return nil, err + } + if dsseEnvelope.PayloadType != in_toto.PayloadType { + return nil, fmt.Errorf("unable to understand payload type %s", dsseEnvelope.PayloadType) } - case caRootsFile != "": - caRoots, err := loadCertChainFromFileOrURL(caRootsFile) + intotoStatement := &attestation.Statement{} + err = intotoStatement.UnmarshalJSON(dsseEnvelope.Payload) if err != nil { - return err + return nil, err } - co.RootCerts = x509.NewCertPool() - if len(caRoots) > 0 { - for _, cert := range caRoots { - co.RootCerts.AddCert(cert) - } + if len(intotoStatement.Subject) < 1 || len(intotoStatement.Subject[0].Digest) < 1 { + return nil, fmt.Errorf("no intoto subject or digest found") } - 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) - } - } + + var digest string + for k, v := range intotoStatement.Subject[0].Digest { + digest = k + ":" + v } - 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() + annotations := intotoStatement.Subject[0].Annotations.AsMap() + + sci := payload.SimpleContainerImage{ + Critical: payload.Critical{ + Identity: payload.Identity{ + DockerReference: name, + }, + Image: payload.Image{ + DockerManifestDigest: digest, + }, + Type: intotoStatement.PredicateType, + }, + Optional: annotations, + } + p, err := json.Marshal(sci) if err != nil { - return fmt.Errorf("getting Fulcio roots: %w", err) + return nil, err } - co.IntermediateCerts, err = fulcio.GetIntermediates() + att, err := static.NewAttestation(p) if err != nil { - return fmt.Errorf("getting Fulcio intermediates: %w", err) + return nil, err } + verifiedOutput = append(verifiedOutput, att) } - return nil + return verifiedOutput, nil } diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index 39baabce400..a146e3ed4e1 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -17,6 +17,7 @@ package verify import ( "context" + "crypto" "errors" "flag" "fmt" @@ -25,20 +26,13 @@ import ( "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/env" - "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/cue" + "github.com/sigstore/cosign/v3/pkg/cosign/rego" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/policy" ) // VerifyAttestationCommand verifies a signature on a supplied container image @@ -73,6 +67,7 @@ type VerifyAttestationCommand struct { IgnoreTlog bool MaxWorkers int UseSignedTimestamps bool + HashAlgorithm crypto.Hash } // Exec runs the verification command @@ -81,6 +76,16 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return flag.ErrHelp } + // key and cert identity are mutually exclusive + if options.NOf(c.KeyRef, c.CertIdentity, c.CertIdentityRegexp) > 1 { + return &options.KeyAndIdentityParseError{} + } + + // always default to sha256 if the algorithm hasn't been explicitly set + if c.HashAlgorithm == 0 { + c.HashAlgorithm = crypto.SHA256 + } + // We can't have both a key and a security key if options.NOf(c.KeyRef, c.Sk) > 1 { return &options.KeyParseError{} @@ -98,20 +103,8 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e if err != nil { return fmt.Errorf("constructing client options: %w", err) } - - trustedMaterial, err := cosign.TrustedRoot() - if err != nil { - ui.Warnf(ctx, "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) - } - - if options.NOf(c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath) > 0 || - env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) != "" || - env.Getenv(env.VariableSigstoreRootFile) != "" || - env.Getenv(env.VariableSigstoreRekorPublicKey) != "" || - env.Getenv(env.VariableSigstoreTSACertificateFile) != "" { - // trusted_root.json was found, but a cert chain was explicitly provided, or environment variables point to the key material, - // so don't overrule the user's intentions. - trustedMaterial = nil + if c.AllowHTTPRegistry || c.AllowInsecure { + c.NameOptions = append(c.NameOptions, name.Insecure) } co := &cosign.CheckOpts{ @@ -128,157 +121,60 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e MaxWorkers: c.MaxWorkers, UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps, NewBundleFormat: c.NewBundleFormat, - TrustedMaterial: trustedMaterial, - } - if c.CheckClaims { - co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier } + vOfflineKey := verifyOfflineWithKey(c.KeyRef, c.CertRef, c.Sk, co) - if c.TrustedRootPath != "" { - co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) + // Auto-detect bundle format for local images + if c.LocalImage { + hasBundles, err := cosign.HasLocalAttestationBundles(images[0]) if err != nil { - return fmt.Errorf("loading trusted root: %w", err) + return fmt.Errorf("checking local image format: %w", err) } - } else if options.NOf(c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath) == 0 && - env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" && - env.Getenv(env.VariableSigstoreRootFile) == "" && - env.Getenv(env.VariableSigstoreRekorPublicKey) == "" && - env.Getenv(env.VariableSigstoreTSACertificateFile) == "" { - co.TrustedMaterial, err = cosign.TrustedRoot() - if err != nil { - ui.Warnf(ctx, "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) + co.NewBundleFormat = hasBundles + } else { + ref, err := name.ParseReference(images[0], c.NameOptions...) + if err == nil && c.NewBundleFormat { + newBundles, _, err := cosign.GetBundles(ctx, ref, co.RegistryClientOpts, c.NameOptions...) + if len(newBundles) == 0 || err != nil { + co.NewBundleFormat = false + } } } - if c.NewBundleFormat { - if err = checkSigstoreBundleUnsupportedOptions(c); err != nil { - return err - } - if co.TrustedMaterial == nil { - return fmt.Errorf("trusted root is required when using new bundle format") - } + if c.CheckClaims { + co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier } - // Ignore Signed Certificate Timestamp if the flag is set or a key is provided - if co.TrustedMaterial == nil && 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) - } + err = SetTrustedMaterial(ctx, c.TrustedRootPath, c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath, vOfflineKey, co) + if err != nil { + return fmt.Errorf("setting trusted material: %w", err) } - // If we are using signed timestamps, we need to load the TSA certificates - if co.UseSignedTimestamps && co.TrustedMaterial == nil && !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 err = CheckSigstoreBundleUnsupportedOptions(*c, vOfflineKey, co); err != nil { + return err } - 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 - } - if co.TrustedMaterial == nil { - // 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) - } - } + err = SetLegacyClientsAndKeys(ctx, c.IgnoreTlog, shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk), keylessVerification(c.KeyRef, c.Sk), c.RekorURL, c.TSACertChainPath, c.CertChain, c.CARoots, c.CAIntermediates, co) + if err != nil { + return fmt.Errorf("setting up clients and keys: %w", err) } - if co.TrustedMaterial == nil && keylessVerification(c.KeyRef, c.Sk) { - if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { - return err - } + // User provides a key or certificate. Otherwise, verification requires a Fulcio certificate + // provided in an attached bundle or OCI annotation. LoadVerifierFromKeyOrCert must be called + // after initializing trust material in order to verify certificate chain. + var closeSV func() + co.SigVerifier, _, closeSV, err = LoadVerifierFromKeyOrCert(ctx, c.KeyRef, c.Slot, c.CertRef, c.CertChain, c.HashAlgorithm, c.Sk, false, co) + if err != nil { + return fmt.Errorf("loading verifierfrom key opts: %w", err) } + defer closeSV() - 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 c.CertRef != "" && c.SCTRef != "" { + sct, err := os.ReadFile(filepath.Clean(c.SCTRef)) if err != nil { - return fmt.Errorf("loading certificate from reference: %w", err) + return fmt.Errorf("reading sct from file: %w", err) } - if c.CertChain == "" { - // If no certChain is passed, the Fulcio root certificate will be used - if co.TrustedMaterial == nil { - 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 is already loaded from c.TrustedRootPath above, - case c.CARoots != "": - // CA roots + possible intermediates are already loaded into co.RootCerts with the call to - // loadCertsKeylessVerification above. + co.SCT = sct } // NB: There are only 2 kinds of verification right now: @@ -303,7 +199,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return err } - verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co) + verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co, c.NameOptions...) if err != nil { return err } @@ -380,19 +276,3 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e 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 -} diff --git a/cmd/cosign/cli/verify/verify_attestation_test.go b/cmd/cosign/cli/verify/verify_attestation_test.go index ee3c0d99659..6fd9166d7d6 100644 --- a/cmd/cosign/cli/verify/verify_attestation_test.go +++ b/cmd/cosign/cli/verify/verify_attestation_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 the Sigstore Authors. +// 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. @@ -16,9 +16,10 @@ package verify import ( "context" + "errors" "testing" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" ) func TestVerifyAttestationMissingSubject(t *testing.T) { @@ -52,3 +53,60 @@ func TestVerifyAttestationMissingIssuer(t *testing.T) { t.Fatal("verifyAttestation expected 'need --certificate-oidc-issuer'") } } + +func TestVerifyAttestationMutuallyExclusiveFlags(t *testing.T) { + ctx := context.Background() + tts := []struct { + name string + cmd VerifyAttestationCommand + expectedError error + }{ + { + name: "both key and cert identity", + cmd: VerifyAttestationCommand{ + KeyRef: "key.pub", + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "hello@foo.com", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both key and cert identity regexp", + cmd: VerifyAttestationCommand{ + KeyRef: "key.pub", + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentityRegexp: "^.*@foo.com$", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both cert identity and cert identity regexp", + cmd: VerifyAttestationCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "hello@foo.com", + CertIdentityRegexp: "^.*@foo.com$", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both key and security key", + cmd: VerifyAttestationCommand{ + KeyRef: "key.pub", + Sk: true, + }, + expectedError: &options.KeyParseError{}, + }, + } + + for _, tt := range tts { + t.Run(tt.name, func(t *testing.T) { + err := tt.cmd.Exec(ctx, []string{"foo", "bar", "baz"}) + if !errors.Is(err, tt.expectedError) { + t.Fatalf("expected %T, got: %T, %v", tt.expectedError, err, err) + } + }) + } +} diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index 926ad71fe48..1aa36dffccb 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -31,19 +31,14 @@ import ( "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/env" - "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/blob" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci/static" + sigs "github.com/sigstore/cosign/v3/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" @@ -74,15 +69,26 @@ type VerifyBlobCmd struct { Offline bool UseSignedTimestamps bool IgnoreTlog bool + HashAlgorithm crypto.Hash } // nolint func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { + // always default to sha256 if the algorithm hasn't been explicitly set + if c.HashAlgorithm == 0 { + c.HashAlgorithm = crypto.SHA256 + } + // 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 and cert identity are mutually exclusive + if options.NOf(c.KeyRef, c.CertIdentity, c.CertIdentityRegexp) > 1 { + return &options.KeyAndIdentityParseError{} + } + // Key, sk, and cert are mutually exclusive. if options.NOf(c.KeyRef, c.Sk, c.CertRef) > 1 { return &options.PubKeyParseError{} @@ -108,64 +114,30 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps, - NewBundleFormat: c.KeyOpts.NewBundleFormat || checkNewBundle(c.BundlePath), + NewBundleFormat: c.KeyOpts.NewBundleFormat && checkNewBundle(c.BundlePath), } + vOfflineKey := verifyOfflineWithKey(c.KeyRef, c.CertRef, c.Sk, co) - // Keys are optional! + // User provides a key or certificate. Otherwise, verification requires a Fulcio certificate + // provided in an attached bundle or OCI annotation. + var closeSV func() 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 - } + co.SigVerifier, cert, closeSV, err = LoadVerifierFromKeyOrCert(ctx, c.KeyRef, c.Slot, c.CertRef, "", c.HashAlgorithm, c.Sk, true, co) + if err != nil { + return fmt.Errorf("loading verifier from key opts: %w", err) } + defer closeSV() - if c.TrustedRootPath != "" { - co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) - if err != nil { - return fmt.Errorf("loading trusted root: %w", err) - } - } else if options.NOf(c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath) == 0 && - env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" && - env.Getenv(env.VariableSigstoreRootFile) == "" && - env.Getenv(env.VariableSigstoreRekorPublicKey) == "" && - env.Getenv(env.VariableSigstoreTSACertificateFile) == "" { - co.TrustedMaterial, err = cosign.TrustedRoot() - if err != nil { - ui.Warnf(ctx, "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) - } + err = SetTrustedMaterial(ctx, c.TrustedRootPath, c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath, vOfflineKey, co) + if err != nil { + return fmt.Errorf("setting trusted material: %w", 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 { - return fmt.Errorf("trusted root is required when using new bundle format") - } + if err = CheckSigstoreBundleUnsupportedOptions(*c, vOfflineKey, co); err != nil { + return err + } + if co.NewBundleFormat { bundle, err := sgbundle.LoadJSONFromPath(c.BundlePath) if err != nil { return err @@ -205,39 +177,13 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { } 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 && co.TrustedMaterial == nil { - 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 - } - if co.TrustedMaterial == nil { - // 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) - } - } + err = SetLegacyClientsAndKeys(ctx, c.IgnoreTlog, shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk), keylessVerification(c.KeyRef, c.Sk), c.RekorURL, c.TSACertChainPath, c.CertChain, c.CARoots, c.CAIntermediates, co) + if err != nil { + return fmt.Errorf("setting up clients and keys: %w", err) } - if co.TrustedMaterial == nil && keylessVerification(c.KeyRef, c.Sk) { - if err := loadCertsKeylessVerification(c.CertChain, c.CARoots, c.CAIntermediates, co); err != nil { - return err - } - } + opts := make([]static.Option, 0) if c.BundlePath != "" { b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath) if err != nil { @@ -330,14 +276,6 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { opts = append(opts, static.WithCertChain(certPEM, chainPEM)) } - // Ignore Signed Certificate Timestamp if the flag is set or a key is provided - if co.TrustedMaterial == nil && 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 diff --git a/cmd/cosign/cli/verify/verify_blob_attestation.go b/cmd/cosign/cli/verify/verify_blob_attestation.go index 3968ee653c8..62c4f476a5a 100644 --- a/cmd/cosign/cli/verify/verify_blob_attestation.go +++ b/cmd/cosign/cli/verify/verify_blob_attestation.go @@ -1,5 +1,5 @@ // -// Copyright 2022 the Sigstore Authors. +// 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. @@ -18,7 +18,6 @@ package verify import ( "context" "crypto" - "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/hex" @@ -30,22 +29,17 @@ import ( "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/env" - "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + internal "github.com/sigstore/cosign/v3/internal/pkg/cosign" + payloadsize "github.com/sigstore/cosign/v3/internal/pkg/cosign/payload/size" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/blob" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "github.com/sigstore/cosign/v3/pkg/policy" + sigs "github.com/sigstore/cosign/v3/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" ) @@ -80,8 +74,9 @@ type VerifyBlobAttestationCommand struct { SignaturePath string // Path to the signature UseSignedTimestamps bool - Digest string - DigestAlg string + Digest string + DigestAlg string + HashAlgorithm crypto.Hash } // Exec runs the verification command @@ -90,11 +85,21 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st return fmt.Errorf("please specify path to the DSSE envelope signature via --signature or --bundle") } + // always default to sha256 if the algorithm hasn't been explicitly set + if c.HashAlgorithm == 0 { + c.HashAlgorithm = crypto.SHA256 + } + // 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 and cert identity are mutually exclusive + if options.NOf(c.KeyRef, c.CertIdentity, c.CertIdentityRegexp) > 1 { + return &options.KeyAndIdentityParseError{} + } + // We can't have both a key and a security key if options.NOf(c.KeyRef, c.Sk) > 1 { return &options.KeyParseError{} @@ -119,51 +124,42 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st Offline: c.Offline, IgnoreTlog: c.IgnoreTlog, UseSignedTimestamps: c.TSACertChainPath != "" || c.UseSignedTimestamps, - NewBundleFormat: c.NewBundleFormat || checkNewBundle(c.BundlePath), + NewBundleFormat: c.NewBundleFormat && checkNewBundle(c.BundlePath), } + vOfflineKey := verifyOfflineWithKey(c.KeyRef, c.CertRef, c.Sk, co) - // Keys are optional! + // User provides a key or certificate. Otherwise, verification requires a Fulcio certificate + // provided in an attached bundle or OCI annotation. + var closeSV func() 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. + co.SigVerifier, cert, closeSV, err = LoadVerifierFromKeyOrCert(ctx, c.KeyRef, c.Slot, c.CertRef, "", c.HashAlgorithm, c.Sk, true, co) + if err != nil { + return fmt.Errorf("loading verifier from key opts: %w", err) } + defer closeSV() var h v1.Hash var digest []byte if c.CheckClaims { if artifactPath != "" { - if c.Digest != "" && c.DigestAlg != "" { - ui.Warnf(ctx, "Ignoring provided digest and digestAlg in favor of provided blob") + if c.Digest != "" { + ui.Warnf(ctx, "Ignoring provided --digest in favor of provided blob") + } + // For the legacy (non-bundle) verification path we still need to + // compute the digest manually. Pick the hash algorithm to use: + // default to SHA-256 for backward compatibility; honor --digestAlg + // so attestations produced against e.g. SHA-512 can be verified. + hashName := "sha256" + hashAlg := crypto.SHA256 + if c.DigestAlg != "" { + parsed, err := parseBlobHashAlgorithm(c.DigestAlg) + if err != nil { + return err + } + hashName = c.DigestAlg + hashAlg = parsed } - // Get the actual digest of the blob - var payload internal.HashReader + f, err := os.Open(filepath.Clean(artifactPath)) if err != nil { return err @@ -178,14 +174,14 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st return err } - payload = internal.NewHashReader(f, sha256.New()) + payload := internal.NewHashReader(f, hashAlg) if _, err := io.ReadAll(&payload); err != nil { return err } digest = payload.Sum(nil) h = v1.Hash{ Hex: hex.EncodeToString(digest), - Algorithm: "sha256", + Algorithm: hashName, } } else if c.Digest != "" && c.DigestAlg != "" { digest, err = hex.DecodeString(c.Digest) @@ -200,39 +196,77 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier } - if c.TrustedRootPath != "" { - co.TrustedMaterial, err = root.NewTrustedRootFromPath(c.TrustedRootPath) + err = SetTrustedMaterial(ctx, c.TrustedRootPath, c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath, vOfflineKey, co) + if err != nil { + return fmt.Errorf("setting trusted material: %w", err) + } + + if err = CheckSigstoreBundleUnsupportedOptions(*c, vOfflineKey, co); err != nil { + return err + } + + if co.NewBundleFormat { + bundle, err := sgbundle.LoadJSONFromPath(c.BundlePath) + if err != nil { + return err + } + + var policyOpt sgverify.ArtifactPolicyOption + switch { + case !c.CheckClaims: + policyOpt = sgverify.WithoutArtifactUnsafe() + case artifactPath != "": + // Pass the artifact directly so sigstore-go can peek at the bundle + // and choose the correct hash algorithm automatically, rather than + // requiring the caller to supply --digestAlg up-front. + artifactFile, err := os.Open(filepath.Clean(artifactPath)) + if err != nil { + return err + } + defer artifactFile.Close() + policyOpt = sgverify.WithArtifact(artifactFile) + default: + policyOpt = sgverify.WithArtifactDigest(h.Algorithm, digest) + } + + _, err = cosign.VerifyNewBundle(ctx, co, policyOpt, bundle) if err != nil { - return fmt.Errorf("loading trusted root: %w", err) + return err } - } else if options.NOf(c.CertChain, c.CARoots, c.CAIntermediates, c.TSACertChainPath) == 0 && - env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" && - env.Getenv(env.VariableSigstoreRootFile) == "" && - env.Getenv(env.VariableSigstoreRekorPublicKey) == "" && - env.Getenv(env.VariableSigstoreTSACertificateFile) == "" { - co.TrustedMaterial, err = cosign.TrustedRoot() + + sigContent, err := bundle.SignatureContent() if err != nil { - ui.Warnf(ctx, "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err) + return fmt.Errorf("fetching signature content: %w", err) } - } - 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") + envContent := sigContent.EnvelopeContent() + if envContent == nil { + return fmt.Errorf("bundle does not contain a DSSE envelope") } - if co.TrustedMaterial == nil { - return fmt.Errorf("trusted root is required when using new bundle format") + rawEnv := envContent.RawEnvelope() + if rawEnv == nil { + return fmt.Errorf("bundle does not contain a raw DSSE envelope") } - bundle, err := sgbundle.LoadJSONFromPath(c.BundlePath) + payloadBytes, err := json.Marshal(rawEnv) if err != nil { - return err + return fmt.Errorf("marshaling envelope: %w", err) } - _, err = cosign.VerifyNewBundle(ctx, co, sgverify.WithArtifactDigest(h.Algorithm, digest), bundle) + att, err := static.NewAttestation(payloadBytes) if err != nil { - return err + return fmt.Errorf("creating attestation from envelope: %w", 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. + b, gotPredicateType, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, att) + if err != nil { + return fmt.Errorf("converting to consumable policy validation: %w", err) + } + if b == nil { + return fmt.Errorf("invalid predicate type, expected %s got %s", c.PredicateType, gotPredicateType) } ui.Infof(ctx, "Verified OK") @@ -247,45 +281,10 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st } 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 && co.TrustedMaterial == nil { - 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 - } - if co.TrustedMaterial == nil { - // 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 co.TrustedMaterial == nil && 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 co.TrustedMaterial == nil && 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) - } + err = SetLegacyClientsAndKeys(ctx, c.IgnoreTlog, shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk), keylessVerification(c.KeyRef, c.Sk), c.RekorURL, c.TSACertChainPath, c.CertChain, c.CARoots, c.CAIntermediates, co) + if err != nil { + return fmt.Errorf("setting up clients and keys: %w", err) } var encodedSig []byte @@ -296,6 +295,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st } } + opts := make([]static.Option, 0) if c.BundlePath != "" { b, err := cosign.FetchLocalSignedPayloadFromPath(c.BundlePath) if err != nil { @@ -403,10 +403,29 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st // 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 { + b, gotPredicateType, err := policy.AttestationToPayloadJSON(ctx, c.PredicateType, signature) + if err != nil { + return fmt.Errorf("converting to consumable policy validation: %w", err) + } + if b == nil { return fmt.Errorf("invalid predicate type, expected %s got %s", c.PredicateType, gotPredicateType) } fmt.Fprintln(os.Stderr, "Verified OK") return nil } + +// parseBlobHashAlgorithm maps the --digestAlg name used by +// verify-blob-attestation to a crypto.Hash. Only algorithms actually +// supported as in-toto subject digest algorithms are accepted. +func parseBlobHashAlgorithm(name string) (crypto.Hash, error) { + switch name { + case "sha256": + return crypto.SHA256, nil + case "sha384": + return crypto.SHA384, nil + case "sha512": + return crypto.SHA512, nil + } + return 0, fmt.Errorf("unsupported --digestAlg %q; supported values are sha256, sha384, sha512", name) +} diff --git a/cmd/cosign/cli/verify/verify_blob_attestation_test.go b/cmd/cosign/cli/verify/verify_blob_attestation_test.go index ed2e24183ca..0bcaaf95079 100644 --- a/cmd/cosign/cli/verify/verify_blob_attestation_test.go +++ b/cmd/cosign/cli/verify/verify_blob_attestation_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 the Sigstore Authors. +// 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. @@ -15,18 +15,29 @@ package verify import ( + "bytes" "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" "encoding/base64" + "encoding/json" + "errors" "os" "path/filepath" "testing" + ssldsse "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" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/dsse" "google.golang.org/protobuf/encoding/protojson" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" ) const pubkey = `-----BEGIN PUBLIC KEY----- @@ -119,8 +130,9 @@ func TestVerifyBlobAttestation(t *testing.T) { }, { description: "verify new bundle with public key", // From blobSLSAProvenanceSignature - bundlePath: makeLocalAttestNewBundle(t, "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJibG9iIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjY1ODc4MWNkNGVkOWJjYTYwZGFjZDA5ZjdiYjkxNGJiNTE1MDJlOGI1ZDYxOWY1N2YzOWExZDY1MjU5NmNjMjQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6IjIifSwiYnVpbGRUeXBlIjoieCIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", "application/vnd.in-toto+json", "MEUCIA8KjZqkrt90fzBojSwwtj3Bqb41E6ruxQk97TLnpzdYAiEAzOAjOTzyvTHqbpFDAn6zhrg6EZv7kxK5faRoVGYMh2c="), - blobPath: blobPath, + bundlePath: makeLocalAttestNewBundle(t, "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJibG9iIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjY1ODc4MWNkNGVkOWJjYTYwZGFjZDA5ZjdiYjkxNGJiNTE1MDJlOGI1ZDYxOWY1N2YzOWExZDY1MjU5NmNjMjQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6IjIifSwiYnVpbGRUeXBlIjoieCIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", "application/vnd.in-toto+json", "MEUCIA8KjZqkrt90fzBojSwwtj3Bqb41E6ruxQk97TLnpzdYAiEAzOAjOTzyvTHqbpFDAn6zhrg6EZv7kxK5faRoVGYMh2c="), + blobPath: blobPath, + predicateType: "slsaprovenance", }, { description: "verify new bundle with public key - bad sig", // From blobSLSAProvenanceSignature @@ -185,6 +197,7 @@ func TestVerifyBlobAttestationNoCheckClaims(t *testing.T) { description string blobPath string signature string + bundlePath string }{ { description: "verify a predicate", @@ -198,6 +211,11 @@ func TestVerifyBlobAttestationNoCheckClaims(t *testing.T) { 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 a bundle with another blob path", + // From blobSLSAProvenanceSignature + bundlePath: makeLocalAttestNewBundle(t, "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJibG9iIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjY1ODc4MWNkNGVkOWJjYTYwZGFjZDA5ZjdiYjkxNGJiNTE1MDJlOGI1ZDYxOWY1N2YzOWExZDY1MjU5NmNjMjQifX1dLCJwcmVkaWNhdGUiOnsiYnVpbGRlciI6eyJpZCI6IjIifSwiYnVpbGRUeXBlIjoieCIsImludm9jYXRpb24iOnsiY29uZmlnU291cmNlIjp7fX19fQ==", "application/vnd.in-toto+json", "MEUCIA8KjZqkrt90fzBojSwwtj3Bqb41E6ruxQk97TLnpzdYAiEAzOAjOTzyvTHqbpFDAn6zhrg6EZv7kxK5faRoVGYMh2c="), + blobPath: anotherBlobPath, }, { description: "verify a predicate with /dev/null", signature: blobSLSAProvenanceSignature, @@ -220,6 +238,11 @@ func TestVerifyBlobAttestationNoCheckClaims(t *testing.T) { CheckClaims: false, PredicateType: "slsaprovenance", } + 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\"}") + } if err := cmd.Exec(ctx, test.blobPath); err != nil { t.Fatalf("verifyBlobAttestation()= %v", err) } @@ -227,11 +250,262 @@ func TestVerifyBlobAttestationNoCheckClaims(t *testing.T) { } } -func makeLocalAttestNewBundle(t *testing.T, payload, payloadType, sig string) string { - b, err := bundle.MakeProtobufBundle("hint", []byte{}, nil, []byte{}) +func TestVerifyBlobAttestationMutuallyExclusiveFlags(t *testing.T) { + ctx := context.Background() + tts := []struct { + name string + cmd VerifyBlobAttestationCommand + expectedError error + }{ + { + name: "both key and cert identity", + cmd: VerifyBlobAttestationCommand{ + KeyOpts: options.KeyOpts{ + KeyRef: "key.pub", + BundlePath: "bundle.sigstore.json", + }, + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "hello@foo.com", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both key and cert identity regexp", + cmd: VerifyBlobAttestationCommand{ + KeyOpts: options.KeyOpts{ + KeyRef: "key.pub", + BundlePath: "bundle.sigstore.json", + }, + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentityRegexp: "^.*@foo.com$", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both cert identity and cert identity regexp", + cmd: VerifyBlobAttestationCommand{ + KeyOpts: options.KeyOpts{ + BundlePath: "bundle.sigstore.json", + }, + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "hello@foo.com", + CertIdentityRegexp: "^.*@foo.com$", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both key and security key", + cmd: VerifyBlobAttestationCommand{ + KeyOpts: options.KeyOpts{ + KeyRef: "key.pub", + Sk: true, + BundlePath: "bundle.sigstore.json", + }, + }, + expectedError: &options.KeyParseError{}, + }, + } + + for _, tt := range tts { + t.Run(tt.name, func(t *testing.T) { + err := tt.cmd.Exec(ctx, "foo") + if !errors.Is(err, tt.expectedError) { + t.Fatalf("expected %T, got: %T, %v", tt.expectedError, err, err) + } + }) + } +} + +func TestVerifyBlobAttestation_MalformedPayloads(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 := cryptoutils.MarshalPublicKeyToPEM(signer.Public()) + if err != nil { + t.Fatal(err) + } + keyRef := writeBlobFile(t, td, string(pubKeyBytes), "cosign.pub") + blobPath := writeBlobFile(t, td, "blob", "blob") + + dsseSigner := dsse.WrapSigner(signer, "application/vnd.in-toto+json") + + tests := []struct { + description string + predicateType string + setupEnv func() (string, error) + }{ + { + description: "missing predicate type", + predicateType: "", + setupEnv: func() (string, error) { + envBytes, err := dsseSigner.SignMessage(bytes.NewReader([]byte(`{"_type": "https://in-toto.io/Statement/v0.1"}`))) + return string(envBytes), err + }, + }, + { + description: "missing payload field in json", + predicateType: "slsaprovenance", + setupEnv: func() (string, error) { + env := ssldsse.Envelope{ + PayloadType: "application/vnd.in-toto+json", + Payload: "", + } + pae := ssldsse.PAE(env.PayloadType, []byte(env.Payload)) + sigBytes, err := signer.SignMessage(bytes.NewReader(pae)) + if err != nil { + return "", err + } + env.Signatures = []ssldsse.Signature{{Sig: base64.StdEncoding.EncodeToString(sigBytes)}} + envJSONBytes, err := json.Marshal(env) + if err != nil { + return "", err + } + var m map[string]interface{} + if err := json.Unmarshal(envJSONBytes, &m); err != nil { + return "", err + } + delete(m, "payload") + envJSONBytes, err = json.Marshal(m) + return string(envJSONBytes), err + }, + }, + { + description: "payload is valid base64 but inner is not valid in-toto statement", + predicateType: "slsaprovenance", + setupEnv: func() (string, error) { + envBytes, err := dsseSigner.SignMessage(bytes.NewReader([]byte(`not-json`))) + return string(envBytes), err + }, + }, + { + description: "unmarshaling ProvenanceStatementSLSA02", + predicateType: "slsaprovenance", + setupEnv: func() (string, error) { + malformedSlsa := `{"_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://slsa.dev/provenance/v0.2", "predicate": {"builder": []}}` + envBytes, err := dsseSigner.SignMessage(bytes.NewReader([]byte(malformedSlsa))) + return string(envBytes), err + }, + }, + { + description: "unmarshaling CosignVulnStatement", + predicateType: "vuln", + setupEnv: func() (string, error) { + malformedVuln := `{"_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://cosign.sigstore.dev/attestation/vuln/v1", "predicate": {"scanner": []}}` + envBytes, err := dsseSigner.SignMessage(bytes.NewReader([]byte(malformedVuln))) + return string(envBytes), err + }, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + sigStr, err := tc.setupEnv() + if err != nil { + t.Fatalf("failed to setup envelope: %v", err) + } + + t.Run("Standalone Signature", func(t *testing.T) { + sigRef := writeBlobFile(t, td, sigStr, "signature") + + cmd := VerifyBlobAttestationCommand{ + KeyOpts: options.KeyOpts{KeyRef: keyRef}, + SignaturePath: sigRef, + IgnoreTlog: true, + CheckClaims: false, + PredicateType: tc.predicateType, + } + + err = cmd.Exec(ctx, blobPath) + if err == nil { + t.Fatalf("[%s Standalone] FAIL: swallowed error, returned Verified OK", tc.description) + } else { + t.Logf("[%s Standalone] PASS: returned error: %v", tc.description, err) + } + }) + + t.Run("Old Bundle Format", func(t *testing.T) { + bundleData := map[string]interface{}{ + "base64Signature": base64.StdEncoding.EncodeToString([]byte(sigStr)), + "cert": string(pubKeyBytes), + } + bundleBytes, err := json.Marshal(bundleData) + if err != nil { + t.Fatalf("failed to marshal old bundle: %v", err) + } + bundleRef := writeBlobFile(t, td, string(bundleBytes), "bundle.json") + + cmd := VerifyBlobAttestationCommand{ + KeyOpts: options.KeyOpts{KeyRef: keyRef, BundlePath: bundleRef}, + IgnoreTlog: true, + CheckClaims: false, + PredicateType: tc.predicateType, + } + + err = cmd.Exec(ctx, blobPath) + if err == nil { + t.Fatalf("[%s Old Bundle] FAIL: swallowed error, returned Verified OK", tc.description) + } else { + t.Logf("[%s Old Bundle] PASS: returned error: %v", tc.description, err) + } + }) + + t.Run("New Bundle Format", func(t *testing.T) { + var envJSON struct { + PayloadType string `json:"payloadType"` + Payload string `json:"payload"` + Signatures []struct { + Sig string `json:"sig"` + } `json:"signatures"` + } + if err := json.Unmarshal([]byte(sigStr), &envJSON); err != nil { + t.Fatalf("failed to unmarshal dsse envelope: %v", err) + } + if len(envJSON.Signatures) == 0 { + t.Fatalf("no signatures in dsse envelope") + } + bundlePath := makeLocalAttestNewBundle(t, envJSON.Payload, envJSON.PayloadType, envJSON.Signatures[0].Sig) + + cmd := VerifyBlobAttestationCommand{ + KeyOpts: options.KeyOpts{KeyRef: keyRef, BundlePath: bundlePath, NewBundleFormat: true}, + IgnoreTlog: true, + CheckClaims: false, + PredicateType: tc.predicateType, + TrustedRootPath: writeTrustedRootFile(t, td, "{\"mediaType\":\"application/vnd.dev.sigstore.trustedroot+json;version=0.1\"}"), + } + + err = cmd.Exec(ctx, blobPath) + if err == nil { + t.Fatalf("[%s New Bundle] FAIL: swallowed error, returned Verified OK", tc.description) + } else { + t.Logf("[%s New Bundle] PASS: returned error: %v", tc.description, err) + } + }) + }) + } +} + +func makeLocalAttestNewBundle(t *testing.T, payload, payloadType, sig string) string { + b := &protobundle.Bundle{ + MediaType: "application/vnd.dev.sigstore.bundle.v0.3+json", + VerificationMaterial: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_PublicKey{ + PublicKey: &protocommon.PublicKeyIdentifier{ + Hint: "hint", + }, + }, + }, + } decodedPayload, err := base64.StdEncoding.DecodeString(payload) if err != nil { @@ -268,3 +542,36 @@ func makeLocalAttestNewBundle(t *testing.T, payload, payloadType, sig string) st } return bundlePath } + +func TestParseBlobHashAlgorithm(t *testing.T) { + cases := []struct { + name string + want crypto.Hash + wantErr bool + }{ + {name: "sha256", want: crypto.SHA256}, + {name: "sha384", want: crypto.SHA384}, + {name: "sha512", want: crypto.SHA512}, + {name: "sha1", wantErr: true}, + {name: "md5", wantErr: true}, + {name: "", wantErr: true}, + {name: "SHA256", wantErr: true}, // case-sensitive; matches in-toto subject convention + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := parseBlobHashAlgorithm(tc.name) + if tc.wantErr { + if err == nil { + t.Fatalf("expected error for %q, got nil", tc.name) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tc.want { + t.Errorf("got %v, want %v", got, tc.want) + } + }) + } +} diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index 0e54449dd93..ca0e89ba7de 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -26,6 +26,7 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" + "errors" "fmt" "net/http" "net/http/httptest" @@ -37,14 +38,14 @@ import ( "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" + "github.com/go-openapi/swag/conv" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/mock" + "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + sigs "github.com/sigstore/cosign/v3/pkg/signature" + ctypes "github.com/sigstore/cosign/v3/pkg/types" 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" @@ -159,9 +160,21 @@ func TestVerifyBlob(t *testing.T) { unexpiredCertPem, _ := cryptoutils.MarshalCertificateToPEM(unexpiredLeafCert) expiredLeafCert, _ := test.GenerateLeafCertWithExpiration(identity, issuer, - time.Now().Add(-time.Hour), leafPriv, rootCert, rootPriv) + time.Now().Add(-10*time.Minute), leafPriv, rootCert, rootPriv) expiredLeafPem, _ := cryptoutils.MarshalCertificateToPEM(expiredLeafCert) + unrelatedPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + unrelatedSigner, err := signature.LoadECDSASignerVerifier(unrelatedPriv, crypto.SHA256) + if err != nil { + t.Fatal(err) + } + unrelatedLeafCert, _ := test.GenerateLeafCertWithExpiration(identity, issuer, + time.Now(), unrelatedPriv, rootCert, rootPriv) + unrelatedCertPem, _ := cryptoutils.MarshalCertificateToPEM(unrelatedLeafCert) + // Make rekor signer rekorPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { @@ -178,7 +191,7 @@ func TestVerifyBlob(t *testing.T) { tmpRekorPubFile := writeBlobFile(t, td, string(pemRekor), "rekor_pub.key") t.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", tmpRekorPubFile) - var makeSignature = func(blob []byte) string { + var makeSignature = func(blob []byte, signer signature.SignerVerifier) string { sig, err := signer.SignMessage(bytes.NewReader(blob)) if err != nil { t.Fatal(err) @@ -186,13 +199,15 @@ func TestVerifyBlob(t *testing.T) { return string(sig) } blobBytes := []byte("foo") - blobSignature := makeSignature(blobBytes) + blobSignature := makeSignature(blobBytes, signer) otherBytes := []byte("bar") - otherSignature := makeSignature(otherBytes) + otherSignature := makeSignature(otherBytes, signer) + + unrelatedSignature := makeSignature(blobBytes, unrelatedSigner) // initialize timestamp for expired and unexpired certificates - expiredTSAOpts := mock.TSAClientOptions{Time: time.Now().Add(-time.Hour), Message: []byte(blobSignature)} + expiredTSAOpts := mock.TSAClientOptions{Time: time.Now().Add(-1 * time.Minute), Message: []byte(blobSignature)} unexpiredTSAOpts := mock.TSAClientOptions{Time: time.Now(), Message: []byte(blobSignature)} tsaClient, err := mock.NewTSAClient(expiredTSAOpts) if err != nil { @@ -300,18 +315,27 @@ func TestVerifyBlob(t *testing.T) { { name: "valid signature with public key - bad bundle cert mismatch", blob: blobBytes, + signature: unrelatedSignature, + key: pubKeyBytes, + bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(unrelatedSignature), + unrelatedCertPem, true), + shouldErr: true, + }, + { + name: "valid signature with public key and bundle cert derived from public key", + blob: blobBytes, signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), unexpiredCertPem, true), - shouldErr: true, + shouldErr: false, }, { name: "valid signature with public key - bad bundle signature mismatch", blob: blobBytes, signature: blobSignature, key: pubKeyBytes, - bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)), + bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes, signer)), pubKeyBytes, true), shouldErr: true, }, @@ -365,21 +389,12 @@ func TestVerifyBlob(t *testing.T) { 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)), + bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes, signer)), unexpiredCertPem, true), shouldErr: true, }, @@ -572,7 +587,7 @@ func TestVerifyBlob(t *testing.T) { for _, tt := range tts { t.Run(tt.name, func(t *testing.T) { tt := tt - entries := make([]models.LogEntry, 0) + entries := make([]models.LogEntry, 0, len(tt.rekorEntry)) for _, entry := range tt.rekorEntry { entries = append(entries, *entry) } @@ -616,6 +631,7 @@ func TestVerifyBlob(t *testing.T) { if tt.key != nil { keyPath := writeBlobFile(t, td, string(tt.key), "key.pem") cmd.KeyRef = keyPath + cmd.CertVerifyOptions = options.CertVerifyOptions{} } if tt.newBundle { cmd.TrustedRootPath = writeTrustedRootFile(t, td, "{\"mediaType\":\"application/vnd.dev.sigstore.trustedroot+json;version=0.1\"}") @@ -647,17 +663,88 @@ func TestVerifyBlobCertMissingSubject(t *testing.T) { } } -func TestVerifyBlobCertMissingIssuer(t *testing.T) { +func TestVerifyBlobMutuallyExclusiveFlags(t *testing.T) { + ctx := context.Background() + tts := []struct { + name string + cmd VerifyBlobCmd + expectedError error + }{ + { + name: "both key and cert identity", + cmd: VerifyBlobCmd{ + KeyOpts: options.KeyOpts{ + KeyRef: "key.pub", + BundlePath: "bundle.sigstore.json", + }, + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "hello@foo.com", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both key and cert identity regex", + cmd: VerifyBlobCmd{ + KeyOpts: options.KeyOpts{ + KeyRef: "key.pub", + BundlePath: "bundle.sigstore.json", + }, + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentityRegexp: "^.*@foo.com$", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both cert identity and cert identity regex", + cmd: VerifyBlobCmd{ + KeyOpts: options.KeyOpts{ + BundlePath: "bundle.sigstore.json", + }, + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "hello@foo.com", + CertIdentityRegexp: "^.*@foo.com$", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both key and secret key", + cmd: VerifyBlobCmd{ + KeyOpts: options.KeyOpts{ + KeyRef: "key.pub", + Sk: true, + }, + }, + expectedError: &options.PubKeyParseError{}, + }, + } + + for _, tt := range tts { + t.Run(tt.name, func(t *testing.T) { + err := tt.cmd.Exec(ctx, "foo") + if !errors.Is(err, tt.expectedError) { + t.Fatalf("expected %T, got: %T, %v", tt.expectedError, err, err) + } + }) + } +} + +func TestVerifyBlobKeyAndCertIdentity(t *testing.T) { ctx := context.Background() verifyBlob := VerifyBlobCmd{ - CertRef: "cert.pem", + KeyOpts: options.KeyOpts{ + KeyRef: "key.pub", + }, CertVerifyOptions: options.CertVerifyOptions{ - CertIdentity: "subject", + CertIdentity: "hello@foo.com", }, } + var expectedErr *options.KeyAndIdentityParseError err := verifyBlob.Exec(ctx, "blob") - if err == nil { - t.Fatalf("verifyBlob() expected '--certificate-oidc-issuer required'") + if !errors.As(err, &expectedErr) { + t.Fatalf("expected KeyAndIdentityParseError, got: %T, %v", err, err) } } @@ -701,9 +788,9 @@ func makeRekorEntry(t *testing.T, rekorSigner signature.ECDSASignerVerifier, } e := models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString(leaf), - IntegratedTime: swag.Int64(integratedTime.Unix()), - LogIndex: swag.Int64(0), - LogID: swag.String(logID), + IntegratedTime: conv.Pointer(integratedTime.Unix()), + LogIndex: conv.Pointer(int64(0)), + LogID: conv.Pointer(logID), } // Marshal payload, sign, and set SET in Bundle jsonPayload, err := json.Marshal(e) @@ -723,9 +810,9 @@ func makeRekorEntry(t *testing.T, rekorSigner signature.ECDSASignerVerifier, e.Verification = &models.LogEntryAnonVerification{ SignedEntryTimestamp: bundleSig, InclusionProof: &models.InclusionProof{ - LogIndex: swag.Int64(0), - TreeSize: swag.Int64(1), - RootHash: swag.String(hex.EncodeToString(uuid)), + LogIndex: conv.Pointer(int64(0)), + TreeSize: conv.Pointer(int64(1)), + RootHash: conv.Pointer(hex.EncodeToString(uuid)), Hashes: []string{}, }, } @@ -789,9 +876,15 @@ func makeLocalBundleWithoutRekorBundle(t *testing.T, sig []byte, svBytes []byte) } 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 := &protobundle.Bundle{ + MediaType: "application/vnd.dev.sigstore.bundle.v0.3+json", + VerificationMaterial: &protobundle.VerificationMaterial{ + Content: &protobundle.VerificationMaterial_PublicKey{ + PublicKey: &protocommon.PublicKeyIdentifier{ + Hint: "hint", + }, + }, + }, } b.Content = &protobundle.Bundle_MessageSignature{ @@ -956,6 +1049,7 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { SignaturePath: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, + PredicateType: "customFoo", } if err := cmd.Exec(context.Background(), blobPath); err != nil { t.Fatal(err) @@ -993,6 +1087,7 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { SignaturePath: "", // Sig is fetched from bundle KeyOpts: options.KeyOpts{BundlePath: bundlePath}, IgnoreSCT: true, + PredicateType: "customFoo", } if err := cmd.Exec(context.Background(), blobPath); err != nil { t.Fatal(err) @@ -1325,6 +1420,7 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) { 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 + PredicateType: "customFoo", } if err := cmd.Exec(context.Background(), blobPath); err != nil { t.Fatal(err) @@ -1454,7 +1550,7 @@ func (s *keylessStack) genLeafCert(t *testing.T, subject string, issuer string) } func (s *keylessStack) genChainFile(t *testing.T) { - var chain []byte + chain := make([]byte, 0, len(s.subPemCert)+len(s.rootPemCert)) chain = append(chain, s.subPemCert...) chain = append(chain, s.rootPemCert...) tmpChainFile, err := os.CreateTemp(s.td, "cosign_fulcio_chain_*.cert") @@ -1638,7 +1734,7 @@ func writeTimestampFile(t *testing.T, td string, ts *bundle.RFC3161Timestamp, na return path } -func writeTrustedRootFile(t *testing.T, td, contents string) string { +func writeTrustedRootFile(t *testing.T, td, contents string) string { //nolint: unparam path := filepath.Join(td, "trusted_root.json") if err := os.WriteFile(path, []byte(contents), 0644); err != nil { t.Fatal(err) diff --git a/cmd/cosign/cli/verify/verify_bundle.go b/cmd/cosign/cli/verify/verify_bundle.go index 3d876f9a5c5..ab43fb2b44a 100644 --- a/cmd/cosign/cli/verify/verify_bundle.go +++ b/cmd/cosign/cli/verify/verify_bundle.go @@ -35,7 +35,7 @@ import ( "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" - "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign" ) func checkNewBundle(bundlePath string) bool { diff --git a/cmd/cosign/cli/verify/verify_test.go b/cmd/cosign/cli/verify/verify_test.go index 620c841b6f9..7d6531060e4 100644 --- a/cmd/cosign/cli/verify/verify_test.go +++ b/cmd/cosign/cli/verify/verify_test.go @@ -27,6 +27,7 @@ import ( "encoding/base64" "encoding/json" "encoding/pem" + "errors" "fmt" "io" "log" @@ -35,13 +36,13 @@ import ( "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/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/fulcio/fulcioroots" + "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/static" "github.com/sigstore/sigstore/pkg/signature/payload" "github.com/stretchr/testify/assert" ) @@ -227,7 +228,11 @@ func TestPrintVerification(t *testing.T) { } func appendSlices(slices [][]byte) []byte { - var tmp []byte + totalLen := 0 + for _, s := range slices { + totalLen += len(s) + } + tmp := make([]byte, 0, totalLen) for _, s := range slices { tmp = append(tmp, s...) } @@ -264,6 +269,55 @@ func TestVerifyCertMissingIssuer(t *testing.T) { } } +func TestVerifyMutuallyExclusiveFlags(t *testing.T) { + ctx := context.Background() + tts := []struct { + name string + cmd VerifyCommand + expectedError error + }{ + { + name: "both key and cert identity", + cmd: VerifyCommand{ + KeyRef: "key.pub", + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "hello@foo.com", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both key and cert identity regexp", + cmd: VerifyCommand{ + KeyRef: "key.pub", + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentityRegexp: "^.*@foo.com$", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + { + name: "both cert identity and cert identity regexp", + cmd: VerifyCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertIdentity: "hello@foo.com", + CertIdentityRegexp: "^.*@foo.com$", + }, + }, + expectedError: &options.KeyAndIdentityParseError{}, + }, + } + + for _, tt := range tts { + t.Run(tt.name, func(t *testing.T) { + err := tt.cmd.Exec(ctx, []string{"foo", "bar", "baz"}) + if !errors.Is(err, tt.expectedError) { + t.Fatalf("expected %T, got: %T, %v", tt.expectedError, err, err) + } + }) + } +} + func TestLoadCertsKeylessVerification(t *testing.T) { certs := getTestCerts(t) certChainFile := makeCertChainFile(t, certs.RootCertPEM, certs.SubCertPEM, certs.LeafCertPEM) @@ -346,3 +400,53 @@ func TestLoadCertsKeylessVerification(t *testing.T) { }) } } +func TestTransformOutputSuccess(t *testing.T) { + // Build minimal in-toto statement + stmt := `{ + "_type": "https://in-toto.io/Statement/v0.1", + "subject": [ + { "name": "artifact", "digest": { "sha256": "deadbeef" }, "annotations": { "foo": "bar" } } + ], + "predicateType": "https://slsa.dev/provenance/v0.2" + }` + // DSSE payloadType for in-toto + payloadType := "application/vnd.in-toto+json" + encodedStmt := base64.StdEncoding.EncodeToString([]byte(stmt)) + dsseEnv := fmt.Sprintf(`{ + "payloadType": "%s", + "payload": "%s", + "signatures": [ + { "keyid": "test", "sig": "MAo=" } + ] + }`, payloadType, encodedStmt) + + sig, err := static.NewSignature([]byte(dsseEnv), "") + if err != nil { + t.Fatalf("creating static signature: %v", err) + } + fmt.Println(dsseEnv) + + name := "example.com/my/image" + out, err := transformOutput([]oci.Signature{sig}, name) + if err != nil { + t.Fatalf("transformOutput returned error: %v", err) + } + if len(out) != 1 { + t.Fatalf("expected 1 transformed signature, got %d", len(out)) + } + + payloadBytes, err := out[0].Payload() + if err != nil { + t.Fatalf("reading transformed payload: %v", err) + } + + var sci payload.SimpleContainerImage + if err := json.Unmarshal(payloadBytes, &sci); err != nil { + t.Fatalf("unmarshal transformed payload: %v", err) + } + + assert.Equal(t, name, sci.Critical.Identity.DockerReference, "docker reference mismatch") + assert.Equal(t, "sha256:deadbeef", sci.Critical.Image.DockerManifestDigest, "digest mismatch") + assert.Equal(t, "https://slsa.dev/provenance/v0.2", sci.Critical.Type, "type mismatch") + assert.Equal(t, map[string]any{"foo": "bar"}, sci.Optional, "missing annotation") +} diff --git a/cmd/cosign/errors/error_wrap_test.go b/cmd/cosign/errors/error_wrap_test.go index 6b397f647b4..f10276bcb5e 100644 --- a/cmd/cosign/errors/error_wrap_test.go +++ b/cmd/cosign/errors/error_wrap_test.go @@ -13,19 +13,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package errors +package errors_test import ( - "errors" + stderrors "errors" "testing" + + "github.com/sigstore/cosign/v3/cmd/cosign/errors" ) func TestWrapWithGenericCosignError(t *testing.T) { errorText := "i am a generic cosign error" - err := WrapError(errors.New(errorText)) + err := errors.WrapError(stderrors.New(errorText)) - var cosignError *CosignError - if errors.As(err, &cosignError) { + var cosignError *errors.CosignError + if stderrors.As(err, &cosignError) { if cosignError.ExitCode() == 1 && cosignError.Message == errorText { t.Logf("generic cosign error successfully returned") return diff --git a/cmd/cosign/errors/exit_code_lookup.go b/cmd/cosign/errors/exit_code_lookup.go index 93d097ab79a..7b038d5a9fa 100644 --- a/cmd/cosign/errors/exit_code_lookup.go +++ b/cmd/cosign/errors/exit_code_lookup.go @@ -18,7 +18,7 @@ package errors import ( "errors" - cosignError "github.com/sigstore/cosign/v2/pkg/cosign" + cosignError "github.com/sigstore/cosign/v3/pkg/cosign" ) func LookupExitCodeForError(err interface{ error }) int { diff --git a/cmd/cosign/errors/exit_code_lookup_test.go b/cmd/cosign/errors/exit_code_lookup_test.go index 88ff7bef17c..6ce4a7ae4a0 100644 --- a/cmd/cosign/errors/exit_code_lookup_test.go +++ b/cmd/cosign/errors/exit_code_lookup_test.go @@ -13,17 +13,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -package errors +package errors_test import ( "fmt" "testing" - pkgError "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v3/cmd/cosign/errors" + pkgError "github.com/sigstore/cosign/v3/pkg/cosign" ) func TestDefaultExitCodeReturnIfErrorTypeToExitCodeMappingDoesNotExist(t *testing.T) { - exitCode := LookupExitCodeForError(fmt.Errorf("I do not exist as an error type")) + exitCode := errors.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") } @@ -33,8 +34,8 @@ func TestDefaultExitCodeReturnIfErrorTypeToExitCodeMappingDoesNotExist(t *testin 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 { + exitCode := errors.LookupExitCodeForError(&pkgError.ErrNoMatchingSignatures{}) + if exitCode != errors.NoMatchingSignature { t.Fatalf("NoMatchingSignature exit code not returned when error is thrown") } t.Logf("Correct default exit code returned") diff --git a/cmd/cosign/main.go b/cmd/cosign/main.go index 50edaefcecc..1f87be21333 100644 --- a/cmd/cosign/main.go +++ b/cmd/cosign/main.go @@ -22,9 +22,9 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli" + cosignError "github.com/sigstore/cosign/v3/cmd/cosign/errors" + "github.com/sigstore/cosign/v3/internal/ui" // Register the provider-specific plugins _ "github.com/sigstore/sigstore/pkg/signature/kms/aws" diff --git a/cmd/help/main.go b/cmd/help/main.go index c91eb8b3bac..88e31e04277 100644 --- a/cmd/help/main.go +++ b/cmd/help/main.go @@ -18,9 +18,9 @@ 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/sigstore/cosign/v3/cmd/cosign/cli" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/templates" + errors "github.com/sigstore/cosign/v3/cmd/cosign/errors" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" ) diff --git a/doc/cosign.md b/doc/cosign.md index c726615dcf8..c403e9ae0d4 100644 --- a/doc/cosign.md +++ b/doc/cosign.md @@ -1,6 +1,6 @@ ## cosign -A tool for Container Signing, Verification and Storage in an OCI registry. +A tool for Container Signing, Verification and Storage in an OCI registry ### Options @@ -13,34 +13,27 @@ A tool for Container Signing, Verification and Storage in an OCI registry. ### 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 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 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 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 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 signing-config](cosign_signing-config.md) - Interact with a Sigstore protobuf signing config * [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 diff --git a/doc/cosign_attach.md b/doc/cosign_attach.md deleted file mode 100644 index 181dcb60bdf..00000000000 --- a/doc/cosign_attach.md +++ /dev/null @@ -1,25 +0,0 @@ -## 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 - diff --git a/doc/cosign_attach_attestation.md b/doc/cosign_attach_attestation.md deleted file mode 100644 index 2c53d1d508e..00000000000 --- a/doc/cosign_attach_attestation.md +++ /dev/null @@ -1,52 +0,0 @@ -## 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 - diff --git a/doc/cosign_attach_sbom.md b/doc/cosign_attach_sbom.md deleted file mode 100644 index f4d5cd846c5..00000000000 --- a/doc/cosign_attach_sbom.md +++ /dev/null @@ -1,53 +0,0 @@ -## 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 - diff --git a/doc/cosign_attach_signature.md b/doc/cosign_attach_signature.md deleted file mode 100644 index a0355876e0e..00000000000 --- a/doc/cosign_attach_signature.md +++ /dev/null @@ -1,66 +0,0 @@ -## 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 - diff --git a/doc/cosign_attest-blob.md b/doc/cosign_attest-blob.md index 3a46e1fbafd..ae9840e1f19 100644 --- a/doc/cosign_attest-blob.md +++ b/doc/cosign_attest-blob.md @@ -1,6 +1,6 @@ ## cosign attest-blob -Attest the supplied blob. +Attest the supplied blob ``` cosign attest-blob [flags] @@ -33,41 +33,33 @@ cosign attest-blob [flags] ### 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) - --statement string path to the statement file. - --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 + --bundle string write everything required to verify the blob to a FILE + --certificate string path to the X.509 certificate for signing attestation + --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 signed attestation. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. + --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 + --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. + --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-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 + --predicate string path to the predicate file. + --signing-config string path to a signing config file. Must provide --bundle, which will output verification material in the new 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) + --statement string path to the statement file. + --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 + --trusted-root string optional path to a TrustedRoot JSON file to verify a signature after signing + --type string specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom") + --use-signing-config whether to use a TUF-provided signing config for the service URLs. Must provide --bundle, which will output verification material in the new format (default true) + -y, --yes skip confirmation prompts for non-destructive operations ``` ### Options inherited from parent commands @@ -80,5 +72,5 @@ cosign attest-blob [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_attest.md b/doc/cosign_attest.md index 5ad9f4e70f4..f862bb53f17 100644 --- a/doc/cosign_attest.md +++ b/doc/cosign_attest.md @@ -1,6 +1,6 @@ ## cosign attest -Attest the supplied container image. +Attest the supplied container image ``` cosign attest [flags] @@ -48,49 +48,42 @@ cosign attest [flags] ### 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, but send the attestation output to STDOUT - --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 - --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) - --statement string path to the statement file. - --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 + --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 + --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 + -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. + --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 + --no-upload do not upload the generated attestation, but send the attestation output to STDOUT + --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-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. + --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 + --signing-config string path to a signing config 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) + --statement string path to the statement file. + --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 + --trusted-root string optional path to a TrustedRoot JSON file to verify a signature after signing + --type string specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI (default "custom") + --use-signing-config whether to use a TUF-provided signing config for the service URLs (default true) + -y, --yes skip confirmation prompts for non-destructive operations ``` ### Options inherited from parent commands @@ -103,5 +96,5 @@ cosign attest [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_bundle.md b/doc/cosign_bundle.md index 27c77009d25..d60a975e4a5 100644 --- a/doc/cosign_bundle.md +++ b/doc/cosign_bundle.md @@ -22,6 +22,8 @@ Tools for interacting with a Sigstore protobuf bundle ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [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 bundle inspect](cosign_bundle_inspect.md) - Inspect a Sigstore protobuf bundle +* [cosign bundle upgrade](cosign_bundle_upgrade.md) - Upgrade a Sigstore protobuf bundle diff --git a/doc/cosign_bundle_create.md b/doc/cosign_bundle_create.md index ac72db4dc88..ff839df2aae 100644 --- a/doc/cosign_bundle_create.md +++ b/doc/cosign_bundle_create.md @@ -10,18 +10,27 @@ Create a Sigstore protobuf bundle by supplying signed material cosign bundle create [flags] ``` +### Examples + +``` + # create a bundle from a signature and certificate + cosign bundle create --artifact --signature --certificate --out bundle.sigstore.json + + # create a bundle from an attestation + cosign bundle create --artifact --attestation --out bundle.sigstore.json +``` + ### 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. + --certificate string path to the signing certificate, likely from Fulcio. -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 diff --git a/doc/cosign_bundle_inspect.md b/doc/cosign_bundle_inspect.md new file mode 100644 index 00000000000..84d09f0ad6b --- /dev/null +++ b/doc/cosign_bundle_inspect.md @@ -0,0 +1,26 @@ +## cosign bundle inspect + +Inspect a Sigstore protobuf bundle + +``` +cosign bundle inspect BUNDLE [flags] +``` + +### Options + +``` + -h, --help help for inspect +``` + +### 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 + diff --git a/doc/cosign_bundle_upgrade.md b/doc/cosign_bundle_upgrade.md new file mode 100644 index 00000000000..2df5ee48fd0 --- /dev/null +++ b/doc/cosign_bundle_upgrade.md @@ -0,0 +1,32 @@ +## cosign bundle upgrade + +Upgrade a Sigstore protobuf bundle + +### Synopsis + +Upgrade a Sigstore Protobuf bundle to the latest version. This command only supports standardized bundles. + +``` +cosign bundle upgrade [flags] +``` + +### Options + +``` + -h, --help help for upgrade + --out string path to the output upgraded bundle file + --rekor-url string URL of the transparency log (default "https://rekor.sigstore.dev") +``` + +### 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 + diff --git a/doc/cosign_clean.md b/doc/cosign_clean.md index fba11ddce57..58991cb8af7 100644 --- a/doc/cosign_clean.md +++ b/doc/cosign_clean.md @@ -1,6 +1,6 @@ ## cosign clean -Remove all signatures from an image. +Remove all signatures from an image ``` cosign clean [flags] @@ -15,20 +15,19 @@ cosign clean [flags] ### 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) + --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 + -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 @@ -41,5 +40,5 @@ cosign clean [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_completion.md b/doc/cosign_completion.md index 0a4f5262649..c3cedc21d15 100644 --- a/doc/cosign_completion.md +++ b/doc/cosign_completion.md @@ -50,5 +50,5 @@ cosign completion [bash|zsh|fish|powershell] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_copy.md b/doc/cosign_copy.md deleted file mode 100644 index 8a499692808..00000000000 --- a/doc/cosign_copy.md +++ /dev/null @@ -1,62 +0,0 @@ -## 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. - diff --git a/doc/cosign_dockerfile.md b/doc/cosign_dockerfile.md deleted file mode 100644 index 6527aaf3074..00000000000 --- a/doc/cosign_dockerfile.md +++ /dev/null @@ -1,23 +0,0 @@ -## 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 - diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md deleted file mode 100644 index 3ee9b0d34c0..00000000000 --- a/doc/cosign_dockerfile_verify.md +++ /dev/null @@ -1,114 +0,0 @@ -## 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 - diff --git a/doc/cosign_download.md b/doc/cosign_download.md index 0700d193023..803c598662f 100644 --- a/doc/cosign_download.md +++ b/doc/cosign_download.md @@ -18,7 +18,7 @@ Provides utilities for downloading artifacts and attached artifacts in a registr ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [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 diff --git a/doc/cosign_download_attestation.md b/doc/cosign_download_attestation.md index 30e768ed193..8743aaedb59 100644 --- a/doc/cosign_download_attestation.md +++ b/doc/cosign_download_attestation.md @@ -15,20 +15,19 @@ cosign download attestation [flags] ### 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 + --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 + -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 diff --git a/doc/cosign_download_sbom.md b/doc/cosign_download_sbom.md index 03b18f3e00a..dfc8d5ee0ed 100644 --- a/doc/cosign_download_sbom.md +++ b/doc/cosign_download_sbom.md @@ -21,19 +21,18 @@ cosign download sbom [flags] ### 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 + --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 + -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 diff --git a/doc/cosign_download_signature.md b/doc/cosign_download_signature.md index 371a8699656..8a19590c538 100644 --- a/doc/cosign_download_signature.md +++ b/doc/cosign_download_signature.md @@ -15,18 +15,17 @@ cosign download signature [flags] ### 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 + --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 + -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 diff --git a/doc/cosign_env.md b/doc/cosign_env.md index 57fee6a2102..37aed64f563 100644 --- a/doc/cosign_env.md +++ b/doc/cosign_env.md @@ -6,6 +6,18 @@ Prints Cosign environment variables cosign env [flags] ``` +### Examples + +``` + cosign env + + # show environment variables with descriptions + cosign env --show-descriptions + + # show environment variables including sensitive values + cosign env --show-descriptions --show-sensitive-values +``` + ### Options ``` @@ -24,5 +36,5 @@ cosign env [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_generate-key-pair.md b/doc/cosign_generate-key-pair.md index e0114837ff6..109abd85a30 100644 --- a/doc/cosign_generate-key-pair.md +++ b/doc/cosign_generate-key-pair.md @@ -1,6 +1,6 @@ ## cosign generate-key-pair -Generates a key-pair. +Generates a key-pair ### Synopsis @@ -45,6 +45,12 @@ cosign generate-key-pair [flags] # generate a key-pair in GitLab with project id cosign generate-key-pair gitlab://[PROJECT_ID] + # generate a key-pair in GitLab with group name (accessible to all projects in the group) + cosign generate-key-pair gitlab://[GROUP_NAME] + + # generate a key-pair in GitLab with subgroup name + cosign generate-key-pair gitlab://[GROUP_NAME]/[SUBGROUP_NAME] + CAVEATS: This command interactively prompts for a password. You can use the COSIGN_PASSWORD environment variable to provide one. @@ -68,5 +74,5 @@ CAVEATS: ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_generate.md b/doc/cosign_generate.md deleted file mode 100644 index 8996f56438a..00000000000 --- a/doc/cosign_generate.md +++ /dev/null @@ -1,59 +0,0 @@ -## 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. - diff --git a/doc/cosign_import-key-pair.md b/doc/cosign_import-key-pair.md index 6a8a0cbd382..2e344062fa4 100644 --- a/doc/cosign_import-key-pair.md +++ b/doc/cosign_import-key-pair.md @@ -1,6 +1,6 @@ ## cosign import-key-pair -Imports a PEM-encoded RSA or EC private key. +Imports a PEM-encoded RSA or EC private key ### Synopsis @@ -45,5 +45,5 @@ CAVEATS: ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_initialize.md b/doc/cosign_initialize.md index 1b927192696..f2d37977401 100644 --- a/doc/cosign_initialize.md +++ b/doc/cosign_initialize.md @@ -1,6 +1,6 @@ ## cosign initialize -Initializes SigStore root to retrieve trusted certificate and key targets for verification. +Initializes SigStore root to retrieve trusted certificate and key targets for verification ### Synopsis @@ -16,7 +16,7 @@ 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. +with Fulcio root CA) are pulled from the trusted metadata. ``` cosign initialize [flags] @@ -25,11 +25,14 @@ cosign initialize [flags] ### Examples ``` -cosign initialize --mirror --out +cosign initialize --mirror -# initialize root with distributed root keys, default mirror, and default out path. +# initialize root with distributed root keys, using the default mirror. cosign initialize +# initialize root with distributed root keys, using the staging mirror. +cosign initialize --staging + # initialize with an out-of-band root key file, using the default mirror. cosign initialize --root @@ -47,6 +50,7 @@ cosign initialize --mirror --root --root-checksum --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: + --staging use the staging TUF repository ``` ### Options inherited from parent commands @@ -59,5 +63,5 @@ cosign initialize --mirror --root --root-checksum ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_load.md b/doc/cosign_load.md index 17412943270..ad5c66678f2 100644 --- a/doc/cosign_load.md +++ b/doc/cosign_load.md @@ -19,19 +19,18 @@ cosign load [flags] ### 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 + --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 + --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 @@ -44,5 +43,5 @@ cosign load [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_login.md b/doc/cosign_login.md index f6f8a2c51fa..6d96598aa68 100644 --- a/doc/cosign_login.md +++ b/doc/cosign_login.md @@ -32,5 +32,5 @@ cosign login [OPTIONS] [SERVER] [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_manifest.md b/doc/cosign_manifest.md deleted file mode 100644 index a55c971e4f9..00000000000 --- a/doc/cosign_manifest.md +++ /dev/null @@ -1,23 +0,0 @@ -## 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 - diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md deleted file mode 100644 index 0914e691f68..00000000000 --- a/doc/cosign_manifest_verify.md +++ /dev/null @@ -1,108 +0,0 @@ -## 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 - diff --git a/doc/cosign_piv-tool.md b/doc/cosign_piv-tool.md index 5cc858fd8c8..aa3c9a35936 100644 --- a/doc/cosign_piv-tool.md +++ b/doc/cosign_piv-tool.md @@ -19,7 +19,7 @@ Provides utilities for managing a hardware token ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [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 diff --git a/doc/cosign_pkcs11-tool.md b/doc/cosign_pkcs11-tool.md index 92e3a405381..a82d76939ca 100644 --- a/doc/cosign_pkcs11-tool.md +++ b/doc/cosign_pkcs11-tool.md @@ -19,7 +19,7 @@ Provides utilities for retrieving information from a PKCS11 token. ### 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](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 URIs of all keys in a PKCS11 token +* [cosign pkcs11-tool list-tokens](cosign_pkcs11-tool_list-tokens.md) - List all PKCS11 tokens linked to a module diff --git a/doc/cosign_pkcs11-tool_list-keys-uris.md b/doc/cosign_pkcs11-tool_list-keys-uris.md index 494021a51a5..c8f7c6aa60d 100644 --- a/doc/cosign_pkcs11-tool_list-keys-uris.md +++ b/doc/cosign_pkcs11-tool_list-keys-uris.md @@ -1,11 +1,18 @@ ## cosign pkcs11-tool list-keys-uris -list-keys-uris lists URIs of all keys in a PKCS11 token +List URIs of all keys in a PKCS11 token ``` cosign pkcs11-tool list-keys-uris [flags] ``` +### Examples + +``` + # list key URIs in a specific PKCS11 token slot + cosign pkcs11-tool list-keys-uris --module-path /usr/lib/libp11kit.so --slot-id 0 +``` + ### Options ``` diff --git a/doc/cosign_pkcs11-tool_list-tokens.md b/doc/cosign_pkcs11-tool_list-tokens.md index e58fad0b0e8..c105091e27c 100644 --- a/doc/cosign_pkcs11-tool_list-tokens.md +++ b/doc/cosign_pkcs11-tool_list-tokens.md @@ -1,11 +1,18 @@ ## cosign pkcs11-tool list-tokens -list-tokens lists all PKCS11 tokens linked to a PKCS11 module +List all PKCS11 tokens linked to a module ``` cosign pkcs11-tool list-tokens [flags] ``` +### Examples + +``` + # list all tokens for a PKCS11 module + cosign pkcs11-tool list-tokens --module-path /usr/lib/libp11kit.so +``` + ### Options ``` diff --git a/doc/cosign_public-key.md b/doc/cosign_public-key.md index 37cd3b6a3ed..b87ecd7ffd1 100644 --- a/doc/cosign_public-key.md +++ b/doc/cosign_public-key.md @@ -1,6 +1,6 @@ ## cosign public-key -Gets a public key from the key-pair. +Gets a public key from the key-pair ### Synopsis @@ -60,5 +60,5 @@ cosign public-key [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_save.md b/doc/cosign_save.md index fa6cd9590b2..03e049161f0 100644 --- a/doc/cosign_save.md +++ b/doc/cosign_save.md @@ -1,6 +1,6 @@ ## cosign save -Save the container image and associated signatures to disk at the specified directory. +Save the container image and associated signatures to disk at the specified directory ### Synopsis @@ -19,19 +19,18 @@ cosign save [flags] ### 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 + --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 + --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 @@ -44,5 +43,5 @@ cosign save [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_sign-blob.md b/doc/cosign_sign-blob.md index d78f9fb250e..951b2e52894 100644 --- a/doc/cosign_sign-blob.md +++ b/doc/cosign_sign-blob.md @@ -1,6 +1,6 @@ ## cosign sign-blob -Sign the supplied blob, outputting the base64-encoded signature to stdout. +Sign the supplied blob, outputting the base64-encoded signature to stdout ``` cosign sign-blob [flags] @@ -11,9 +11,6 @@ cosign sign-blob [flags] ``` 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 @@ -36,35 +33,28 @@ cosign sign-blob [flags] ### Options ``` - --b64 whether to base64 encode the output (default true) --bundle string write everything required to verify the blob to a FILE + --certificate string path to the X.509 certificate for signing attestation + --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 signed attestation. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate. --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 + --signing-algorithm string signing algorithm to use for signing/hashing (allowed ecdsa-sha2-256-nistp256, ecdsa-sha2-384-nistp384, ecdsa-sha2-512-nistp521, rsa-sign-pkcs1-2048-sha256, rsa-sign-pkcs1-3072-sha256, rsa-sign-pkcs1-4096-sha256) (default "ecdsa-sha2-256-nistp256") + --signing-config string path to a signing config file. Must provide --bundle, which will output verification material in the new 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-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) + --trusted-root string optional path to a TrustedRoot JSON file to verify a signature after signing + --use-signing-config whether to use a TUF-provided signing config for the service URLs. Must provide --bundle, which will output verification material in the new format (default true) -y, --yes skip confirmation prompts for non-destructive operations ``` @@ -78,5 +68,5 @@ cosign sign-blob [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_sign.md b/doc/cosign_sign.md index 07c3bb98fa3..9206d2b9b40 100644 --- a/doc/cosign_sign.md +++ b/doc/cosign_sign.md @@ -1,6 +1,6 @@ ## cosign sign -Sign the supplied container image. +Sign the supplied container image ### Synopsis @@ -62,9 +62,6 @@ cosign sign [flags] # 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 ``` @@ -72,53 +69,43 @@ cosign sign [flags] ### 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 + --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 + --bundle string write everything required to verify the image to 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 + -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. + --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-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'. + --payload string path to a payload file to use rather than generating one + -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 + --signing-config string path to a signing config 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 + --trusted-root string optional path to a TrustedRoot JSON file to verify a signature after signing + --upload whether to upload the signature (default true) + --use-signing-config whether to use a TUF-provided signing config for the service URLs (default true) + -y, --yes skip confirmation prompts for non-destructive operations ``` ### Options inherited from parent commands @@ -131,5 +118,5 @@ cosign sign [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_signing-config.md b/doc/cosign_signing-config.md index 5ee6621907d..7a3137638e2 100644 --- a/doc/cosign_signing-config.md +++ b/doc/cosign_signing-config.md @@ -22,6 +22,6 @@ Tool for interacting with a Sigstore protobuf signing config ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry * [cosign signing-config create](cosign_signing-config_create.md) - Create a Sigstore protobuf signing config diff --git a/doc/cosign_signing-config_create.md b/doc/cosign_signing-config_create.md index d56f8b81536..c0d1fe83fc6 100644 --- a/doc/cosign_signing-config_create.md +++ b/doc/cosign_signing-config_create.md @@ -30,6 +30,10 @@ cosign signing-config create \ --fulcio stringArray fulcio service specification, as a comma-separated key-value list. Required keys: url, api-version (integer), start-time, operator. Optional keys: end-time. -h, --help help for create + --no-default-fulcio removes the default Fulcio URLs from the signing config. + --no-default-oidc removes the default OIDC provider URLs from the signing config. + --no-default-rekor removes the default Rekor URLs from the signing config. + --no-default-tsa removes the default TSA URLs from the signing config. --oidc-provider stringArray oidc provider specification, as a comma-separated key-value list. Required keys: url, api-version (integer), start-time, operator. Optional keys: end-time. --out string path to output signing config @@ -39,6 +43,8 @@ cosign signing-config create \ --tsa stringArray timestamping authority specification, as a comma-separated key-value list. Required keys: url, api-version (integer), start-time, operator. Optional keys: end-time. --tsa-config string timestamping authority configuration. Required if --tsa is provided. One of: ANY, ALL, EXACT: + --with-default-rekor-v2 use the Sigstore TUF root, with the Rekor v2 service, as default values to populate the signing config. Specifying the other service flags will override the default values. + --with-default-services use the Sigstore TUF root as default values to populate the signing config. Specifying the other service flags will override the default values. ``` ### Options inherited from parent commands diff --git a/doc/cosign_tree.md b/doc/cosign_tree.md index fc005d9c244..6150d3f6fae 100644 --- a/doc/cosign_tree.md +++ b/doc/cosign_tree.md @@ -15,20 +15,18 @@ cosign tree [flags] ### 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] - --experimental-oci11 set to true to enable experimental OCI 1.1 behaviour - -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-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 + --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 + -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-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 ``` ### Options inherited from parent commands @@ -41,5 +39,5 @@ cosign tree [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_triangulate.md b/doc/cosign_triangulate.md deleted file mode 100644 index 875bc72c257..00000000000 --- a/doc/cosign_triangulate.md +++ /dev/null @@ -1,44 +0,0 @@ -## 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. - diff --git a/doc/cosign_trusted-root.md b/doc/cosign_trusted-root.md index eb2dc15dfb9..3a61a43a8d2 100644 --- a/doc/cosign_trusted-root.md +++ b/doc/cosign_trusted-root.md @@ -22,6 +22,6 @@ Tools for interacting with a Sigstore protobuf trusted root ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [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 diff --git a/doc/cosign_trusted-root_create.md b/doc/cosign_trusted-root_create.md index 19f0c77ee3e..ff85d0301d5 100644 --- a/doc/cosign_trusted-root_create.md +++ b/doc/cosign_trusted-root_create.md @@ -26,16 +26,21 @@ cosign trusted-root create \ ### Options ``` - --ctfe stringArray ctfe service specification, as a comma-separated key-value list. - Required keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time. - --fulcio stringArray fulcio service specification, as a comma-separated key-value list. - Required keys: url, certificate-chain (path to PEM-encoded certificate chain). Optional keys: start-time, end-time. - -h, --help help for create - --out string path to output trusted root - --rekor stringArray rekor service specification, as a comma-separated key-value list. - Required keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time, origin. - --tsa stringArray timestamping authority specification, as a comma-separated key-value list. - Required keys: url, certificate-chain (path to PEM-encoded certificate chain). Optional keys: start-time, end-time. + --ctfe stringArray ctfe service specification, as a comma-separated key-value list. + Required keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time, origin. + --fulcio stringArray fulcio service specification, as a comma-separated key-value list. + Required keys: url, certificate-chain (path to PEM-encoded certificate chain). Optional keys: start-time, end-time. + -h, --help help for create + --no-default-ctfe removes the default CTFE URLs from the trusted root. + --no-default-fulcio removes the default Fulcio URLs from the trusted root. + --no-default-rekor removes the default Rekor URLs from the trusted root. + --no-default-tsa removes the default TSA URLs from the trusted root. + --out string path to output trusted root + --rekor stringArray rekor service specification, as a comma-separated key-value list. + Required keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time, origin. + --tsa stringArray timestamping authority specification, as a comma-separated key-value list. + Required keys: url, certificate-chain (path to PEM-encoded certificate chain). Optional keys: start-time, end-time. + --with-default-services use the Sigstore TUF root as default values to populate the trusted root. Specifying the other service flags will override the default values. ``` ### Options inherited from parent commands diff --git a/doc/cosign_upload.md b/doc/cosign_upload.md deleted file mode 100644 index 28e69c11687..00000000000 --- a/doc/cosign_upload.md +++ /dev/null @@ -1,24 +0,0 @@ -## 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 - diff --git a/doc/cosign_upload_blob.md b/doc/cosign_upload_blob.md deleted file mode 100644 index 59c0c7a1b6b..00000000000 --- a/doc/cosign_upload_blob.md +++ /dev/null @@ -1,64 +0,0 @@ -## 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 - diff --git a/doc/cosign_upload_wasm.md b/doc/cosign_upload_wasm.md deleted file mode 100644 index 15bc70a7f4c..00000000000 --- a/doc/cosign_upload_wasm.md +++ /dev/null @@ -1,44 +0,0 @@ -## 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 - diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md index b29905644ec..8cddd1eb149 100644 --- a/doc/cosign_verify-attestation.md +++ b/doc/cosign_verify-attestation.md @@ -56,51 +56,39 @@ cosign verify-attestation [flags] ### 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 + --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 + --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) + -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) + -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 + --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 + --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) + --trusted-root string Path to a Sigstore TrustedRoot JSON file + --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 @@ -113,5 +101,5 @@ cosign verify-attestation [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_verify-blob-attestation.md b/doc/cosign_verify-blob-attestation.md index d65c180e79a..27a65d59ae5 100644 --- a/doc/cosign_verify-blob-attestation.md +++ b/doc/cosign_verify-blob-attestation.md @@ -7,7 +7,7 @@ Verify an attestation on the supplied blob 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. +Signed material is provided with the --bundle flag. The blob may be specified as a path to a file. ``` @@ -17,10 +17,25 @@ cosign verify-blob-attestation [flags] ### Examples ``` - cosign verify-blob-attestation (--key ||) --signature [path to BLOB] + cosign verify-blob-attestation --bundle --certificate-identity --certificate-oidc-issuer - # Verify a simple blob attestation with a DSSE style signature - cosign verify-blob-attestation --key cosign.pub (--signature |)[path to BLOB] + # Verify a blob attestation (keyless) + cosign verify-blob-attestation --bundle artifact.sigstore.json --certificate-identity foo@example.com --certificate-oidc-issuer https://accounts.google.com + + # Verify a blob attestation with a public key + cosign verify-blob-attestation --bundle artifact.sigstore.json --key cosign.pub + + # Verify a blob attestation with Azure KMS + cosign verify-blob-attestation --bundle artifact.sigstore.json --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] + + # Verify a blob attestation with AWS KMS + cosign verify-blob-attestation --bundle artifact.sigstore.json --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] + + # Verify a blob attestation with GCP KMS + cosign verify-blob-attestation --bundle artifact.sigstore.json --key gcpkms://projects/[PROJECT]/locations/global/keyRings/[KEYRING]/cryptoKeys/[KEY] + + # Verify a blob attestation with Hashicorp Vault + cosign verify-blob-attestation --bundle artifact.sigstore.json --key hashivault://[KEY] ``` @@ -29,10 +44,6 @@ cosign verify-blob-attestation [flags] ``` --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 @@ -45,23 +56,14 @@ cosign verify-blob-attestation [flags] --check-claims if true, verifies the digest exists in the in-toto subject (using either the provided digest and digest algorithm or the provided blob's sha256 digest). If false, only the DSSE envelope is verified. (default true) --digest string Digest to use for verifying in-toto subject (instead of providing a blob) --digestAlg string Digest algorithm to use for verifying in-toto subject (instead of providing a blob) - --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. + --trusted-root string Path to a Sigstore TrustedRoot JSON file --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 ``` @@ -76,5 +78,5 @@ cosign verify-blob-attestation [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index 016a04da5de..8b2b715e1a8 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -5,10 +5,11 @@ 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. +You may specify either a key, a bundle (optionally with trusted root), or a kms reference to verify against. + If you use a key, bundle, or trusted root, 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 preferred way to provide verification material is via a Sigstore bundle using --bundle, +which contains the signature, certificate, and transparency log proof. The blob may be specified as a path to a file or - for stdin. ``` @@ -18,46 +19,34 @@ cosign verify-blob [flags] ### Examples ``` - cosign verify-blob (--key ||)|(--certificate ) --signature + cosign verify-blob --bundle --certificate-identity --certificate-oidc-issuer - # Verify a simple blob and message - cosign verify-blob --key cosign.pub (--signature | msg) + # Verify a signature with a bundle and trusted root + cosign verify-blob --bundle artifact.sigstore.json --trusted-root trusted_root.json - # Verify a signature with certificate and CA certificate chain - cosign verify-blob --certificate cert.pem --certificate-chain certchain.pem --signature $sig + # Verify a blob (keyless) + cosign verify-blob --bundle artifact.sigstore.json --certificate-identity foo@example.com --certificate-oidc-issuer https://accounts.google.com - # 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 blob with an on-disk public key + cosign verify-blob --bundle artifact.sigstore.json --key cosign.pub - # Verify a signature from an environment variable - cosign verify-blob --key cosign.pub --signature $sig msg + # Verify a blob against Azure Key Vault + cosign verify-blob --bundle artifact.sigstore.json --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] - # verify a signature with public key provided by URL - cosign verify-blob --key https://host.for/ --signature $sig msg + # Verify a blob against AWS KMS + cosign verify-blob --bundle artifact.sigstore.json --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] - # verify a signature with signature and key provided by URL - cosign verify-blob --key https://host.for/ --signature https://example.com/ + # Verify a blob against Google Cloud KMS + cosign verify-blob --bundle artifact.sigstore.json --key gcpkms://projects/[PROJECT ID]/locations/[LOCATION]/keyRings/[KEYRING]/cryptoKeys/[KEY] - # Verify a signature against Azure Key Vault - cosign verify-blob --key azurekms://[VAULT_NAME][VAULT_URI]/[KEY] --signature $sig + # Verify a blob against Hashicorp Vault + cosign verify-blob --bundle artifact.sigstore.json --key hashivault://[KEY] - # Verify a signature against AWS KMS - cosign verify-blob --key awskms://[ENDPOINT]/[ID/ALIAS/ARN] --signature $sig + # Verify a blob against GitLab with project name + cosign verify-blob --bundle artifact.sigstore.json --key gitlab://[OWNER]/[PROJECT_NAME] - # 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 + # Verify a blob against GitLab with project id + cosign verify-blob --bundle artifact.sigstore.json --key gitlab://[PROJECT_ID] ``` @@ -65,10 +54,6 @@ cosign verify-blob [flags] ``` --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 @@ -78,23 +63,14 @@ cosign verify-blob [flags] --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. + --trusted-root string Path to a Sigstore TrustedRoot JSON file --use-signed-timestamps verify rfc3161 timestamps ``` @@ -108,5 +84,5 @@ cosign verify-blob [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index 42ee11c7b28..c699f4a799d 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -28,23 +28,11 @@ cosign verify [flags] # 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 a trusted root + cosign verify --trusted-root trusted_root.json # verify image with public key provided by URL cosign verify --key https://host.for/[FILE] @@ -71,54 +59,39 @@ cosign verify [flags] ### 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 + --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 + --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) + -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) + -o, --output string output format for the signing image information (json|text) (default "json") + --payload string payload path or remote URL + --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 + --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) + --trusted-root string Path to a Sigstore TrustedRoot JSON file + --use-signed-timestamps verify rfc3161 timestamps ``` ### Options inherited from parent commands @@ -131,5 +104,5 @@ cosign verify [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/doc/cosign_version.md b/doc/cosign_version.md index 924b0563ebf..46cbbcff545 100644 --- a/doc/cosign_version.md +++ b/doc/cosign_version.md @@ -23,5 +23,5 @@ cosign version [flags] ### SEE ALSO -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. +* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry diff --git a/go.mod b/go.mod index aae71c6b087..6814c3be45a 100644 --- a/go.mod +++ b/go.mod @@ -1,91 +1,87 @@ -module github.com/sigstore/cosign/v2 +module github.com/sigstore/cosign/v3 -go 1.24.3 +go 1.26.0 require ( - cuelang.org/go v0.12.1 + cloud.google.com/go/compute/metadata v0.9.0 + cuelang.org/go v0.16.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.103.0 + github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.12.0 + github.com/buildkite/agent/v3 v3.127.2 github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 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.4.0 - github.com/google/certificate-transparency-go v1.3.2 + github.com/go-jose/go-jose/v4 v4.1.4 + github.com/go-openapi/runtime v0.32.3 + github.com/go-openapi/strfmt v0.26.3 + github.com/go-openapi/swag/conv v0.26.1 + github.com/go-piv/piv-go/v2 v2.6.0 + github.com/google/certificate-transparency-go v1.3.3 github.com/google/go-cmp v0.7.0 - github.com/google/go-containerregistry v0.20.6 - github.com/google/go-github/v73 v73.0.0 - github.com/in-toto/attestation v1.1.2 - github.com/in-toto/in-toto-golang v0.9.0 + github.com/google/go-containerregistry v0.21.6 + github.com/google/go-github/v88 v88.0.0 + github.com/in-toto/attestation v1.2.0 + github.com/in-toto/in-toto-golang v0.11.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/manifoldco/promptui v0.9.0 - github.com/miekg/pkcs11 v1.1.1 + github.com/miekg/pkcs11 v1.1.2 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.6.0 - github.com/secure-systems-lab/go-securesystemslib v0.9.0 - github.com/sigstore/fulcio v1.7.1 - github.com/sigstore/protobuf-specs v0.5.0 - github.com/sigstore/rekor v1.3.10 - github.com/sigstore/rekor-tiles v0.1.7-0.20250624231741-98cd4a77300f - github.com/sigstore/sigstore v1.9.5 - github.com/sigstore/sigstore-go v1.1.0 - github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.5 - github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.5 - github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.5 - github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.5 - github.com/sigstore/timestamp-authority v1.2.8 - github.com/spf13/cobra v1.9.1 - github.com/spf13/pflag v1.0.7 - 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.1.1 + github.com/open-policy-agent/opa v1.17.1 + github.com/secure-systems-lab/go-securesystemslib v0.11.0 + github.com/sigstore/protobuf-specs v0.5.1 + github.com/sigstore/rekor v1.5.2 + github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 + github.com/sigstore/sigstore v1.10.8 + github.com/sigstore/sigstore-go v1.2.0 + github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 + github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 + github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8 + github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8 + github.com/sigstore/timestamp-authority/v2 v2.1.2 + github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 + github.com/spf13/viper v1.21.0 + github.com/spiffe/go-spiffe/v2 v2.7.0 + github.com/stretchr/testify v1.11.1 + github.com/theupdateframework/go-tuf/v2 v2.4.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.137.0 - golang.org/x/crypto v0.40.0 - golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.16.0 - golang.org/x/term v0.33.0 - google.golang.org/api v0.243.0 - google.golang.org/protobuf v1.36.6 - k8s.io/api v0.33.3 - k8s.io/apimachinery v0.33.3 - k8s.io/client-go v0.33.3 - k8s.io/utils v0.0.0-20241210054802-24370beab758 - sigs.k8s.io/release-utils v0.12.0 + gitlab.com/gitlab-org/api/client-go v1.46.0 + golang.org/x/crypto v0.53.0 + golang.org/x/oauth2 v0.36.0 + golang.org/x/sync v0.21.0 + golang.org/x/term v0.44.0 + google.golang.org/api v0.283.0 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af + k8s.io/api v0.36.1 + k8s.io/apimachinery v0.36.1 + k8s.io/client-go v0.36.1 + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 + sigs.k8s.io/release-utils v0.12.4 ) require ( - cel.dev/expr v0.24.0 // indirect - cloud.google.com/go v0.121.1 // indirect - cloud.google.com/go/auth v0.16.3 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 // indirect + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.7.0 // indirect - cloud.google.com/go/iam v1.5.2 // indirect - cloud.google.com/go/kms v1.22.0 // indirect - cloud.google.com/go/longrunning v0.6.7 // indirect - cloud.google.com/go/monitoring v1.24.2 // indirect - cloud.google.com/go/spanner v1.82.0 // indirect - cloud.google.com/go/storage v1.55.0 // indirect - cuelabs.dev/go/oci/ociregistry v0.0.0-20241125120445-2c00c104c6e1 // indirect - filippo.io/edwards25519 v1.1.0 // indirect + cloud.google.com/go/iam v1.11.0 // indirect + cloud.google.com/go/kms v1.31.0 // indirect + cloud.google.com/go/longrunning v1.0.0 // indirect + connectrpc.com/connect v1.20.0 // indirect + cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819 // 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.18.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // 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/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // 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 @@ -95,11 +91,7 @@ require ( 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.4.2 // indirect - github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect @@ -114,84 +106,84 @@ require ( 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/avast/retry-go/v4 v4.6.1 // indirect - github.com/aws/aws-sdk-go v1.55.7 // indirect - github.com/aws/aws-sdk-go-v2 v1.36.6 // indirect - github.com/aws/aws-sdk-go-v2/config v1.29.18 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.71 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // 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.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.41.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 // indirect - github.com/aws/smithy-go v1.22.4 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.18 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.17 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect + github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.52.0 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect + github.com/aws/smithy-go v1.25.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect - github.com/buildkite/go-pipeline v0.14.0 // indirect + github.com/buildkite/go-pipeline v0.17.1 // indirect github.com/buildkite/interpolate v0.1.5 // indirect - github.com/buildkite/roko v1.3.1 // indirect + github.com/buildkite/roko v1.4.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cenkalti/backoff/v5 v5.0.2 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // 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/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // 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.14.1 // indirect + github.com/coreos/go-oidc/v3 v3.18.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect - github.com/docker/cli v28.2.2+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/docker/cli v29.4.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.5 // 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/envoyproxy/go-control-plane/envoy v1.32.4 // indirect - github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/emicklei/proto v1.14.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/globocom/go-buffer v1.2.2 // 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/fsnotify/fsnotify v1.10.1 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-chi/chi/v5 v5.3.0 // indirect github.com/go-logr/logr v1.4.3 // 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-sql-driver/mysql v1.9.2 // indirect - github.com/go-viper/mapstructure/v2 v2.3.0 // indirect + github.com/go-openapi/analysis v0.25.2 // indirect + github.com/go-openapi/errors v0.22.7 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.6 // indirect + github.com/go-openapi/loads v0.23.3 // indirect + github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect + github.com/go-openapi/spec v0.22.5 // indirect + github.com/go-openapi/swag v0.26.0 // indirect + github.com/go-openapi/swag/cmdutils v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.26.0 // indirect + github.com/go-openapi/swag/mangling v0.26.0 // indirect + github.com/go-openapi/swag/netutils v0.26.0 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.1 // indirect + github.com/go-openapi/swag/yamlutils v0.26.0 // indirect + github.com/go-openapi/validate v0.25.3 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect + github.com/goccy/go-json v0.10.6 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-querystring v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/google/trillian v1.7.2 // 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.15.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.16 // indirect + github.com/googleapis/gax-go/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // 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 @@ -200,112 +192,106 @@ require ( github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcl v1.0.1-vault-5 // indirect - github.com/hashicorp/vault/api v1.16.0 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/hashicorp/vault/api v1.22.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect - github.com/jackc/puddle/v2 v2.2.2 // 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/jellydator/ttlcache/v3 v3.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.6 // 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/lestrrat-go/blackmagic v1.0.4 // indirect + github.com/lestrrat-go/dsig v1.2.1 // indirect + github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc/v3 v3.0.5 // indirect + github.com/lestrrat-go/jwx/v3 v3.1.1 // indirect + github.com/lestrrat-go/option/v2 v2.0.0 // indirect + github.com/letsencrypt/boulder v0.20260309.0 // indirect + github.com/lib/pq v1.12.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/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oklog/ulid v1.3.1 // indirect + github.com/natefinch/atomic v1.0.1 // indirect + github.com/oklog/ulid/v2 v2.1.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.1 // 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.4 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.63.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/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.20.1 // indirect + github.com/protocolbuffers/txtpbfmt v0.0.0-20260217160748-a481f6a22f94 // indirect + github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/rogpeppe/go-internal v1.14.1 // 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/sagikazarmark/locafero v0.11.0 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect - github.com/segmentio/ksuid v1.0.4 // indirect + github.com/segmentio/asm v1.2.1 // 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/sirupsen/logrus v1.9.4 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // 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/tchap/go-patricia/v2 v2.3.3 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect - github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 // indirect + github.com/tink-crypto/tink-go-awskms/v3 v3.0.0 // indirect github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 // indirect - github.com/tink-crypto/tink-go-hcvault/v2 v2.3.0 // indirect - github.com/tink-crypto/tink-go/v2 v2.4.0 // indirect + github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0 // indirect + github.com/tink-crypto/tink-go/v2 v2.6.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect - github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26 // indirect - github.com/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823 // indirect + github.com/transparency-dev/formats v0.1.1 // indirect github.com/urfave/negroni v1.0.0 // indirect - github.com/vbatts/tar-split v0.12.1 // indirect - github.com/vektah/gqlparser/v2 v2.5.28 // indirect + github.com/valyala/fastjson v1.6.10 // indirect + github.com/vektah/gqlparser/v2 v2.5.33 // indirect github.com/x448/float16 v0.8.4 // 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.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.37.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect - go.step.sm/crypto v0.67.0 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.step.sm/crypto v0.81.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-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/mod v0.26.0 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.35.0 // indirect - google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect - google.golang.org/grpc v1.73.0 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + go.uber.org/zap v1.28.0 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.45.0 // indirect + google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/grpc v1.81.1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/ini.v1 v1.67.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 2f111d8bcf7..99f5ba778ff 100644 --- a/go.sum +++ b/go.sum @@ -1,652 +1,50 @@ -al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= -al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= +al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 h1:s6hzCXtND/ICdGPTMGk7C+/BFlr2Jg5GyH0NKf4XGXg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw= -cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= -cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= -cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= -cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= -cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= -cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= -cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= -cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= -cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= -cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= -cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= -cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= -cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= -cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= -cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= -cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= -cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= -cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= -cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= -cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= -cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= -cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= -cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= -cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= -cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= -cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= -cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= -cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= -cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= -cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= -cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= -cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= -cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= -cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= -cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= -cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= -cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= -cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= -cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= -cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= -cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= -cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc= -cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= -cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= -cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= -cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= -cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= -cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= -cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= -cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= -cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= -cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= -cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= -cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= -cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= -cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= -cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= -cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= -cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= -cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= -cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= -cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= -cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= -cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= -cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= -cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= -cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= -cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= -cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= -cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= -cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= -cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= -cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= -cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= -cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= -cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= -cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= -cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= -cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= -cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= -cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= -cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= -cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= -cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= -cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= -cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= -cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= -cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= -cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= -cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= -cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= -cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= -cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= -cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= -cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= -cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= -cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= -cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= -cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= -cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= -cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= -cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= -cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= -cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= -cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= -cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= -cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= -cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= -cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= -cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= -cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= -cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= -cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= -cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= -cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= -cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= -cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= -cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= -cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= -cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= -cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= -cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= -cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= -cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= -cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= -cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= -cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= -cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= -cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= -cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= -cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= -cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= -cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= -cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= -cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= -cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= -cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= -cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= -cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= -cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= -cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= -cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= -cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= -cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= -cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= -cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= -cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= -cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= -cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= -cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= -cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= -cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= -cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= -cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= -cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= -cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= -cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= -cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= -cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= -cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= -cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= -cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= -cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= -cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= -cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= -cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk= -cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= -cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= -cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= -cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= -cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= -cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= -cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= -cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= -cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= -cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= -cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= -cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= -cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= -cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= -cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= -cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= -cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= -cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= -cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= -cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= -cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= -cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= -cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= -cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= -cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= -cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= -cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= -cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= -cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= -cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= -cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= -cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= -cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= -cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= -cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= -cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= -cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= -cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= -cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= -cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= -cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= -cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= -cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= -cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= -cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= -cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= -cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= -cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= -cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= -cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= -cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= -cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= -cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= -cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= -cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= -cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= -cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= -cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= -cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= -cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= -cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= -cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= -cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= -cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= -cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= -cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= -cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= -cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= -cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= -cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= -cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= -cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= -cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= -cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= -cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= -cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= -cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= -cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= -cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= -cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= -cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= -cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= -cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= -cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= -cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= -cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/spanner v1.82.0 h1:w9uO8RqEoBooBLX4nqV1RtgudyU2ZX780KTLRgeVg60= -cloud.google.com/go/spanner v1.82.0/go.mod h1:BzybQHFQ/NqGxvE/M+/iU29xgutJf7Q85/4U9RWMto0= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= -cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= -cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= -cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= -cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= -cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= -cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= -cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= -cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= -cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= -cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= -cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= -cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= -cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= -cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= -cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= -cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= -cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= -cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= -cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= -cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= -cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= -cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= -cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= -cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= -cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= -cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= -cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= -cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= -cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= -cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= -cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= -cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= -cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= -cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= -cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= -cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= -cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= -cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= -cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= -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= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.11.0 h1:KieQ9Pb+LLPak1O3Rv3GgCxhnmkYf7Xyh0P5HfF1jFM= +cloud.google.com/go/iam v1.11.0/go.mod h1:KP+nKGugNJW4LcLx1uEZcq1ok5sQHFaQehQNl4QDgV4= +cloud.google.com/go/kms v1.31.0 h1:LS8N92OxFDgOLg5NCo3OmbvjtQAIVT5gUHVLKIDHaFE= +cloud.google.com/go/kms v1.31.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U= +cloud.google.com/go/longrunning v1.0.0 h1:lwzWEYD8+NkYV7dhexOz6kmlvajZA70+bW/xMhRVVdY= +cloud.google.com/go/longrunning v1.0.0/go.mod h1:8nqFBPOO1U/XkhWl0I19AMZEphrHi73VNABIpKYaTwM= +connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= +connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= +cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819 h1:Zh+Ur3OsoWpvALHPLT45nOekHkgOt+IOfutBbPqM17I= +cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819/go.mod h1:WjmQxb+W6nVNCgj8nXrF24lIz95AHwnSl36tpjDZSU8= +cuelang.org/go v0.16.1 h1:iPN1lHZd2J0hjcr8hfq9PnIGk7VfPkKFfxH4de+m9sE= +cuelang.org/go v0.16.1/go.mod h1:/aW3967FeWC5Hc1cDrN4Z4ICVApdMi83wO5L3uF/1hM= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e h1:VsUbObBMxXlc23Eb9VeeJYE4jvTs87qa5RqSN2U5FJU= +filippo.io/mldsa v0.0.0-20260215214346-43d0283efc3e/go.mod h1:32qQ5yj3R24Eu03iWFWchdC3OB653wPvoepWejkefbY= 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.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= 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.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -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/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 h1:MaKvxE6D0KkjOg6Wd9M00iqP5PR0kUxCfiezes4JweM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0/go.mod h1:i2h9fsTFKZorh8RdV2IcSUf/Qj98GlTkrTvUbX/s8as= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= 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= @@ -674,34 +72,15 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM 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.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= -github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= -github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= 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.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= -github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= -github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= 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= @@ -746,79 +125,65 @@ github.com/aliyun/credentials-go v1.3.2 h1:L4WppI9rctC8PdlMgyTkF8bBsy9pyKQEzBD1b github.com/aliyun/credentials-go v1.3.2/go.mod h1:tlpz4uys4Rn7Ik4/piGRrTbXy2uLKvePgQJJduE+Y5c= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= -github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= -github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= 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/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/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk= -github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA= -github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= -github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU= -github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= -github.com/aws/aws-sdk-go-v2/config v1.29.18 h1:x4T1GRPnqKV8HMJOMtNktbpQMl3bIsfx8KbqmveUO2I= -github.com/aws/aws-sdk-go-v2/config v1.29.18/go.mod h1:bvz8oXugIsH8K7HLhBv06vDqnFv3NsGDt2Znpk7zmOU= -github.com/aws/aws-sdk-go-v2/credentials v1.17.71 h1:r2w4mQWnrTMJjOyIsZtGp3R3XGY3nqHn8C26C2lQWgA= -github.com/aws/aws-sdk-go-v2/credentials v1.17.71/go.mod h1:E7VF3acIup4GB5ckzbKFrCK0vTvEQxOxgdq4U3vcMCY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 h1:D9ixiWSG4lyUBL2DDNK924Px9V/NBVpML90MHqyTADY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33/go.mod h1:caS/m4DI+cij2paz3rtProRBI4s/+TCiWoaWZuQ9010= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs= -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.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 h1:vvbXsA2TVO80/KT7ZqCbx934dt6PY+vQ8hZpUZ/cpYg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18/go.mod h1:m2JJHledjBGNMsLOF1g9gbAxprzq3KjC8e4lxtn+eWg= -github.com/aws/aws-sdk-go-v2/service/kms v1.41.3 h1:P0mjq/4mqTRA8SlS/4jL946RBW287kkKI/fazTTDJ3E= -github.com/aws/aws-sdk-go-v2/service/kms v1.41.3/go.mod h1:79gw7fH6dqzJz3a5qwDnQv5GDPs8b6eJIb9hJ+/c/YU= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 h1:rGtWqkQbPk7Bkwuv3NzpE/scwwL9sC1Ul3tn9x83DUI= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.6/go.mod h1:u4ku9OLv4TO4bCPdxf4fA1upaMaJmP9ZijGk3AAOC6Q= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 h1:OV/pxyXh+eMA0TExHEC4jyWdumLxNbzz1P0zJoezkJc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4/go.mod h1:8Mm5VGYwtm+r305FfPSuc+aFkrypeylGYhFim6XEPoc= -github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 h1:aUrLQwJfZtwv3/ZNG2xRtEen+NqI3iesuacjP51Mv1s= -github.com/aws/aws-sdk-go-v2/service/sts v1.34.1/go.mod h1:3wFBZKoWnX3r+Sm7in79i54fBmNfwhdNdQuscCw7QIk= -github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= -github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= -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/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= +github.com/aws/aws-sdk-go-v2/config v1.32.18 h1:Hcia46bxhGgF3BaSnG8nSNCWmqTK6bj9xN9/FJ3WK6Q= +github.com/aws/aws-sdk-go-v2/config v1.32.18/go.mod h1:zEjCAYmxqDadH1WX8CdBvmLKhUEUVFgKRQG38zjDmrY= +github.com/aws/aws-sdk-go-v2/credentials v1.19.17 h1:gP2nkGsS+KMvF/jfFz2Vv2qiiOqWKyPACSzPsqHgoW8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.17/go.mod h1:Bsew3S/moG5iT77giPj1q8wb/s0RE5/QfH+ASjYtuQc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= +github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3 h1:RtGctYMmkTerGClvdY6bHXdtly4FeYw9wz/NPz62LF8= +github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3/go.mod h1:vBfBu24Ka3/5UZtepbTV0gnc9VPLT8ok+0oDDaYAzn4= +github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.10 h1:1A/sI3LNMi3fhRI5TFLMwwo7ALAALSFVCSGvFlr1Iys= +github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.38.10/go.mod h1:Diyyyz0b43X13pdi1mVMqlTwDjOmRbJMvDsqnduUYWM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= +github.com/aws/aws-sdk-go-v2/service/kms v1.52.0 h1:QNtg+Mtj1zmepk568+UKBD5DFfqh+ESTUUqQT27JkQc= +github.com/aws/aws-sdk-go-v2/service/kms v1.52.0/go.mod h1:Y0+uxvxz6ib4KktRdK0V4X45Vcs/JyYoz8H71pO8xeI= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.0 h1:nDARhv/oF55bcxF7rCI/4PDxOKnVXVWwDuDwCs2I2SQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.0/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio= +github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.12.0 h1:JFWXO6QPihCknDdnL6VaQE57km4ZKheHIGd9YiOGcTo= +github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.12.0/go.mod h1:046/oLyFlYdAghYQE2yHXi/E//VM5Cf3/dFmA+3CZ0c= 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/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/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/buildkite/agent/v3 v3.103.0 h1:yzHF1gVuSNHO+fsOyyJun95fx8oTSGQmdudjKTaaW8M= -github.com/buildkite/agent/v3 v3.103.0/go.mod h1:5N1KWTrYKq7D2k7g10Hit6DnQ0jfjKgE3JPnJVr5teM= -github.com/buildkite/go-pipeline v0.14.0 h1:TMkFalrkniy2l5wEfmGyckT5kf21akWOY07i4esosAI= -github.com/buildkite/go-pipeline v0.14.0/go.mod h1:VE37qY3X5pmAKKUMoDZvPsHOQuyakB9cmXj9Qn6QasA= +github.com/buildkite/agent/v3 v3.127.2 h1:FVTyxD0VDzioAt//9ZcAIwdwnaI4niWNBxvqNybTdqo= +github.com/buildkite/agent/v3 v3.127.2/go.mod h1:xeL4YeMdZBxwmV07/SfLHG/13BTEp6FkqldhRPQQaHE= +github.com/buildkite/go-pipeline v0.17.1 h1:m7+a2SF9hLjqsbe6HShmzm9M4rDk4QtanKTF2LD6LG0= +github.com/buildkite/go-pipeline v0.17.1/go.mod h1:VE37qY3X5pmAKKUMoDZvPsHOQuyakB9cmXj9Qn6QasA= 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/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/buildkite/roko v1.4.0 h1:DxixoCdpNqxu4/1lXrXbfsKbJSd7r1qoxtef/TT2J80= +github.com/buildkite/roko v1.4.0/go.mod h1:0vbODqUFEcVf4v2xVXRfZZRsqJVsCCHTG/TBRByGK4E= +github.com/bytecodealliance/wasmtime-go/v44 v44.0.0 h1:WRZXnLPIer/TWs5aYPaMlmVcOlzmR6Ur6wjLRIQOhTQ= +github.com/bytecodealliance/wasmtime-go/v44 v44.0.0/go.mod h1:GP93piU+39CoFVCQ5xfHrPOUtL0APlMnkbblJ2d3YY0= 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/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= -github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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= @@ -837,52 +202,37 @@ github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyM 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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= -github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= 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.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= -github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= +github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A= +github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.19 h1:tUN6H7LWqNx4hQVxomd0CVsDwaDr9gaRQaI4GpSmrsA= -github.com/creack/pty v1.1.19/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= -github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= -github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= +github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= +github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= 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/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= 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.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= -github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= +github.com/dgraph-io/badger/v4 v4.9.1 h1:DocZXZkg5JJHJPtUErA0ibyHxOVUDVoXLSCV6t8NC8w= +github.com/dgraph-io/badger/v4 v4.9.1/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= -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= @@ -892,285 +242,186 @@ github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1G 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 v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A= -github.com/docker/cli v28.2.2+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.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= -github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= +github.com/docker/cli v29.4.3+incompatible h1:u+UliYm2J/rYrIh2FqHQg32neRG8GjbvNuwQRTzGspU= +github.com/docker/cli v29.4.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= 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/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q= +github.com/emicklei/proto v1.14.3/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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= -github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= -github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 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/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= +github.com/foxcpp/go-mockdns v1.2.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.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globocom/go-buffer v1.2.2 h1:ICgtlUe5GIYIZFdAVj57+5WYBR4DA56cX+PYZDhGDwc= -github.com/globocom/go-buffer v1.2.2/go.mod h1:kY1ALQS0ChiiThmWhsFoT5CYSiuad0t3keIew5LsWdM= -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-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -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-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= +github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/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-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-piv/piv-go/v2 v2.4.0 h1:xamQ/fR4MJiw/Ndbk6yi7MVwhjrwlnDAPuaH9zcGb+I= -github.com/go-piv/piv-go/v2 v2.4.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI= +github.com/go-openapi/analysis v0.25.2 h1:I0vy4n3alz+DHTiN1PRhCb7QZxkK6g5YmswZKv2TKuw= +github.com/go-openapi/analysis v0.25.2/go.mod h1:Uhs1t/2XR10EnwONYILGEzw8gcfGIG5Xk5K2AxnhqDo= +github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA= +github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= +github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= +github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ= +github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA= +github.com/go-openapi/runtime v0.32.3 h1:J7Ycy5DJmhhP1By3NifhRUjnkXTrk21qbeqSULjwX8U= +github.com/go-openapi/runtime v0.32.3/go.mod h1:/WTQi0fa5DiGnnCXQKsTkSm15OzJp8Uz3H2t+67TBr4= +github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= +github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= +github.com/go-openapi/spec v0.22.5 h1:KhO7RBlKQfonUWX2WzQCoLIXVA6AcNqDGZ3a1Dutdlo= +github.com/go-openapi/spec v0.22.5/go.mod h1:vxpOtMya5TXtENXKE5bKqv5NjocVhyhxHrlZfvKnZ74= +github.com/go-openapi/strfmt v0.26.3 h1:rzmslHarJgBbf2qfGge+X3htclQfmXqBZMm0Too0HhU= +github.com/go-openapi/strfmt v0.26.3/go.mod h1:a5nsUw0oRpQzZeOwx8bi6cKbzFZslpbCKt1LEot+KnQ= +github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= +github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= +github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= +github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.1 h1:slr5FVkg9Wc3Y5zcwenD8Sd/PQ94b2I/QJI7N7KTBpg= +github.com/go-openapi/swag/conv v0.26.1/go.mod h1:mvQXgPptZk9GTrFgGwWvT4q+dN+zQej9JfmGwnipz1A= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= +github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= +github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= +github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.1 h1:yg42FgMzRR6PVQ3M3qHz1s+Y6/P4HoJ3cBarXa3OVnU= +github.com/go-openapi/swag/typeutils v0.26.1/go.mod h1:VfnV+oUtSP2vCSCn2aJgnr8OevUYemyIzzS1VOzS10o= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= +github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo= +github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-openapi/validate v0.25.3 h1:4nzAIavcJ7WveHK2+V1UAkZK3kWcjzxZCzjfZAfavKs= +github.com/go-openapi/validate v0.25.3/go.mod h1:GemfuGMyYpIaBoKpX3z8sLywrmxpzWVOoJ7R0VeAVuk= +github.com/go-piv/piv-go/v2 v2.6.0 h1:/Z+uqlv5luC8aqLh/uO05egk23UH4KT/ldvPHXpnKls= +github.com/go-piv/piv-go/v2 v2.6.0/go.mod h1:gcsS2WGbToZk5PwtsCIlCWzV/R+r/wN+Xi+6O6IlU3c= 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.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= -github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/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/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -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/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= 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/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= -github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3/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.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A= -github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs= -github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/certificate-transparency-go v1.3.3 h1:hq/rSxztSkXN2tx/3jQqF6Xc0O565UQPdHrOWvZwybo= +github.com/google/certificate-transparency-go v1.3.3/go.mod h1:iR17ZgSaXRzSa5qvjFl8TnVD5h8ky2JMVio+dzoKMgA= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/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.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU= -github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y= -github.com/google/go-github/v73 v73.0.0 h1:aR+Utnh+Y4mMkS+2qLQwcQ/cF9mOTpdwnzlaw//rG24= -github.com/google/go-github/v73 v73.0.0/go.mod h1:fa6w8+/V+edSU0muqdhCVY7Beh1M8F1IlQPZIANKIYw= -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/go-containerregistry v0.21.6 h1:T+yqQIlJXKrM98Om4DlW3GoWQAmhZuLMwoDOvVrtiUM= +github.com/google/go-containerregistry v0.21.6/go.mod h1:U7MMSBIJynke2MVQrQk19NP9k/uQsGz/h0amIFSHMbo= +github.com/google/go-github/v88 v88.0.0 h1:dZA9IKkPK1eXZj4ypngnpRj5FwdpTv4whix2PrQMP7M= +github.com/google/go-github/v88 v88.0.0/go.mod h1:rufTDgn2N45wjhukLTyxmvc9nilSp3mr3Rgtt6b1MPw= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= 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/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= -github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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/trillian v1.7.2 h1:EPBxc4YWY4Ak8tcuhyFleY+zYlbCDCa4Sn24e1Ka8Js= -github.com/google/trillian v1.7.2/go.mod h1:mfQJW4qRH6/ilABtPYNBerVJAJ/upxHLX81zxNQw05s= +github.com/google/trillian v1.7.3 h1:hziW+vo4czis48tzx2GK5xRBl/ZxBA9B0/UR5avXOro= +github.com/google/trillian v1.7.3/go.mod h1:qh8iy4x/GvnVXUBd5pK4oncuT1Y9vVYfibQVsR/WpKg= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.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.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -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.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/googleapis/enterprise-certificate-proxy v0.3.16 h1:F/VPrx0YPBdksZJQdCAp0WUsqnNmZpUZszzfYt0M5Dw= +github.com/googleapis/enterprise-certificate-proxy v0.3.16/go.mod h1:9Yb0eAkH/Xqhvv3zbeKf/+wMJqCeocWc6KIhDvEAuYE= +github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= +github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= 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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/graph-gophers/graphql-go v1.9.0 h1:yu0ucKHLc5qGpRwLYKIWtr9bOoxovkWasuBrPQwlHls= +github.com/graph-gophers/graphql-go v1.9.0/go.mod h1:23olKZ7duEvHlF/2ELEoSZaY1aNPfShjP782SOoNTyM= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= 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= @@ -1190,70 +441,36 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9 github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -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/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= 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/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E= -github.com/in-toto/attestation v1.1.2/go.mod h1:gYFddHMZj3DiQ0b62ltNi1Vj5rC879bTmBbrv9CRHpM= -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/in-toto/attestation v1.2.0 h1:aPRUZ3azbqD7yEBD5fP3TD8Dszf+YHo284SOcpahjQk= +github.com/in-toto/attestation v1.2.0/go.mod h1:r79G45gOmzPismgObLSL+rZTFxUgZLOQJI6LofTZgXk= +github.com/in-toto/in-toto-golang v0.11.0 h1:nfidMYBFx+E0lnmX5KUnN2Pdm8zdNKal1ayjJuzzRoA= +github.com/in-toto/in-toto-golang v0.11.0/go.mod h1:u3PjTnwFKjp5a1YCcw8SJg0G+tMeKfVoWsWeFMDCMtw= 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/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.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= -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/jellydator/ttlcache/v3 v3.4.0 h1:YS4P125qQS0tNhtL6aeYkheEaB/m8HCqdMMP4mnWdTY= +github.com/jellydator/ttlcache/v3 v3.4.0/go.mod h1:Hw9EgjymziQD3yGsQdf1FqFdpp7YjFMd4Srg5EJlgD4= 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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 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.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= -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/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= 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= @@ -1262,31 +479,35 @@ 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/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= -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/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= +github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/dsig v1.2.1 h1:MwxzZhE4+4fguHi+uDALKVlC3Cn+O1QU1Q/F8D7hVIc= +github.com/lestrrat-go/dsig v1.2.1/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc/v3 v3.0.5 h1:S+Mb4L2I+bM6JGTibLmxExhyTOqnXjqx+zi9MoXw/TM= +github.com/lestrrat-go/httprc/v3 v3.0.5/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= +github.com/lestrrat-go/jwx/v3 v3.1.1 h1:yd9AdPmZ4INnQ7k42IrzXYpnEG803+SrQ6hdMvzHJzw= +github.com/lestrrat-go/jwx/v3 v3.1.1/go.mod h1:uw/MN2M/Xiu4FhwcIwH11Zsh9JWx9SWzgALl7/uIEkU= +github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= +github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= +github.com/letsencrypt/boulder v0.20260309.0 h1:kZynrxK3QfqLGx6hhoz+Rfs3hgltJs1p9Mp+4+VwnY0= +github.com/letsencrypt/boulder v0.20260309.0/go.mod h1:yG8lj8pNPZ8taq3oNdTpfBS+eC74IaEuiewqzVpXiWE= +github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= +github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= 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.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -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/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= 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/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/miekg/pkcs11 v1.1.2 h1:/VxmeAX5qU6Q3EwafypogwWbYryHFmF2RpkJmw3m4MQ= +github.com/miekg/pkcs11 v1.1.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= 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.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -1300,12 +521,15 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w 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/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= 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= @@ -1313,206 +537,167 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI 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/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= 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.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= 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.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 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 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= -github.com/open-policy-agent/opa v1.6.0 h1:/S/cnNQJ2MUMNzizHPbisTWBHowmLkPrugY5jjkPlRQ= -github.com/open-policy-agent/opa v1.6.0/go.mod h1:zFmw4P+W62+CWGYRDDswfVYSCnPo6oYaktQnfIaRFC4= +github.com/open-policy-agent/opa v1.17.1 h1:wO0MOux/VCqY41aVAD6Toe1p3A7O7DlRZ1RHmYSpoS8= +github.com/open-policy-agent/opa v1.17.1/go.mod h1:lcuZYSlqQpXFzsA6EJCELmfR5+nNOpZYX+eo7xaIIlk= 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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -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/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= 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.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 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/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= -github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -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.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= -github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/protocolbuffers/txtpbfmt v0.0.0-20260217160748-a481f6a22f94 h1:2PC6Ql3jipz1KvBlqUHjjk6v4aMwE86mfDu1XMH0LR8= +github.com/protocolbuffers/txtpbfmt v0.0.0-20260217160748-a481f6a22f94/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= 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/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= 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/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -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/secure-systems-lab/go-securesystemslib v0.11.0 h1:iuCR9kcMFD4QurdKrGvPLoKZLv9YvwPYVr0473BdtFs= +github.com/secure-systems-lab/go-securesystemslib v0.11.0/go.mod h1:+PMOTjUGwHj2vcZ+TFKlb1tXRbrdWE1LYDT5i9JC80Q= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 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.7.1 h1:RcoW20Nz49IGeZyu3y9QYhyyV3ZKQ85T+FXPKkvE+aQ= -github.com/sigstore/fulcio v1.7.1/go.mod h1:7lYY+hsd8Dt+IvKQRC+KEhWpCZ/GlmNvwIa5JhypMS8= -github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= -github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= -github.com/sigstore/rekor v1.3.10 h1:/mSvRo4MZ/59ECIlARhyykAlQlkmeAQpvBPlmJtZOCU= -github.com/sigstore/rekor v1.3.10/go.mod h1:JvryKJ40O0XA48MdzYUPu0y4fyvqt0C4iSY7ri9iu3A= -github.com/sigstore/rekor-tiles v0.1.7-0.20250624231741-98cd4a77300f h1:zaqWahYAlVouSm5qwCH+2vZ3eenZFBwzzuBz/IZyy5c= -github.com/sigstore/rekor-tiles v0.1.7-0.20250624231741-98cd4a77300f/go.mod h1:1Epq0PQ73v5Z276rAY241JyaP8gtD64I6sgYIECHPvc= -github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU= -github.com/sigstore/sigstore v1.9.5/go.mod h1:VtxgvGqCmEZN9X2zhFSOkfXxvKUjpy8RpUW39oCtoII= -github.com/sigstore/sigstore-go v1.1.0 h1:NBfyvL/LiBIplnIZAtC7GtDZ7qj82A/GTpn0+5WV7BM= -github.com/sigstore/sigstore-go v1.1.0/go.mod h1:97lDVpZVBCTFX114KPAManEsShVe934KyaVhZGhPVBM= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.5 h1:qp2VFyKuFQvTGmZwk5Q7m5nE4NwnF9tHwkyz0gtWAck= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.9.5/go.mod h1:DKlQjjr+GsWljEYPycI0Sf8URLCk4EbGA9qYjF47j4g= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.5 h1:CRZcdYn5AOptStsLRAAACudAVmb1qUbhMlzrvm7ju3o= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.9.5/go.mod h1:b9rFfITq2fp1M3oJmq6lFFhSrAz5vOEJH1qzbMsZWN4= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.5 h1:7U0GsO0UGG1PdtgS6wBkRC0sMgq7BRVaFlPRwN4m1Qg= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.9.5/go.mod h1:/2qrI0nnCy/DTIPOMFaZlFnNPWEn5UeS70P37XEM88o= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.5 h1:S2ukEfN1orLKw2wEQIUHDDlzk0YcylhcheeZ5TGk8LI= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.9.5/go.mod h1:m7sQxVJmDa+rsmS1m6biQxaLX83pzNS7ThUEyjOqkCU= -github.com/sigstore/timestamp-authority v1.2.8 h1:BEV3fkphwU4zBp3allFAhCqQb99HkiyCXB853RIwuEE= -github.com/sigstore/timestamp-authority v1.2.8/go.mod h1:G2/0hAZmLPnevEwT1S9IvtNHUm9Ktzvso6xuRhl94ZY= -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/sigstore/protobuf-specs v0.5.1 h1:/5OPaNuolRJmQfeZLayJGFXMpsRJEdgC6ah1/+7Px7U= +github.com/sigstore/protobuf-specs v0.5.1/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats= +github.com/sigstore/rekor v1.5.2 h1:k6pX4o1zFAzAvDbXiVIp5IHj1b0wcDaxsbsbNpuRO8o= +github.com/sigstore/rekor v1.5.2/go.mod h1:WkMnITBccOFauPkT6yte74tF5gC83pefKRGZvNOsbjI= +github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F6m3Bo/f59bZo5dv1sTIfUnQqVnepIdDV24KoDw= +github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= +github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= +github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= +github.com/sigstore/sigstore-go v1.2.0 h1:8k8sGMVUUWwZ/KA+s4Q66yEPEzcC1xZ8UsTgI46J9Fc= +github.com/sigstore/sigstore-go v1.2.0/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= +github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8/go.mod h1:YiTpAsxoWXhF9KlLOVWCh7BckN5cYO8X01WufDq1ido= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8 h1:MxpAIMZVzn0Tpbarc9ax1I498oQBp7oYSMgoMSsOmKI= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.10.8/go.mod h1:bnAUEkFNam6STvkVZhptVwWzWR5pS24CEtQ+lhxu7S0= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8 h1:1DGe4/clcdOnkz5MINEczWlmEvjUtZd+AjPPT/cBhQ8= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.10.8/go.mod h1:6IDFhpgxtzqbnzrFkyegbj7RfWwKeRrb3/+xAD1Wp+Y= +github.com/sigstore/timestamp-authority/v2 v2.1.2 h1:7DDhnknLL4w8VwomyvW2W8qblOS9LDR8oihna+jc7Ls= +github.com/sigstore/timestamp-authority/v2 v2.1.2/go.mod h1:o6rAVZceFyejClIj/uStRNIemP16bVMZtbMmhk6pr0U= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= 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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -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/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/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/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4= +github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= 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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -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/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= +github.com/tchap/go-patricia/v2 v2.3.3/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.1.1 h1:OWcoHItwsGO+7m0wLa7FDWPR4oB1cj0zOr1kosE4G+I= -github.com/theupdateframework/go-tuf/v2 v2.1.1/go.mod h1:V675cQGhZONR0OGQ8r1feO0uwtsTBYPDWHzAAPn5rjE= -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/theupdateframework/go-tuf/v2 v2.4.2 h1:w7976/W8uTwlsegP5nRymlpjPgrwSh+AXUf85is6nJk= +github.com/theupdateframework/go-tuf/v2 v2.4.2/go.mod h1:JqBrIUnNLAaNq/8GmBcEMFWfAFBbqp/MkJEJseXKbks= +github.com/tink-crypto/tink-go-awskms/v3 v3.0.0 h1:XSohRhCkXAVI0iaCnWB/GS05TEmpnKurQmzaY1jzt3Y= +github.com/tink-crypto/tink-go-awskms/v3 v3.0.0/go.mod h1:+7MXsShLzVbSQ6dI0Pe4JuZM52jD1jQ1itAygd/MDsA= 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-hcvault/v2 v2.3.0 h1:6nAX1aRGnkg2SEUMwO5toB2tQkP0Jd6cbmZ/K5Le1V0= -github.com/tink-crypto/tink-go-hcvault/v2 v2.3.0/go.mod h1:HOC5NWW1wBI2Vke1FGcRBvDATkEYE7AUDiYbXqi2sBw= -github.com/tink-crypto/tink-go/v2 v2.4.0 h1:8VPZeZI4EeZ8P/vB6SIkhlStrJfivTJn+cQ4dtyHNh0= -github.com/tink-crypto/tink-go/v2 v2.4.0/go.mod h1:l//evrF2Y3MjdbpNDNGnKgCpo5zSmvUvnQ4MU+yE2sw= +github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0 h1:eXuNqgrcYelxU1MVikOJDP3wTS5lvihM4ntoAbAMfvs= +github.com/tink-crypto/tink-go-hcvault/v2 v2.5.0/go.mod h1:3RhcxAqek6xUlRFmJifvU4CYLZN60KMQdIKqpZAZJG0= +github.com/tink-crypto/tink-go/v2 v2.6.0 h1:+KHNBHhWH33Vn+igZWcsgdEPUxKwBMEe0QC60t388v4= +github.com/tink-crypto/tink-go/v2 v2.6.0/go.mod h1:2WbBA6pfNsAfBwDCggboaHeB2X29wkU8XHtGwh2YIk8= 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/formats v0.0.0-20250421220931-bb8ad4d07c26 h1:YTbkeFbzcer+42bIgo6Za2194nKwhZPgaZKsP76QffE= -github.com/transparency-dev/formats v0.0.0-20250421220931-bb8ad4d07c26/go.mod h1:ODywn0gGarHMMdSkWT56ULoK8Hk71luOyRseKek9COw= +github.com/transparency-dev/formats v0.1.1 h1:4bVHJc+KdBgpA1OJD1yjI+g0i5Z1graCppTMH8lWKJI= +github.com/transparency-dev/formats v0.1.1/go.mod h1:qtZ8goRuJ8FTBG9c9+Bj0rn2rUG7eG/AUTkr+Aw3jFw= 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/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823 h1:s3p7wNrK/mnKI2bdp9PrQd9eBVxo1i5rU6O5hKkN0zc= -github.com/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823/go.mod h1:Jv2IDwG1q8QNXZTaI1X6QX8s96WlJn73ka2hT1n4N5c= 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.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= -github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= -github.com/vektah/gqlparser/v2 v2.5.28 h1:bIulcl3LF69ba6EiZVGD88y4MkM+Jxrf3P2MX8xLRkY= -github.com/vektah/gqlparser/v2 v2.5.28/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= +github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= +github.com/vektah/gqlparser/v2 v2.5.33 h1:lRp8aIeNUNbimf/axZd7ETg24q06hBtPaas+TcvI/7E= +github.com/vektah/gqlparser/v2 v2.5.33/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= 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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -1523,6 +708,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo 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/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= 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= @@ -1533,776 +720,217 @@ 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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= -github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -gitlab.com/gitlab-org/api/client-go v0.137.0 h1:H26yL44qnb38Czl20pEINCJrcj63W6/BX8iKPVUKQP0= -gitlab.com/gitlab-org/api/client-go v0.137.0/go.mod h1:AcAYES3lfkIS4zhso04S/wyUaWQmDYve2Fd9AF7C6qc= -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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -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/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= -go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= -go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= -go.step.sm/crypto v0.67.0 h1:1km9LmxMKG/p+mKa1R4luPN04vlJYnRLlLQrWv7egGU= -go.step.sm/crypto v0.67.0/go.mod h1:+AoDpB0mZxbW/PmOXuwkPSpXRgaUaoIK+/Wx/HGgtAU= +gitlab.com/gitlab-org/api/client-go v1.46.0 h1:YxBWFZIFYKcGESCb9fpkwzouo+apyB9pr/XTWzNoL24= +gitlab.com/gitlab-org/api/client-go v1.46.0/go.mod h1:FtgyU6g2HS5+fMhw6nLK96GBEEBx5MzntOiJWfIaiN8= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.step.sm/crypto v0.81.0 h1:e+ouzpNt3Xm4dp7HGXhgYB5y4iFik3vh3phHKWmvugU= +go.step.sm/crypto v0.81.0/go.mod h1:fsTizqQeASjTXnbv9O00XtRlIuXRkCdoRiJNyXGQujc= 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= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/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.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 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.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 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-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/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-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/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-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/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.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.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.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/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-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/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-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/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-20220328115105-d36c6a25d886/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-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/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.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/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.4.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.7.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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 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.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 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.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= 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-20190206041539-40960b6deb8e/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/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-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= -golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= 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-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.243.0 h1:sw+ESIJ4BVnlJcWu9S+p2Z6Qq1PjG77T8IJ1xtp4jZQ= -google.golang.org/api v0.243.0/go.mod h1:GE4QtYfaybx1KmeHMdBnNnyLzBZCVihGBXAmJu/uUr8= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.283.0 h1:0lkp8u0MPwJVHqRL+nJlMAoZVVzbmiXmFHXMOTmSPik= +google.golang.org/api v0.283.0/go.mod h1:6Wssta4c5n9qHq5CBhmlai5h/PUa1djdDAIhYEHyvcM= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= -google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 h1:1ZwqphdOdWYXsUHgMpU/101nCtf/kSp9hOrcvFsnl10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d h1:N1Ec54vZnIPd7MnxRiYLW+oY4fDR4BOS/LrssdD9+ek= +google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:c2hJ1grtnH0xUiEKGDGkjGNTJ1Hy2LrblyKOHF0sqRM= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= 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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 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/ini.v1 v1.67.2 h1:JtOSMb9OuaCZKr7h5D/h6iii14sK0hLbplTc6frx4Ss= +gopkg.in/ini.v1 v1.67.2/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= 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.3/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= @@ -2314,73 +942,28 @@ 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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= -k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= -k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= -k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= -k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= -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-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= -k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= +k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= +k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= +k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= +k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/release-utils v0.12.0 h1:+Z8cEUAaxItrMcTOJ0jtUg3Fm1uNgPNol+VIL6XtQqQ= -sigs.k8s.io/release-utils v0.12.0/go.mod h1:TveYRPK4Mq6qXA0PJiUMEOlWvvIQG0Mh5APQmHD5JpA= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/release-utils v0.12.4 h1:kuG6WTWGCKx5uUrJwl2uFErOKOw+4Ba8WrPmOQh5J3g= +sigs.k8s.io/release-utils v0.12.4/go.mod h1:Tc3iM9DVM3W9oJu/6rEI+LnREuhy8lZ7wInQhRBtUoo= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= 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= diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 00000000000..6a9e64a1221 --- /dev/null +++ b/internal/auth/auth.go @@ -0,0 +1,172 @@ +// 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 auth + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/go-jose/go-jose/v4" + "github.com/go-jose/go-jose/v4/jwt" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign/privacy" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/providers" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore/pkg/oauthflow" + "golang.org/x/term" +) + +const ( + flowNormal = "normal" + flowDevice = "device" + flowToken = "token" + flowClientCredentials = "client_credentials" +) + +var SigstoreOIDCIssuerAPIVersions = []uint32{1} + +type IDTokenConfig struct { + TokenOrPath string + DisableProviders bool + Provider string + AuthFlow string + SkipConfirm bool + OIDCServices []root.Service + ClientID string + ClientSecret string + RedirectURL string +} + +// RetrieveIDToken returns an ID token from one of the following sources: +// * Flag value +// * File, path provided by flag +// * Provider, e.g. a well-known location of a token for an environment like K8s or CI/CD +// * OpenID Connect authentication protocol +func RetrieveIDToken(ctx context.Context, c IDTokenConfig) (string, error) { + idToken, err := ReadIDToken(ctx, c.TokenOrPath, c.DisableProviders, c.Provider) + if err != nil { + return "", fmt.Errorf("reading ID token: %w", err) + } + if idToken != "" { + return idToken, nil + } + flow, err := GetOAuthFlow(ctx, c.AuthFlow, idToken, c.SkipConfirm) + if err != nil { + return "", fmt.Errorf("setting auth flow: %w", err) + } + oidcIssuerSvc, err := root.SelectService(c.OIDCServices, SigstoreOIDCIssuerAPIVersions, time.Now()) + if err != nil { + return "", fmt.Errorf("selecting OIDC issuer: %w", err) + } + _, idToken, err = AuthenticateCaller(flow, idToken, oidcIssuerSvc.URL, c.ClientID, c.ClientSecret, c.RedirectURL) + if err != nil { + return "", fmt.Errorf("authenticating caller: %w", err) + } + return idToken, err +} + +// ReadIDToken returns an OpenID Connect token from either a file or a well-known location from an identity provider +func ReadIDToken(ctx context.Context, tokOrPath string, disableProviders bool, oidcProvider string) (string, error) { + idToken, err := idToken(tokOrPath) + if err != nil { + return "", 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) && !disableProviders { + if oidcProvider != "" { + provider, err = providers.ProvideFrom(ctx, oidcProvider) + if err != nil { + return "", fmt.Errorf("getting provider: %w", err) + } + idToken, err = provider.Provide(ctx, "sigstore") + } else { + idToken, err = providers.Provide(ctx, "sigstore") + } + if err != nil { + return "", fmt.Errorf("fetching ambient OIDC credentials: %w", err) + } + } + return idToken, nil +} + +// GetOAuthFlow returns authentication flow that the client will initiate +func GetOAuthFlow(ctx context.Context, authFlow, idToken string, skipConfirm bool) (string, error) { + var flow string + switch { + case authFlow != "": + // Caller manually set flow option. + flow = authFlow + 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 !skipConfirm { + if err := ui.ConfirmContinue(ctx); err != nil { + statementErr = err + } + } + }) + if statementErr != nil { + return "", statementErr + } + flow = flowNormal + } + return flow, nil +} + +// AuthenticateCaller performs an OpenID Connect authentication to exchange credentials for an identity token +func AuthenticateCaller(flow, idToken, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string) (string, string, error) { + var tokenGetter oauthflow.TokenGetter + switch flow { + case flowClientCredentials: + tokenGetter = oauthflow.NewClientCredentialsFlow(oidcIssuer) + case flowDevice: + tokenGetter = oauthflow.NewDeviceFlowTokenGetterForIssuer(oidcIssuer) + case flowNormal: + tokenGetter = oauthflow.DefaultIDTokenGetter + case flowToken: + tokenGetter = &oauthflow.StaticTokenGetter{RawToken: idToken} + default: + return "", "", fmt.Errorf("unsupported oauth flow: %s", flow) + } + + tok, err := oauthflow.OIDConnect(oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL, tokenGetter) + if err != nil { + return "", "", err + } + return tok.Subject, tok.RawString, 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, []jose.SignatureAlgorithm{"RS256"}); 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 +} diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go new file mode 100644 index 00000000000..8b1d32fbb38 --- /dev/null +++ b/internal/auth/auth_test.go @@ -0,0 +1,208 @@ +// 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 auth + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + + "github.com/sigstore/cosign/v3/pkg/providers" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + // Generated from https://justtrustme.dev/token?sub=test-subject + dummyJWT = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImFhOWE1YjA5LTExMzktNGU2YS1hNjMxLTA2ZTU3NDU4NzI0MSJ9.eyJleHAiOjE3NTM4MzY1NTIsImlhdCI6MTc1MzgzNDc1MiwiaXNzIjoiaHR0cHM6Ly9qdXN0dHJ1c3RtZS5kZXYiLCJzdWIiOiJ0ZXN0LXN1YmplY3QifQ.WWNGLWQsSDcz0cFlGbMfmLkGaMpiAsVfik2vAj_YPIXNG6jgkMmIF69TbrwH-qlSfKNNI1GTktxlufsQwOUiseVdqV7fOCdvPhQsozHye8JT-AgZ9wcH3DGcdp-5R5KOKlFNXHFcBjI9lS0KIelWoJLj8YzisOi0hWRdAwpJwuselV-d7IlcLZhJiZO3n-d15YB4fRMpjTr_aj--hdec7ywzmCQqKL3XdAjAmR99JExMKs_w25-6K7akjVSE1lljf8Wf9CBfOlwvWKxXPvIwzE0DC2yWS103yWfGHEf3UbKPlF34Xqo6beHTnf9uiO0HdWTaQp2e0eShsQDX9hpIeg" +) + +func Test_idToken(t *testing.T) { + td := t.TempDir() + tokenFile := filepath.Join(td, "token.jwt") + err := os.WriteFile(tokenFile, []byte(dummyJWT), 0600) + require.NoError(t, err) + + nonExistentFile := filepath.Join(td, "nonexistent") + + tests := []struct { + name string + s string + want string + wantErr bool + }{ + { + name: "empty string", + s: "", + want: "", + }, + { + name: "valid jwt", + s: dummyJWT, + want: dummyJWT, + }, + { + name: "not a jwt or file", + s: "not-a-jwt", + wantErr: true, + }, + { + name: "file path", + s: tokenFile, + want: dummyJWT, + }, + { + name: "non-existent file", + s: nonExistentFile, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := idToken(tt.s) + if (err != nil) != tt.wantErr { + t.Errorf("idToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("idToken() = %v, want %v", got, tt.want) + } + }) + } +} + +type mockProvider struct { + token string + err error +} + +func (m *mockProvider) Enabled(_ context.Context) bool { + return true +} + +func (m *mockProvider) Provide(_ context.Context, _ string) (string, error) { + return m.token, m.err +} + +func TestReadIDToken(t *testing.T) { + ctx := context.Background() + td := t.TempDir() + tokenFile := filepath.Join(td, "token.jwt") + err := os.WriteFile(tokenFile, []byte(dummyJWT), 0600) + require.NoError(t, err) + + providers.Register("mock-success", &mockProvider{token: "mock-token"}) + providers.Register("mock-fail", &mockProvider{err: errors.New("mock error")}) + + tests := []struct { + name string + tokOrPath string + disableProviders bool + oidcProvider string + want string + wantErr bool + }{ + { + name: "raw token", + tokOrPath: dummyJWT, + want: dummyJWT, + }, + { + name: "token from file", + tokOrPath: tokenFile, + want: dummyJWT, + }, + { + name: "no token, providers disabled", + tokOrPath: "", + disableProviders: true, + want: "", + }, + { + name: "no token, specific provider success", + tokOrPath: "", + oidcProvider: "mock-success", + want: "mock-token", + }, + { + name: "no token, specific provider fail", + tokOrPath: "", + oidcProvider: "mock-fail", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ReadIDToken(ctx, tt.tokOrPath, tt.disableProviders, tt.oidcProvider) + if (err != nil) != tt.wantErr { + t.Errorf("ReadIDToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ReadIDToken() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetOAuthFlow(t *testing.T) { + tests := []struct { + name string + authFlow string + idToken string + want string + }{ + { + name: "auth flow set explicitly", + authFlow: "client_credentials", + want: "client_credentials", + }, + { + name: "id token set", + idToken: dummyJWT, + want: "token", + }, + // Other flows can't be easily tested due to lack of interactivity + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetOAuthFlow(context.Background(), tt.authFlow, tt.idToken, false) + + if err != nil { + t.Errorf("GetOAuthFlow() error = %v", err) + } + if got != tt.want { + t.Errorf("GetOAuthFlow() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAuthenticateCaller(t *testing.T) { + t.Run("token flow", func(t *testing.T) { + subject, token, err := AuthenticateCaller("token", dummyJWT, "", "", "", "") + require.NoError(t, err) + assert.Equal(t, "test-subject", subject) + assert.Equal(t, dummyJWT, token) + }) + + t.Run("unsupported flow", func(t *testing.T) { + _, _, err := AuthenticateCaller("bad-flow", "", "", "", "", "") + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported oauth flow") + }) +} diff --git a/internal/key/svkeypair.go b/internal/key/svkeypair.go new file mode 100644 index 00000000000..fb373ae4551 --- /dev/null +++ b/internal/key/svkeypair.go @@ -0,0 +1,151 @@ +// 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 key + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + + "github.com/sigstore/cosign/v3/pkg/cosign" + protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" + signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" +) + +// SignerVerifierKeypair is a wrapper around a SignerVerifier that implements +// sigstore-go's Keypair interface. +type SignerVerifierKeypair struct { + sv signature.SignerVerifier + hint []byte + keyAlg string + sigAlg signature.AlgorithmDetails +} + +// NewSignerVerifierKeypair creates a new SignerVerifierKeypair from a SignerVerifier. +func NewSignerVerifierKeypair(sv signature.SignerVerifier, defaultLoadOptions *[]signature.LoadOption) (*SignerVerifierKeypair, error) { + pubKey, err := sv.PublicKey() + if err != nil { + return nil, fmt.Errorf("getting public key: %w", err) + } + pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey) + if err != nil { + return nil, fmt.Errorf("marshalling public key: %w", err) + } + hashedBytes := sha256.Sum256(pubKeyBytes) + hint := []byte(base64.StdEncoding.EncodeToString(hashedBytes[:])) + + var keyAlg string + switch pubKey.(type) { + case *ecdsa.PublicKey: + keyAlg = "ECDSA" + case *rsa.PublicKey: + keyAlg = "RSA" + case ed25519.PublicKey: + keyAlg = "ED25519" + default: + return nil, errors.New("unsupported key type") + } + + algo, err := signature.GetDefaultAlgorithmDetails(pubKey, *cosign.GetDefaultLoadOptions(defaultLoadOptions)...) + if err != nil { + return nil, fmt.Errorf("getting default algorithm details: %w", err) + } + + return &SignerVerifierKeypair{ + sv: sv, + hint: hint, + keyAlg: keyAlg, + sigAlg: algo, + }, nil +} + +// GetHashAlgorithm returns the hash algorithm to generate the digest to be signed. +func (k *SignerVerifierKeypair) GetHashAlgorithm() protocommon.HashAlgorithm { + return k.sigAlg.GetProtoHashType() +} + +func (k *SignerVerifierKeypair) GetSigningAlgorithm() protocommon.PublicKeyDetails { + return k.sigAlg.GetSignatureAlgorithm() +} + +// GetHint returns a hint for the public key. +func (k *SignerVerifierKeypair) GetHint() []byte { + return k.hint +} + +// GetKeyAlgorithm returns the key algorithm, to be used in requests to Fulcio. +func (k *SignerVerifierKeypair) GetKeyAlgorithm() string { + return k.keyAlg +} + +// GetPublicKey returns the public key. +func (k *SignerVerifierKeypair) GetPublicKey() crypto.PublicKey { + pubKey, err := k.sv.PublicKey() + if err != nil { + // The interface does not allow returning an error + return nil + } + return pubKey +} + +// GetPublicKeyPem returns the public key in PEM format. +func (k *SignerVerifierKeypair) GetPublicKeyPem() (string, error) { + pubKey, err := k.sv.PublicKey() + if err != nil { + return "", err + } + pemBytes, err := cryptoutils.MarshalPublicKeyToPEM(pubKey) + if err != nil { + return "", err + } + return string(pemBytes), nil +} + +// SignData signs the given data with the SignerVerifier. +func (k *SignerVerifierKeypair) SignData(ctx context.Context, data []byte) ([]byte, []byte, error) { + var digest []byte + sOpts := []signature.SignOption{signatureoptions.WithContext(ctx)} + + hashType := k.sigAlg.GetHashType() + if hashType != 0 { + h := hashType.New() + h.Write(data) + digest = h.Sum(nil) + sOpts = append(sOpts, signatureoptions.WithDigest(digest)) + } + + sig, err := k.sv.SignMessage(bytes.NewReader(data), sOpts...) + if err != nil { + return nil, nil, err + } + return sig, digest, nil +} + +// Close closes the underlying SignerVerifier if it has a Close() method. +func (k *SignerVerifierKeypair) Close() { + if closer, ok := k.sv.(interface{ Close() }); ok { + closer.Close() + } +} diff --git a/internal/key/svkeypair_test.go b/internal/key/svkeypair_test.go new file mode 100644 index 00000000000..aba195c22ed --- /dev/null +++ b/internal/key/svkeypair_test.go @@ -0,0 +1,320 @@ +// 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 key + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "errors" + "io" + "strings" + "testing" + + protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" +) + +// mockSignerVerifier is a mock implementation of signature.SignerVerifier for testing. +type mockSignerVerifier struct { + pubKey crypto.PublicKey + pubKeyErr error + signErr error +} + +func (m *mockSignerVerifier) PublicKey(_ ...signature.PublicKeyOption) (crypto.PublicKey, error) { + if m.pubKeyErr != nil { + return nil, m.pubKeyErr + } + return m.pubKey, nil +} + +func (m *mockSignerVerifier) SignMessage(_ io.Reader, _ ...signature.SignOption) ([]byte, error) { + if m.signErr != nil { + return nil, m.signErr + } + return []byte("mock-signature"), nil +} + +func (m *mockSignerVerifier) VerifySignature(_, _ io.Reader, _ ...signature.VerifyOption) error { + return errors.New("not implemented") +} + +func TestNewKMSKeypair(t *testing.T) { + ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate ecdsa key: %v", err) + } + rsaPriv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("failed to generate rsa key: %v", err) + } + _, ed25519Priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("failed to generate ed25519 key: %v", err) + } + + testCases := []struct { + name string + sv signature.SignerVerifier + expectErr bool + errMsg string + }{ + { + name: "ECDSA key", + sv: &mockSignerVerifier{ + pubKey: &ecdsaPriv.PublicKey, + }, + expectErr: false, + }, + { + name: "RSA key", + sv: &mockSignerVerifier{ + pubKey: &rsaPriv.PublicKey, + }, + expectErr: false, + }, + { + name: "ED25519 key", + sv: &mockSignerVerifier{ + pubKey: ed25519Priv.Public(), + }, + expectErr: false, + }, + { + name: "Unsupported key type", + sv: &mockSignerVerifier{ + pubKey: "not a key", + }, + expectErr: true, + errMsg: "unsupported public key type", + }, + { + name: "PublicKey returns error", + sv: &mockSignerVerifier{ + pubKeyErr: errors.New("pubkey error"), + }, + expectErr: true, + errMsg: "getting public key: pubkey error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + kp, err := NewSignerVerifierKeypair(tc.sv, nil) + if tc.expectErr { + if err == nil { + t.Errorf("expected an error, but got none") + } else if !strings.Contains(err.Error(), tc.errMsg) { + t.Errorf("expected error message '%s', got '%s'", tc.errMsg, err.Error()) + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if kp == nil { + t.Error("expected a keypair, but got nil") + } + } + }) + } +} + +func TestKMSKeypair_ECDSA_Methods(t *testing.T) { + ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate ecdsa key: %v", err) + } + sv := &mockSignerVerifier{pubKey: &ecdsaPriv.PublicKey} + kp, err := NewSignerVerifierKeypair(sv, nil) + if err != nil { + t.Fatalf("failed to create KMSKeypair: %v", err) + } + + t.Run("GetHashAlgorithm", func(t *testing.T) { + if kp.GetHashAlgorithm() != protocommon.HashAlgorithm_SHA2_256 { + t.Errorf("expected SHA2_256, got %v", kp.GetHashAlgorithm()) + } + }) + + t.Run("GetSigningAlgorithm", func(t *testing.T) { + if kp.GetSigningAlgorithm() != protocommon.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256 { + t.Errorf("expected ECDSA_P256_SHA256, got %v", kp.GetSigningAlgorithm()) + } + }) + + t.Run("GetHint", func(t *testing.T) { + pubKeyBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPriv.PublicKey) + if err != nil { + t.Fatalf("marshalling public key: %v", err) + } + hashedBytes := sha256.Sum256(pubKeyBytes) + expectedHint := base64.StdEncoding.EncodeToString(hashedBytes[:]) + + if string(kp.GetHint()) != expectedHint { + t.Errorf("expected hint %s, got %s", expectedHint, string(kp.GetHint())) + } + }) + + t.Run("GetKeyAlgorithm", func(t *testing.T) { + if kp.GetKeyAlgorithm() != "ECDSA" { + t.Errorf("expected ECDSA, got %s", kp.GetKeyAlgorithm()) + } + }) + + t.Run("GetPublicKey", func(t *testing.T) { + pub := kp.GetPublicKey() + if !pub.(*ecdsa.PublicKey).Equal(&ecdsaPriv.PublicKey) { + t.Error("public keys do not match") + } + }) + + t.Run("GetPublicKeyPem", func(t *testing.T) { + pem, err := kp.GetPublicKeyPem() + if err != nil { + t.Fatalf("GetPublicKeyPem returned an error: %v", err) + } + pub, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(pem)) + if err != nil { + t.Fatalf("failed to unmarshal pem: %v", err) + } + if !pub.(*ecdsa.PublicKey).Equal(&ecdsaPriv.PublicKey) { + t.Error("public keys do not match") + } + }) + + t.Run("SignData", func(t *testing.T) { + data := []byte("some data to sign") + sig, digest, err := kp.SignData(context.Background(), data) + if err != nil { + t.Fatalf("SignData returned an error: %v", err) + } + if string(sig) != "mock-signature" { + t.Errorf("expected signature 'mock-signature', got '%s'", string(sig)) + } + + h := sha256.New() + h.Write(data) + expectedDigest := h.Sum(nil) + if !bytes.Equal(digest, expectedDigest) { + t.Errorf("expected digest %x, got %x", expectedDigest, digest) + } + }) + + t.Run("SignData with error", func(t *testing.T) { + errSV := &mockSignerVerifier{ + pubKey: &ecdsaPriv.PublicKey, + signErr: errors.New("signing failed"), + } + errKP, err := NewSignerVerifierKeypair(errSV, nil) + if err != nil { + t.Fatalf("failed to create KMSKeypair: %v", err) + } + + _, _, err = errKP.SignData(context.Background(), []byte("data")) + if err == nil { + t.Error("expected an error, but got none") + } else if err.Error() != "signing failed" { + t.Errorf("expected error 'signing failed', got '%s'", err.Error()) + } + }) +} + +func TestKMSKeypair_ED25519_Methods(t *testing.T) { + _, ed25519Priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("failed to generate ed25519 key: %v", err) + } + sv := &mockSignerVerifier{pubKey: ed25519Priv.Public()} + kp, err := NewSignerVerifierKeypair(sv, &[]signature.LoadOption{}) + if err != nil { + t.Fatalf("failed to create KMSKeypair: %v", err) + } + + t.Run("GetHashAlgorithm", func(t *testing.T) { + if kp.GetHashAlgorithm() != protocommon.HashAlgorithm_HASH_ALGORITHM_UNSPECIFIED { + t.Errorf("expected HASH_ALGORITHM_UNSPECIFIED, got %v", kp.GetHashAlgorithm()) + } + }) + + t.Run("GetSigningAlgorithm", func(t *testing.T) { + if kp.GetSigningAlgorithm() != protocommon.PublicKeyDetails_PKIX_ED25519 { + t.Errorf("expected PKIX_ED25519, got %v", kp.GetSigningAlgorithm()) + } + }) + + t.Run("GetHint", func(t *testing.T) { + pubKeyBytes, err := x509.MarshalPKIXPublicKey(ed25519Priv.Public()) + if err != nil { + t.Fatalf("marshalling public key: %v", err) + } + hashedBytes := sha256.Sum256(pubKeyBytes) + expectedHint := base64.StdEncoding.EncodeToString(hashedBytes[:]) + + if string(kp.GetHint()) != expectedHint { + t.Errorf("expected hint %s, got %s", expectedHint, string(kp.GetHint())) + } + }) + + t.Run("GetKeyAlgorithm", func(t *testing.T) { + if kp.GetKeyAlgorithm() != "ED25519" { + t.Errorf("expected ED25519, got %s", kp.GetKeyAlgorithm()) + } + }) + + t.Run("GetPublicKey", func(t *testing.T) { + pub := kp.GetPublicKey() + if !pub.(ed25519.PublicKey).Equal(ed25519Priv.Public()) { + t.Error("public keys do not match") + } + }) + + t.Run("GetPublicKeyPem", func(t *testing.T) { + pem, err := kp.GetPublicKeyPem() + if err != nil { + t.Fatalf("GetPublicKeyPem returned an error: %v", err) + } + pub, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(pem)) + if err != nil { + t.Fatalf("failed to unmarshal pem: %v", err) + } + if !pub.(ed25519.PublicKey).Equal(ed25519Priv.Public()) { + t.Error("public keys do not match") + } + }) + + t.Run("SignData", func(t *testing.T) { + data := []byte("some data to sign") + sig, digest, err := kp.SignData(context.Background(), data) + if err != nil { + t.Fatalf("SignData returned an error: %v", err) + } + if string(sig) != "mock-signature" { + t.Errorf("expected signature 'mock-signature', got '%s'", string(sig)) + } + if len(digest) != 0 { + t.Errorf("expected empty digest, got %x", digest) + } + }) +} diff --git a/internal/pkg/cosign/common.go b/internal/pkg/cosign/common.go index a1aa8ebc35f..a0b021c14b0 100644 --- a/internal/pkg/cosign/common.go +++ b/internal/pkg/cosign/common.go @@ -15,6 +15,7 @@ package cosign import ( + "crypto" "errors" "hash" "io" @@ -39,14 +40,24 @@ func FileExists(filename string) (bool, error) { // HashReader hashes while it reads. type HashReader struct { - r io.Reader - h hash.Hash + r io.Reader + h hash.Hash + ch crypto.Hash } -func NewHashReader(r io.Reader, h hash.Hash) HashReader { +func NewHashReader(r io.Reader, ch crypto.Hash) HashReader { + if ch == 0 { + return HashReader{ + r: r, + h: nil, + ch: ch, + } + } + h := ch.New() return HashReader{ - r: io.TeeReader(r, h), - h: h, + r: io.TeeReader(r, h), + h: h, + ch: ch, } } @@ -54,16 +65,39 @@ func NewHashReader(r io.Reader, h hash.Hash) HashReader { 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) } +func (h *HashReader) Sum(p []byte) []byte { + if h.h == nil { + return nil + } + return h.h.Sum(p) +} // Reset implements hash.Hash. -func (h *HashReader) Reset() { h.h.Reset() } +func (h *HashReader) Reset() { + if h.h == nil { + return + } + h.h.Reset() +} // Size implements hash.Hash. -func (h *HashReader) Size() int { return h.h.Size() } +func (h *HashReader) Size() int { + if h.h == nil { + return 0 + } + return h.h.Size() +} // BlockSize implements hash.Hash. -func (h *HashReader) BlockSize() int { return h.h.BlockSize() } +func (h *HashReader) BlockSize() int { + if h.h == nil { + return 0 + } + return h.h.BlockSize() +} // Write implements hash.Hash func (h *HashReader) Write(p []byte) (int, error) { return 0, errors.New("not implemented") } //nolint: revive + +// HashFunc implements cosign.NamedHash +func (h *HashReader) HashFunc() crypto.Hash { return h.ch } diff --git a/internal/pkg/cosign/common_test.go b/internal/pkg/cosign/common_test.go index 4a54109e435..fb0460c18f9 100644 --- a/internal/pkg/cosign/common_test.go +++ b/internal/pkg/cosign/common_test.go @@ -17,6 +17,7 @@ package cosign import ( "bytes" + "crypto" "crypto/sha256" "io" "os" @@ -55,19 +56,39 @@ func Test_FileExists(t *testing.T) { 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) - } + t.Run("SHA256", func(t *testing.T) { + r := NewHashReader(bytes.NewReader(input), crypto.SHA256) - if !bytes.Equal(got, input) { - t.Errorf("io.ReadAll returned %s, want %s", got, input) - } + got, err := io.ReadAll(&r) + if err != nil { + t.Fatal(err) + } - gotHash := r.Sum(nil) - if hash := sha256.Sum256(input); !bytes.Equal(gotHash, hash[:]) { - t.Errorf("Sum returned %s, want %s", gotHash, hash) - } + 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) + } + }) + + t.Run("unspecified hash algorithm", func(t *testing.T) { + r := NewHashReader(bytes.NewReader(input), crypto.Hash(0)) + + 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) + } + + if gotHash := r.Sum(nil); gotHash != nil { + t.Errorf("Sum returned %s, want nil", gotHash) + } + }) } diff --git a/internal/pkg/cosign/ephemeral/signer.go b/internal/pkg/cosign/ephemeral/signer.go deleted file mode 100644 index 801dcabcd02..00000000000 --- a/internal/pkg/cosign/ephemeral/signer.go +++ /dev/null @@ -1,77 +0,0 @@ -// 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 -} diff --git a/internal/pkg/cosign/ephemeral/signer_test.go b/internal/pkg/cosign/ephemeral/signer_test.go deleted file mode 100644 index b51721731cb..00000000000 --- a/internal/pkg/cosign/ephemeral/signer_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// 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) - } -} diff --git a/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go b/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go index 3b44da88420..c68af1994b3 100644 --- a/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go +++ b/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go @@ -22,7 +22,7 @@ import ( "os" "sync" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/fulcioroots" ) diff --git a/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots_test.go b/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots_test.go index 7d7ad46254e..e6309977d5f 100644 --- a/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots_test.go +++ b/internal/pkg/cosign/fulcio/fulcioroots/fulcioroots_test.go @@ -19,7 +19,7 @@ import ( "sync" "testing" - "github.com/sigstore/cosign/v2/test" + "github.com/sigstore/cosign/v3/internal/test" "github.com/sigstore/sigstore/pkg/cryptoutils" ) @@ -34,7 +34,7 @@ func TestGetFulcioRoots(t *testing.T) { subCert, _, _ := test.GenerateSubordinateCa(rootCert, rootPriv) subPemCert, _ := cryptoutils.MarshalCertificateToPEM(subCert) - var chain []byte + chain := make([]byte, 0, len(subPemCert)+len(rootPemCert)) chain = append(chain, subPemCert...) chain = append(chain, rootPemCert...) diff --git a/internal/pkg/cosign/fulcio/signer.go b/internal/pkg/cosign/fulcio/signer.go deleted file mode 100644 index fb805425f04..00000000000 --- a/internal/pkg/cosign/fulcio/signer.go +++ /dev/null @@ -1,60 +0,0 @@ -// 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, - } -} diff --git a/internal/pkg/cosign/fulcio/signer_test.go b/internal/pkg/cosign/fulcio/signer_test.go deleted file mode 100644 index 5bac26fb87b..00000000000 --- a/internal/pkg/cosign/fulcio/signer_test.go +++ /dev/null @@ -1,128 +0,0 @@ -// 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) - } -} diff --git a/internal/pkg/cosign/payload/attestor.go b/internal/pkg/cosign/payload/attestor.go deleted file mode 100644 index d7bbb16c0a6..00000000000 --- a/internal/pkg/cosign/payload/attestor.go +++ /dev/null @@ -1,89 +0,0 @@ -// 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, - } -} diff --git a/internal/pkg/cosign/payload/attestor_test.go b/internal/pkg/cosign/payload/attestor_test.go deleted file mode 100644 index 23cf90d2f9a..00000000000 --- a/internal/pkg/cosign/payload/attestor_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// 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) - } -} diff --git a/internal/pkg/cosign/payload/signer.go b/internal/pkg/cosign/payload/signer.go deleted file mode 100644 index 6cefbe3a656..00000000000 --- a/internal/pkg/cosign/payload/signer.go +++ /dev/null @@ -1,114 +0,0 @@ -// 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 -} diff --git a/internal/pkg/cosign/payload/signer_test.go b/internal/pkg/cosign/payload/signer_test.go deleted file mode 100644 index d2ccf7dcb24..00000000000 --- a/internal/pkg/cosign/payload/signer_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// 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) - } -} diff --git a/internal/pkg/cosign/payload/size/size.go b/internal/pkg/cosign/payload/size/size.go index f867477c732..aac4dca2c5b 100644 --- a/internal/pkg/cosign/payload/size/size.go +++ b/internal/pkg/cosign/payload/size/size.go @@ -16,7 +16,7 @@ package payload import ( "github.com/dustin/go-humanize" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) const defaultMaxSize = uint64(134217728) // 128MiB diff --git a/internal/pkg/cosign/rekor/signer.go b/internal/pkg/cosign/rekor/signer.go deleted file mode 100644 index 2fa5e2595ef..00000000000 --- a/internal/pkg/cosign/rekor/signer.go +++ /dev/null @@ -1,118 +0,0 @@ -// 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, - } -} diff --git a/internal/pkg/cosign/rekor/signer_test.go b/internal/pkg/cosign/rekor/signer_test.go deleted file mode 100644 index 5f3dfa02351..00000000000 --- a/internal/pkg/cosign/rekor/signer_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// 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) - } -} diff --git a/internal/pkg/cosign/sign.go b/internal/pkg/cosign/sign.go index b2f746daee4..93873c101be 100644 --- a/internal/pkg/cosign/sign.go +++ b/internal/pkg/cosign/sign.go @@ -19,7 +19,7 @@ import ( "crypto" "io" - "github.com/sigstore/cosign/v2/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci" ) // Signer signs payloads in the form of `oci.Signature`s diff --git a/internal/pkg/cosign/tsa/client/client.go b/internal/pkg/cosign/tsa/client/client.go index b07eff365d4..43ff05aaa14 100644 --- a/internal/pkg/cosign/tsa/client/client.go +++ b/internal/pkg/cosign/tsa/client/client.go @@ -54,7 +54,7 @@ type TimestampAuthorityClientImpl struct { const defaultTimeout = 10 * time.Second -func getHTTPTransport(cacertFilename, certFilename, keyFilename, serverName string, timeout time.Duration) (http.RoundTripper, error) { +func GetHTTPTransport(cacertFilename, certFilename, keyFilename, serverName string, timeout time.Duration) (http.RoundTripper, error) { if timeout == 0 { timeout = defaultTimeout } @@ -123,7 +123,7 @@ func (t *TimestampAuthorityClientImpl) GetTimestampResponse(tsq []byte) ([]byte, // 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) + tr, err := GetHTTPTransport(t.CACert, t.Cert, t.Key, t.ServerName, t.Timeout) if err != nil { return nil, err } @@ -164,14 +164,3 @@ func (t *TimestampAuthorityClientImpl) GetTimestampResponse(tsq []byte) ([]byte, 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, - } -} diff --git a/internal/pkg/cosign/tsa/mock/mock_tsa_client.go b/internal/pkg/cosign/tsa/mock/mock_tsa_client.go index f85b5f0a8b6..44412769206 100644 --- a/internal/pkg/cosign/tsa/mock/mock_tsa_client.go +++ b/internal/pkg/cosign/tsa/mock/mock_tsa_client.go @@ -24,10 +24,10 @@ import ( "time" "github.com/digitorus/timestamp" - "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/client" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" - "github.com/sigstore/timestamp-authority/pkg/signer" + "github.com/sigstore/timestamp-authority/v2/pkg/signer" ) // TSAClient creates RFC3161 timestamps and implements client.TimestampAuthority. diff --git a/internal/pkg/cosign/tsa/signer.go b/internal/pkg/cosign/tsa/signer.go deleted file mode 100644 index 9fb0b66b407..00000000000 --- a/internal/pkg/cosign/tsa/signer.go +++ /dev/null @@ -1,114 +0,0 @@ -// 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, - } -} diff --git a/internal/pkg/cosign/tsa/signer_test.go b/internal/pkg/cosign/tsa/signer_test.go deleted file mode 100644 index 4cb136b5de6..00000000000 --- a/internal/pkg/cosign/tsa/signer_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// 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) - } -} diff --git a/internal/pkg/cosign/tsa/utils.go b/internal/pkg/cosign/tsa/utils.go deleted file mode 100644 index 354675e097a..00000000000 --- a/internal/pkg/cosign/tsa/utils.go +++ /dev/null @@ -1,46 +0,0 @@ -// 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 -} diff --git a/internal/pkg/cosign/tsa/utils_test.go b/internal/pkg/cosign/tsa/utils_test.go deleted file mode 100644 index ba294ffe8f0..00000000000 --- a/internal/pkg/cosign/tsa/utils_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// 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") - } -} diff --git a/test/cert_utils.go b/internal/test/cert_utils.go similarity index 100% rename from test/cert_utils.go rename to internal/test/cert_utils.go diff --git a/internal/ui/log_test.go b/internal/ui/log_test.go index b1172367f02..01b96aad044 100644 --- a/internal/ui/log_test.go +++ b/internal/ui/log_test.go @@ -17,7 +17,7 @@ import ( "context" "testing" - "github.com/sigstore/cosign/v2/internal/ui" + "github.com/sigstore/cosign/v3/internal/ui" "github.com/stretchr/testify/assert" ) diff --git a/internal/ui/prompt_test.go b/internal/ui/prompt_test.go index 26c97ca49d1..3910d9b8bdf 100644 --- a/internal/ui/prompt_test.go +++ b/internal/ui/prompt_test.go @@ -19,7 +19,7 @@ import ( "errors" "testing" - "github.com/sigstore/cosign/v2/internal/ui" + "github.com/sigstore/cosign/v3/internal/ui" "github.com/stretchr/testify/assert" ) diff --git a/internal/ui/spinner.go b/internal/ui/spinner.go new file mode 100644 index 00000000000..f0598bfbf08 --- /dev/null +++ b/internal/ui/spinner.go @@ -0,0 +1,66 @@ +// 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 ui + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "github.com/moby/term" +) + +// Spinner shows progress for long-running operations in the terminal +type Spinner struct { + done chan struct{} +} + +// NewSpinner starts a spinner in a goroutine and returns it. +func NewSpinner(ctx context.Context, message string) *Spinner { + s := &Spinner{ + done: make(chan struct{}), + } + + go func() { + // Don't show spinner if not in a terminal + fd := os.Stderr.Fd() + if !term.IsTerminal(fd) { + Infof(ctx, "%s", message) + return + } + + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + spinnerChars := []rune{'|', '/', '-', '\\'} + i := 0 + for { + select { + case <-ticker.C: + i++ + fmt.Fprintf(os.Stderr, "\r%s %c ", message, spinnerChars[i%len(spinnerChars)]) + case <-s.done: + fmt.Fprintf(os.Stderr, "\r%s\r", strings.Repeat(" ", len(message)+3)) + return + } + } + }() + return s +} + +func (s *Spinner) Stop() { + close(s.done) +} diff --git a/pkg/blob/load.go b/pkg/blob/load.go index 8ee624e93a8..29b388815f5 100644 --- a/pkg/blob/load.go +++ b/pkg/blob/load.go @@ -50,6 +50,9 @@ func LoadFileOrURL(fileRef string) ([]byte, error) { return nil, err } defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("loading URL %s: server returned HTTP %d", fileRef, resp.StatusCode) + } raw, err = io.ReadAll(resp.Body) if err != nil { return nil, err diff --git a/pkg/blob/load_test.go b/pkg/blob/load_test.go index 58b6948b883..100728da5dd 100644 --- a/pkg/blob/load_test.go +++ b/pkg/blob/load_test.go @@ -99,6 +99,34 @@ func TestLoadURL(t *testing.T) { } } +func TestLoadURLNon2xxStatus(t *testing.T) { + tests := []struct { + name string + statusCode int + }{ + {"NotFound", http.StatusNotFound}, + {"InternalServerError", http.StatusInternalServerError}, + {"Forbidden", http.StatusForbidden}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.WriteHeader(tc.statusCode) + rw.Write([]byte("error page")) + })) + defer server.Close() + + _, err := LoadFileOrURL(server.URL) + if err == nil { + t.Fatalf("LoadFileOrURL() with HTTP %d: expected error, got nil", tc.statusCode) + } + if !strings.Contains(err.Error(), "HTTP") { + t.Errorf("error should mention HTTP status, got: %v", err) + } + }) + } +} + func TestLoadURLWithChecksum(t *testing.T) { data := []byte("test") diff --git a/pkg/cosign/attestation/attestation.go b/pkg/cosign/attestation/attestation.go index e675088d58e..4f3b6b60649 100644 --- a/pkg/cosign/attestation/attestation.go +++ b/pkg/cosign/attestation/attestation.go @@ -23,9 +23,15 @@ import ( "strings" "time" + slsa02_attest "github.com/in-toto/attestation/go/predicates/provenance/v02" + slsa1_attest "github.com/in-toto/attestation/go/predicates/provenance/v1" + in_toto_attest "github.com/in-toto/attestation/go/v1" "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" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" ) const ( @@ -42,6 +48,55 @@ const ( OpenVexNamespace = "https://openvex.dev/ns" ) +type Statement struct { + *in_toto_attest.Statement + // Catch legacy string predicates to preserve exact byte marshaling + LegacyPredicate *string `json:"-"` +} + +func (s *Statement) MarshalJSON() ([]byte, error) { + if s.LegacyPredicate == nil { + return protojson.Marshal(s.Statement) + } + legacyStatement := map[string]any{ + "_type": s.Type, + "subject": s.Subject, + "predicateType": s.PredicateType, + "predicate": s.LegacyPredicate, + } + return json.Marshal(legacyStatement) +} + +func (s *Statement) UnmarshalJSON(stBytes []byte) error { + st := in_toto_attest.Statement{} + if err := protojson.Unmarshal(stBytes, &st); err == nil { + s.Statement = &st + return nil + } + + // The predicate might be a string instead of a JSON object, which the in-toto library can't parse. + // Convert it to a JSON object and try again. + stmtMap := make(map[string]any) + if err := json.Unmarshal(stBytes, &stmtMap); err != nil { + return err + } + predicate, ok := stmtMap["predicate"] + if !ok { + return fmt.Errorf("could not parse statement, could not find predicate") + } + p, ok := predicate.(string) + if !ok { + return fmt.Errorf("could not parse predicate with type %T", predicate) + } + s.LegacyPredicate = &p + delete(stmtMap, "predicate") + remarshaled, err := json.Marshal(stmtMap) + if err != nil { + return fmt.Errorf("failed to marshal statement: %w", err) + } + return json.Unmarshal(remarshaled, &s.Statement) +} + // CosignPredicate specifies the format of the Custom Predicate. type CosignPredicate struct { Data interface{} @@ -60,8 +115,10 @@ type CosignVulnPredicate struct { // as a InToto Statement // https://github.com/in-toto/attestation/issues/58 type CosignVulnStatement struct { - in_toto.StatementHeader - Predicate CosignVulnPredicate `json:"predicate"` + Type string `json:"type,omitempty"` + Subject []*in_toto_attest.ResourceDescriptor `json:"subject,omitempty"` + PredicateType string `json:"predicateType,omitempty"` + Predicate CosignVulnPredicate `json:"predicate"` } type Invocation struct { @@ -106,7 +163,7 @@ type GenerateOpts struct { // 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) { +func GenerateStatement(opts GenerateOpts) (*Statement, error) { predicate, err := io.ReadAll(opts.Predicate) if err != nil { return nil, err @@ -138,7 +195,7 @@ func GenerateStatement(opts GenerateOpts) (interface{}, error) { } } -func generateVulnStatement(predicate []byte, digest string, repo string) (interface{}, error) { +func generateVulnStatement(predicate []byte, digest string, repo string) (*Statement, error) { var vuln CosignVulnPredicate err := json.Unmarshal(predicate, &vuln) @@ -146,10 +203,25 @@ func generateVulnStatement(predicate []byte, digest string, repo string) (interf return nil, err } - return in_toto.Statement{ - StatementHeader: generateStatementHeader(digest, repo, CosignVulnProvenanceV01), - Predicate: vuln, - }, nil + vulnObj, err := structToStruct(vuln) + if err != nil { + return nil, err + } + + return &Statement{ + Statement: &in_toto_attest.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: CosignVulnProvenanceV01, + Subject: []*in_toto_attest.ResourceDescriptor{ + { + Name: repo, + Digest: map[string]string{ + "sha256": digest, + }, + }, + }, + Predicate: vulnObj, + }}, nil } func timestamp(opts GenerateOpts) string { @@ -167,42 +239,42 @@ func customType(opts GenerateOpts) string { 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) (*Statement, error) { + payload, err := generateCustomPredicate(rawPayload, customType, timestamp) + if err != nil { + return nil, err } -} -func generateCustomStatement(rawPayload []byte, customType, digest, repo, timestamp string) (interface{}, error) { - payload, err := generateCustomPredicate(rawPayload, customType, timestamp) + predicate, err := structpb.NewStruct(payload) if err != nil { return nil, err } - return in_toto.Statement{ - StatementHeader: generateStatementHeader(digest, repo, customType), - Predicate: payload, - }, nil + return &Statement{ + Statement: &in_toto_attest.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: customType, + Subject: []*in_toto_attest.ResourceDescriptor{ + { + Name: repo, + Digest: map[string]string{ + "sha256": digest, + }, + }, + }, + Predicate: predicate, + }}, nil } -func generateCustomPredicate(rawPayload []byte, customType, timestamp string) (interface{}, error) { +func generateCustomPredicate(rawPayload []byte, customType, timestamp string) (map[string]any, error) { if customType == CosignCustomProvenanceV01 { - return &CosignPredicate{ - Data: string(rawPayload), - Timestamp: timestamp, + return map[string]any{ + "Data": string(rawPayload), + "Timestamp": timestamp, }, nil } - var result map[string]interface{} + var result map[string]any if err := json.Unmarshal(rawPayload, &result); err != nil { return nil, fmt.Errorf("invalid JSON payload for predicate type %s: %w", customType, err) } @@ -210,39 +282,89 @@ func generateCustomPredicate(rawPayload []byte, customType, timestamp string) (i return result, nil } -func generateSLSAProvenanceStatementSLSA02(rawPayload []byte, digest string, repo string) (interface{}, error) { - var predicate slsa02.ProvenancePredicate - err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(predicate)) +func generateSLSAProvenanceStatementSLSA02(rawPayload []byte, digest string, repo string) (*Statement, error) { + var predicate slsa02_attest.Provenance + err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(predicate.ProtoReflect().Interface())) if err != nil { return nil, fmt.Errorf("provenance predicate: %w", err) } - err = json.Unmarshal(rawPayload, &predicate) + protoOpts := protojson.UnmarshalOptions{DiscardUnknown: true} + err = protoOpts.Unmarshal(rawPayload, &predicate) if err != nil { - return "", fmt.Errorf("unmarshal Provenance predicate: %w", err) + return nil, fmt.Errorf("unmarshal Provenance predicate: %w", err) } - return in_toto.ProvenanceStatementSLSA02{ - StatementHeader: generateStatementHeader(digest, repo, slsa02.PredicateSLSAProvenance), - Predicate: predicate, - }, nil + predicateObj, err := protoStructToStruct(&predicate) + if err != nil { + return nil, err + } + return &Statement{ + Statement: &in_toto_attest.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa02.PredicateSLSAProvenance, + Subject: []*in_toto_attest.ResourceDescriptor{ + { + Name: repo, + Digest: map[string]string{ + "sha256": digest, + }, + }, + }, + Predicate: predicateObj, + }}, nil } -func generateSLSAProvenanceStatementSLSA1(rawPayload []byte, digest string, repo string) (interface{}, error) { - var predicate slsa1.ProvenancePredicate - err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(predicate)) +func generateSLSAProvenanceStatementSLSA1(rawPayload []byte, digest string, repo string) (*Statement, error) { + var genericPredicate map[string]any + if err := json.Unmarshal(rawPayload, &genericPredicate); err != nil { + return nil, fmt.Errorf("unmarshal raw payload to map: %w", err) + } + + // Safely rename the key if it exists + if runDetails, ok := genericPredicate["runDetails"].(map[string]any); ok { + if metadata, ok := runDetails["metadata"].(map[string]any); ok { + if val, exists := metadata["invocationID"]; exists { + metadata["invocationId"] = val + delete(metadata, "invocationID") + } + } + } + + modifiedPayload, err := json.Marshal(genericPredicate) + if err != nil { + return nil, fmt.Errorf("marshal modified payload: %w", err) + } + + var predicate slsa1_attest.Provenance + err = checkRequiredJSONFields(modifiedPayload, reflect.TypeOf(predicate.ProtoReflect().Interface())) if err != nil { return nil, fmt.Errorf("provenance predicate: %w", err) } - err = json.Unmarshal(rawPayload, &predicate) + protoOpts := protojson.UnmarshalOptions{DiscardUnknown: true} + err = protoOpts.Unmarshal(modifiedPayload, &predicate) if err != nil { - return "", fmt.Errorf("unmarshal Provenance predicate: %w", err) + return nil, fmt.Errorf("unmarshal Provenance predicate: %w", err) } - return in_toto.ProvenanceStatementSLSA1{ - StatementHeader: generateStatementHeader(digest, repo, slsa1.PredicateSLSAProvenance), - Predicate: predicate, - }, nil + predicateObj, err := protoStructToStruct(&predicate) + if err != nil { + return nil, err + } + return &Statement{ + Statement: &in_toto_attest.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: slsa1.PredicateSLSAProvenance, + Subject: []*in_toto_attest.ResourceDescriptor{ + { + Name: repo, + Digest: map[string]string{ + "sha256": digest, + }, + }, + }, + Predicate: predicateObj, + }}, nil } -func generateLinkStatement(rawPayload []byte, digest string, repo string) (interface{}, error) { +func generateLinkStatement(rawPayload []byte, digest string, repo string) (*Statement, error) { var link in_toto.Link err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(link)) if err != nil { @@ -250,52 +372,114 @@ func generateLinkStatement(rawPayload []byte, digest string, repo string) (inter } err = json.Unmarshal(rawPayload, &link) if err != nil { - return "", fmt.Errorf("unmarshal Link statement: %w", err) + return nil, fmt.Errorf("unmarshal Link statement: %w", err) } - return in_toto.LinkStatement{ - StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateLinkV1), - Predicate: link, - }, nil + linkObj, err := structToStruct(link) + if err != nil { + return nil, err + } + return &Statement{ + Statement: &in_toto_attest.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: in_toto.PredicateLinkV1, + Subject: []*in_toto_attest.ResourceDescriptor{ + { + Name: repo, + Digest: map[string]string{ + "sha256": digest, + }, + }, + }, + Predicate: linkObj, + }}, nil } -func generateOpenVexStatement(rawPayload []byte, digest string, repo string) (interface{}, error) { - var data interface{} +func generateOpenVexStatement(rawPayload []byte, digest string, repo string) (*Statement, error) { + var data map[string]any if err := json.Unmarshal(rawPayload, &data); err != nil { return nil, err } - return in_toto.Statement{ - StatementHeader: generateStatementHeader(digest, repo, OpenVexNamespace), - Predicate: data, - }, nil + dataObj, err := structpb.NewStruct(data) + if err != nil { + return nil, err + } + return &Statement{ + Statement: &in_toto_attest.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: OpenVexNamespace, + Subject: []*in_toto_attest.ResourceDescriptor{ + { + Name: repo, + Digest: map[string]string{ + "sha256": digest, + }, + }, + }, + Predicate: dataObj, + }}, nil } -func generateSPDXStatement(rawPayload []byte, digest string, repo string, parseJSON bool) (interface{}, error) { - var data interface{} +func generateSPDXStatement(rawPayload []byte, digest string, repo string, parseJSON bool) (*Statement, error) { + stmt := &in_toto_attest.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: in_toto.PredicateSPDX, + Subject: []*in_toto_attest.ResourceDescriptor{ + { + Name: repo, + Digest: map[string]string{ + "sha256": digest, + }, + }, + }, + } if parseJSON { + var data map[string]any if err := json.Unmarshal(rawPayload, &data); err != nil { return nil, err } - } else { - data = string(rawPayload) + dataObj, err := structpb.NewStruct(data) + if err != nil { + return nil, err + } + stmt.Predicate = dataObj + return &Statement{Statement: stmt}, nil } - return in_toto.SPDXStatement{ - StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateSPDX), - Predicate: data, + legacyPredicate := string(rawPayload) + return &Statement{ + Statement: stmt, + LegacyPredicate: &legacyPredicate, }, nil } -func generateCycloneDXStatement(rawPayload []byte, digest string, repo string) (interface{}, error) { - var data interface{} +func generateCycloneDXStatement(rawPayload []byte, digest string, repo string) (*Statement, error) { + var data map[string]any 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 + dataObj, err := structpb.NewStruct(data) + if err != nil { + return nil, err + } + return &Statement{ + Statement: &in_toto_attest.Statement{ + Type: in_toto.StatementInTotoV01, + PredicateType: in_toto.PredicateCycloneDX, + Subject: []*in_toto_attest.ResourceDescriptor{ + { + Name: repo, + Digest: map[string]string{ + "sha256": digest, + }, + }, + }, + Predicate: dataObj, + }}, nil } func checkRequiredJSONFields(rawPayload []byte, typ reflect.Type) error { + if typ.Kind() == reflect.Pointer { + typ = typ.Elem() + } var tmp map[string]interface{} if err := json.Unmarshal(rawPayload, &tmp); err != nil { return err @@ -305,7 +489,7 @@ func checkRequiredJSONFields(rawPayload []byte, typ reflect.Type) error { allFields := make([]string, 0) for i := 0; i < attributeCount; i++ { jsonTagFields := strings.SplitN(typ.Field(i).Tag.Get("json"), ",", 2) - if len(jsonTagFields) < 2 { + if len(jsonTagFields) < 2 && jsonTagFields[0] != "" && jsonTagFields[0] != "-" { allFields = append(allFields, jsonTagFields[0]) } } @@ -318,3 +502,35 @@ func checkRequiredJSONFields(rawPayload []byte, typ reflect.Type) error { } return nil } + +func structToStruct(obj any) (*structpb.Struct, error) { + toJSON, err := json.Marshal(obj) + if err != nil { + return nil, err + } + var toMap map[string]any + if err := json.Unmarshal(toJSON, &toMap); err != nil { + return nil, err + } + toStruct, err := structpb.NewStruct(toMap) + if err != nil { + return nil, err + } + return toStruct, nil +} + +func protoStructToStruct(obj proto.Message) (*structpb.Struct, error) { + toJSON, err := protojson.Marshal(obj) + if err != nil { + return nil, err + } + var toMap map[string]any + if err := json.Unmarshal(toJSON, &toMap); err != nil { + return nil, err + } + toStruct, err := structpb.NewStruct(toMap) + if err != nil { + return nil, err + } + return toStruct, nil +} diff --git a/pkg/cosign/attestation/attestation_test.go b/pkg/cosign/attestation/attestation_test.go new file mode 100644 index 00000000000..97be8f26007 --- /dev/null +++ b/pkg/cosign/attestation/attestation_test.go @@ -0,0 +1,253 @@ +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func TestGenerateStatement(t *testing.T) { + fixedTime := time.Date(2024, 3, 11, 10, 0, 0, 0, time.UTC) + opts := GenerateOpts{ + Digest: "abcdef123456", + Repo: "test-repo", + Time: func() time.Time { return fixedTime }, + } + + tests := []struct { + name string + predType string + predicate string + wantJSON string + }{ + { + name: "custom", + predType: "custom", + predicate: "some data", + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://cosign.sigstore.dev/attestation/v1","predicate":{"Data":"some data","Timestamp":"2024-03-11T10:00:00Z"}}`, + }, + { + name: "custom type", + predType: "https://example.com/predicate/v1", + predicate: `{"key":"value"}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://example.com/predicate/v1","predicate":{"key":"value"}}`, + }, + { + name: "custom empty string", + predType: "custom", + predicate: "", + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://cosign.sigstore.dev/attestation/v1","predicate":{"Data":"","Timestamp":"2024-03-11T10:00:00Z"}}`, + }, + { + name: "spdx string", + predType: "spdx", + predicate: "SPDX-data", + + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://spdx.dev/Document","predicate":"SPDX-data"}`, + }, + { + name: "spdx string multi-line", + predType: "spdx", + predicate: "SPDXVersion: SPDX-2.3\\nDataLicense: CC0-1.0\\nDocumentName: Test", + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://spdx.dev/Document","predicate":"SPDXVersion: SPDX-2.3\\nDataLicense: CC0-1.0\\nDocumentName: Test"}`, + }, + { + name: "spdx json", + predType: "spdxjson", + predicate: `{"spdxVersion":"SPDX-2.3"}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://spdx.dev/Document","predicate":{"spdxVersion":"SPDX-2.3"}}`, + }, + { + name: "spdx json complex", + predType: "spdxjson", + predicate: `{ + "spdxVersion": "SPDX-2.3", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "Test Document", + "documentNamespace": "http://example.com/spdx/TestDocument", + "creationInfo": { + "creators": ["Person: John Doe"], + "created": "2024-03-11T10:00:00Z" + } + }`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://spdx.dev/Document","predicate":{"spdxVersion":"SPDX-2.3","dataLicense":"CC0-1.0","SPDXID":"SPDXRef-DOCUMENT","name":"Test Document","documentNamespace":"http://example.com/spdx/TestDocument","creationInfo":{"creators":["Person: John Doe"],"created":"2024-03-11T10:00:00Z"}}}`, + }, + { + name: "cyclonedx", + predType: "cyclonedx", + predicate: `{"bomFormat":"CycloneDX"}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://cyclonedx.org/bom","predicate":{"bomFormat":"CycloneDX"}}`, + }, + { + name: "cyclonedx complex", + predType: "cyclonedx", + predicate: `{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "metadata": { + "timestamp": "2024-03-11T10:00:00Z", + "tools": [ + { + "vendor": "Test Vendor", + "name": "Test Tool", + "version": "1.0.0" + } + ] + }, + "components": [] + }`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://cyclonedx.org/bom","predicate":{"bomFormat":"CycloneDX","specVersion":"1.4","version":1,"metadata":{"timestamp":"2024-03-11T10:00:00Z","tools":[{"vendor":"Test Vendor","name":"Test Tool","version":"1.0.0"}]},"components":[]}}`, + }, + { + name: "link", + predType: "link", + predicate: `{"_type":"link","name":"test-link","command":["cmd"],"materials":{"hash":{"sha256":"123"}},"products":{"hash":{"sha256":"456"}},"byproducts":{"command":"test"},"environment":{"env":"test"}}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://in-toto.io/Link/v1", "predicate":{"_type":"link","name":"test-link","command":["cmd"],"materials":{"hash":{"sha256":"123"}},"products":{"hash":{"sha256":"456"}},"byproducts":{"command":"test"},"environment":{"env":"test"}}}`, + }, + { + name: "link minimal", + predType: "link", + predicate: `{"_type":"link","name":"minimal-link","command":["do"],"materials":{},"products":{},"byproducts":{},"environment":{"env":"test"}}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://in-toto.io/Link/v1", "predicate":{"_type":"link","name":"minimal-link","command":["do"],"materials":{},"products":{},"byproducts":{},"environment":{"env":"test"}}}`, + }, + { + name: "vuln", + predType: "vuln", + predicate: `{"invocation":{"parameters":"foo=bar","uri":"test-uri","event_id":"123","builder.id":"456"},"scanner":{"uri":"test-scanner","version":"2","db":{"uri":"test-db-uri","version":"3"},"result":"passed"},"metadata":{"scanStartedOn":"2024-03-11T10:00:00Z","scanFinishedOn":"2024-03-11T10:05:00Z"}}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://cosign.sigstore.dev/attestation/vuln/v1","predicate":{"invocation":{"parameters":"foo=bar","uri":"test-uri","event_id":"123","builder.id":"456"},"scanner":{"uri":"test-scanner","version":"2","db":{"uri":"test-db-uri","version":"3"},"result":"passed"},"metadata":{"scanStartedOn":"2024-03-11T10:00:00Z","scanFinishedOn":"2024-03-11T10:05:00Z"}}}`, + }, + { + name: "vuln minimal", + predType: "vuln", + predicate: `{"invocation":{},"scanner":{"uri":"test-scanner"},"metadata":{"scanStartedOn":"2024-03-11T10:00:00Z"}}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://cosign.sigstore.dev/attestation/vuln/v1","predicate":{"invocation":{"parameters":null,"uri":"","event_id":"","builder.id":""},"scanner":{"uri":"test-scanner","version":"","db":{"uri":"","version":""},"result":null},"metadata":{"scanStartedOn":"2024-03-11T10:00:00Z","scanFinishedOn":"0001-01-01T00:00:00Z"}}}`, + }, + { + name: "openvex", + predType: "openvex", + predicate: `{"@context":"https://openvex.dev/ns","@id":"some-id"}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://openvex.dev/ns","predicate":{"@context":"https://openvex.dev/ns","@id":"some-id"}}`, + }, + { + name: "openvex complex", + predType: "openvex", + predicate: `{ + "@context": "https://openvex.dev/ns", + "@id": "https://example.com/vex/doc-1", + "author": "Test Author", + "timestamp": "2024-03-11T10:00:00Z", + "statements": [ + { + "vulnerability": "CVE-2026-5678", + "products": ["pkg:npm/test-package@1.0.0"], + "status": "not_affected" + } + ] + }`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://openvex.dev/ns","predicate":{"@context":"https://openvex.dev/ns","@id":"https://example.com/vex/doc-1","author":"Test Author","timestamp":"2024-03-11T10:00:00Z","statements":[{"vulnerability":"CVE-2026-5678","products":["pkg:npm/test-package@1.0.0"],"status":"not_affected"}]}}`, + }, + { + name: "openvex minimal", + predType: "openvex", + predicate: `{"@context":"https://openvex.dev/ns","@id":"some-id"}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://openvex.dev/ns","predicate":{"@context":"https://openvex.dev/ns","@id":"some-id"}}`, + }, + { + name: "slsaprovenance", + predType: "slsaprovenance", + predicate: `{"builder":{"id":"2"},"buildType":"test"}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://slsa.dev/provenance/v0.2","predicate":{"builder":{"id":"2"},"buildType":"test"}}`, + }, + { + name: "slsaprovenance02", + predType: "slsaprovenance02", + predicate: `{"builder":{"id":"2"},"buildType":"test"}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://slsa.dev/provenance/v0.2","predicate":{"builder":{"id":"2"},"buildType":"test"}}`, + }, + { + name: "slsaprovenance1", + predType: "slsaprovenance1", + predicate: `{"buildDefinition":{"buildType":"test"},"runDetails":{"builder":{"id":"x"}}}`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://slsa.dev/provenance/v1","predicate":{"buildDefinition":{"buildType":"test"},"runDetails":{"builder":{"id":"x"}}}}`, + }, + { + name: "slsaprovenance1 complex", + predType: "slsaprovenance1", + predicate: `{ + "buildDefinition": { + "buildType": "https://example.com/Makefile", + "externalParameters": { + "version": "1.0" + }, + "internalParameters": {}, + "resolvedDependencies": [ + { + "uri": "git+https://example.com/repo.git", + "digest": {"sha1": "abcdef123456"} + } + ] + }, + "runDetails": { + "builder": { + "id": "https://example.com/builder" + }, + "metadata": { + "invocationID": "test-invocation" + }, + "byproducts": [] + } + }`, + wantJSON: `{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test-repo","digest":{"sha256":"abcdef123456"}}],"predicateType":"https://slsa.dev/provenance/v1","predicate":{"buildDefinition":{"buildType":"https://example.com/Makefile","externalParameters":{"version":"1.0"},"internalParameters":{},"resolvedDependencies":[{"uri":"git+https://example.com/repo.git","digest":{"sha1":"abcdef123456"}}]},"runDetails":{"builder":{"id":"https://example.com/builder"},"metadata":{"invocationId":"test-invocation"}}}}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opts.Type = tt.predType + opts.Predicate = strings.NewReader(tt.predicate) + + gotStmt, err := GenerateStatement(opts) + if err != nil { + t.Fatalf("GenerateStatement() error = %v", err) + } + + gotJSON, err := gotStmt.MarshalJSON() + if err != nil { + t.Fatalf("gotStmt.MarshalJSON() error = %v", err) + } + + if diff := cmp.Diff(normalizeJSON(t, []byte(tt.wantJSON)), normalizeJSON(t, gotJSON)); diff != "" { + t.Errorf("GenerateStatement() JSON diff (-want +got): %s", diff) + t.Logf("GOT JSON: %s", string(gotJSON)) + } + }) + } +} + +func normalizeJSON(t *testing.T, data []byte) map[string]any { + t.Helper() + var v map[string]any + if err := json.Unmarshal(data, &v); err != nil { + t.Fatalf("json.Unmarshal error for: %s %v", string(data), err) + } + return v +} diff --git a/pkg/cosign/bundle/constants.go b/pkg/cosign/bundle/constants.go new file mode 100644 index 00000000000..0482b53f754 --- /dev/null +++ b/pkg/cosign/bundle/constants.go @@ -0,0 +1,17 @@ +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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 + +const BundleV03MediaType = "application/vnd.dev.sigstore.bundle.v0.3+json" diff --git a/pkg/cosign/bundle/protobundle.go b/pkg/cosign/bundle/protobundle.go deleted file mode 100644 index a26e2fb2bb7..00000000000 --- a/pkg/cosign/bundle/protobundle.go +++ /dev/null @@ -1,65 +0,0 @@ -// 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 -} diff --git a/pkg/cosign/bundle/protobundle_test.go b/pkg/cosign/bundle/protobundle_test.go deleted file mode 100644 index dea1bce59bb..00000000000 --- a/pkg/cosign/bundle/protobundle_test.go +++ /dev/null @@ -1,92 +0,0 @@ -// 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") - } - }) - } -} diff --git a/pkg/cosign/bundle/rekor.go b/pkg/cosign/bundle/rekor.go index 5331302051d..6bc48a7bd4d 100644 --- a/pkg/cosign/bundle/rekor.go +++ b/pkg/cosign/bundle/rekor.go @@ -14,10 +14,6 @@ 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 { @@ -31,18 +27,3 @@ type RekorPayload struct { 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, - }, - } -} diff --git a/pkg/cosign/bundle/rekor_test.go b/pkg/cosign/bundle/rekor_test.go deleted file mode 100644 index eb0e26ad6fb..00000000000 --- a/pkg/cosign/bundle/rekor_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// 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) - } - }) - } -} diff --git a/pkg/cosign/bundle/sign.go b/pkg/cosign/bundle/sign.go new file mode 100644 index 00000000000..8d2afb577f7 --- /dev/null +++ b/pkg/cosign/bundle/sign.go @@ -0,0 +1,212 @@ +// 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 bundle + +import ( + "context" + "crypto/x509" + "encoding/pem" + "fmt" + "log" + "net/http" + "sync" + "time" + + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore-go/pkg/sign" + "github.com/sigstore/sigstore/pkg/signature" + "google.golang.org/protobuf/encoding/protojson" +) + +type SignOptions struct { + TSAClientTransport http.RoundTripper + CertificateProvider sign.CertificateProvider +} + +func SignData(ctx context.Context, content sign.Content, keypair sign.Keypair, idToken string, cert []byte, signingConfig *root.SigningConfig, trustedMaterial root.TrustedMaterial, opts SignOptions) ([]byte, error) { + var bundleOpts sign.BundleOptions + + if trustedMaterial != nil { + bundleOpts.TrustedRoot = trustedMaterial + } + + switch { + case opts.CertificateProvider != nil: + bundleOpts.CertificateProvider = opts.CertificateProvider + if idToken != "" { + bundleOpts.CertificateProviderOptions = &sign.CertificateProviderOptions{ + IDToken: idToken, + } + } + case idToken != "": + provider, err := newFulcioProvider(signingConfig) + if err != nil { + return nil, err + } + bundleOpts.CertificateProvider = provider + bundleOpts.CertificateProviderOptions = &sign.CertificateProviderOptions{ + IDToken: idToken, + } + case cert != nil: + bundleOpts.CertificateProvider = &localCertProvider{cert} + default: + publicKeyPem, err := keypair.GetPublicKeyPem() + if err != nil { + return nil, err + } + block, _ := pem.Decode([]byte(publicKeyPem)) + pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + log.Fatal(err) + } + verifier, err := signature.LoadDefaultVerifier(pubKey) + if err != nil { + log.Fatal(err) + } + key := root.NewExpiringKey(verifier, time.Time{}, time.Time{}) + keyTrustedMaterial := root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) { + return key, nil + }) + if bundleOpts.TrustedRoot != nil { + trustedMaterial := &verifyTrustedMaterial{ + TrustedMaterial: bundleOpts.TrustedRoot, + keyTrustedMaterial: keyTrustedMaterial, + } + bundleOpts.TrustedRoot = trustedMaterial + } + } + + if len(signingConfig.TimestampAuthorityURLs()) != 0 { + tsaSvcs, err := root.SelectServices(signingConfig.TimestampAuthorityURLs(), + signingConfig.TimestampAuthorityURLsConfig(), sign.TimestampAuthorityAPIVersions, time.Now()) + if err != nil { + log.Fatal(err) + } + for _, tsaSvc := range tsaSvcs { + tsaOpts := &sign.TimestampAuthorityOptions{ + URL: tsaSvc.URL, + Timeout: 30 * time.Second, + Retries: 1, + } + if opts.TSAClientTransport != nil { + tsaOpts.Transport = opts.TSAClientTransport + } + bundleOpts.TimestampAuthorities = append(bundleOpts.TimestampAuthorities, sign.NewTimestampAuthority(tsaOpts)) + } + } + + var usingRekorV2 bool + if len(signingConfig.RekorLogURLs()) != 0 { + rekorSvcs, err := root.SelectServices(signingConfig.RekorLogURLs(), + signingConfig.RekorLogURLsConfig(), sign.RekorAPIVersions, time.Now()) + if err != nil { + return nil, err + } + for _, rekorSvc := range rekorSvcs { + if rekorSvc.MajorAPIVersion == 2 { + usingRekorV2 = true + } + rekorOpts := &sign.RekorOptions{ + BaseURL: rekorSvc.URL, + Timeout: 90 * time.Second, + Retries: 1, + Version: rekorSvc.MajorAPIVersion, + } + bundleOpts.TransparencyLogs = append(bundleOpts.TransparencyLogs, sign.NewRekor(rekorOpts)) + } + } + // When requesting a short-lived Fulcio certificate, a timestamp must be provided during + // verification. It can come from either Rekor v1 providing a signed entry timestamp, or + // from a timestamp authority. Rekor v2 doesn't timestamp entries, so when a client + // is configured to use Rekor v2 when retrieving a Fulcio certificate, we enforce + // that a timestamp authority is provided as well. + if usingRekorV2 && len(bundleOpts.TimestampAuthorities) == 0 && idToken != "" { + return nil, fmt.Errorf("a timestamp authority must be provided to request a short-lived certificate that will be logged to Rekor") + } + + spinner := ui.NewSpinner(ctx, "Signing artifact...") + defer spinner.Stop() + + bundle, err := sign.Bundle(content, keypair, bundleOpts) + + if err != nil { + return nil, fmt.Errorf("error signing bundle: %w", err) + } + return protojson.Marshal(bundle) +} + +type verifyTrustedMaterial struct { + root.TrustedMaterial + keyTrustedMaterial root.TrustedMaterial +} + +func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstrainedVerifier, error) { + return v.keyTrustedMaterial.PublicKeyVerifier(hint) +} + +type localCertProvider struct { + cert []byte +} + +func (c *localCertProvider) GetCertificate(_ context.Context, _ sign.Keypair, _ *sign.CertificateProviderOptions) ([]byte, error) { + certBlock, _ := pem.Decode(c.cert) + if certBlock == nil { + return nil, fmt.Errorf("could not decode cert") + } + return certBlock.Bytes, nil +} + +type cachingCertProvider struct { + provider sign.CertificateProvider + once sync.Once + fetch func() ([]byte, error) +} + +func (c *cachingCertProvider) GetCertificate(ctx context.Context, keypair sign.Keypair, opts *sign.CertificateProviderOptions) ([]byte, error) { + c.once.Do(func() { + c.fetch = sync.OnceValues(func() ([]byte, error) { + return c.provider.GetCertificate(ctx, keypair, opts) + }) + }) + return c.fetch() +} + +func newFulcioProvider(signingConfig *root.SigningConfig) (sign.CertificateProvider, error) { + if len(signingConfig.FulcioCertificateAuthorityURLs()) == 0 { + return nil, fmt.Errorf("no fulcio URLs provided in signing config") + } + fulcioSvc, err := root.SelectService(signingConfig.FulcioCertificateAuthorityURLs(), sign.FulcioAPIVersions, time.Now()) + if err != nil { + return nil, err + } + fulcioOpts := &sign.FulcioOptions{ + BaseURL: fulcioSvc.URL, + Timeout: 30 * time.Second, + Retries: 1, + } + return sign.NewFulcio(fulcioOpts), nil +} + +// NewCachingFulcioProvider creates a caching Fulcio provider from the given signing config. +// This function should not be used in long-running processes, as the certificate will +// expire. +func NewCachingFulcioProvider(signingConfig *root.SigningConfig) (sign.CertificateProvider, error) { + provider, err := newFulcioProvider(signingConfig) + if err != nil { + return nil, err + } + return &cachingCertProvider{provider: provider}, nil +} diff --git a/pkg/cosign/ctlog.go b/pkg/cosign/ctlog.go index 9f2ebc3d5ec..5ad76a00b14 100644 --- a/pkg/cosign/ctlog.go +++ b/pkg/cosign/ctlog.go @@ -20,7 +20,7 @@ import ( "fmt" "os" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" "github.com/sigstore/sigstore/pkg/tuf" ) diff --git a/pkg/cosign/cue/cue_test.go b/pkg/cosign/cue/cue_test.go index 9748fad3437..e16b3782622 100644 --- a/pkg/cosign/cue/cue_test.go +++ b/pkg/cosign/cue/cue_test.go @@ -157,7 +157,7 @@ func TestValidationJSON(t *testing.T) { } `, pass: false, - errorMsg: "authorityMatches.keysignature.signatures: invalid value [{subject:\"PLACEHOLDER\",issuer:\"PLACEHOLDER\"}] (does not satisfy list.MinItems(2))", + errorMsg: "authorityMatches.keysignature.signatures: invalid value [{subject:\"PLACEHOLDER\",issuer:\"PLACEHOLDER\"}] (does not satisfy list.MinItems(2)): len(list) < MinItems(2) (1 < 2)", }, } diff --git a/pkg/cosign/errors.go b/pkg/cosign/errors.go index f728cd4975d..923e27913f2 100644 --- a/pkg/cosign/errors.go +++ b/pkg/cosign/errors.go @@ -92,6 +92,7 @@ func (e *ErrNoCertificateFoundOnSignature) Unwrap() error { } // NewVerificationError exists for backwards compatibility. +// // Deprecated: see [VerificationFailure]. func NewVerificationError(msg string, args ...interface{}) error { return &VerificationError{ @@ -100,6 +101,7 @@ func NewVerificationError(msg string, args ...interface{}) error { } // VerificationError exists for backwards compatibility. +// // Deprecated: see [VerificationFailure]. type VerificationError struct { message string @@ -111,10 +113,12 @@ func (e *VerificationError) Error() string { var ( // ErrNoMatchingAttestationsMessage exists for backwards compatibility. + // // Deprecated: see [ErrNoMatchingAttestations]. ErrNoMatchingAttestationsMessage = "no matching attestations" // ErrNoMatchingAttestationsType exists for backwards compatibility. + // // Deprecated: see [ErrNoMatchingAttestations]. ErrNoMatchingAttestationsType = "NoMatchingAttestations" ) diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go index 709333ac77f..e352f1d1092 100644 --- a/pkg/cosign/fetch.go +++ b/pkg/cosign/fetch.go @@ -27,10 +27,10 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" "golang.org/x/sync/errgroup" ) @@ -186,8 +186,9 @@ func FetchAttestations(se oci.SignedEntity, predicateType string) ([]Attestation if err != nil { return fmt.Errorf("decoding payload: %w", err) } - var statement in_toto.Statement - if err := json.Unmarshal(decodedPayload, &statement); err != nil { + statement := &attestation.Statement{} + + if err := statement.UnmarshalJSON(decodedPayload); err != nil { return fmt.Errorf("unmarshaling statement: %w", err) } if statement.PredicateType != predicateType { diff --git a/pkg/cosign/fuzz_test.go b/pkg/cosign/fuzz_test.go index db56ab11e1f..9cf9afc579f 100644 --- a/pkg/cosign/fuzz_test.go +++ b/pkg/cosign/fuzz_test.go @@ -24,7 +24,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/pkg/oci/mutate" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" ) var ( @@ -61,7 +61,7 @@ func FuzzImportKeyPairLoadPrivateKey(f *testing.F) { return } // Loading the private key should also work. - _, err = LoadPrivateKey(keyBytes.PrivateBytes, password) + _, err = LoadPrivateKey(keyBytes.PrivateBytes, password, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/cosign/git/git.go b/pkg/cosign/git/git.go index b4380f2c137..4a1b81d0ece 100644 --- a/pkg/cosign/git/git.go +++ b/pkg/cosign/git/git.go @@ -18,9 +18,9 @@ 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" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/git/github" + "github.com/sigstore/cosign/v3/pkg/cosign/git/gitlab" ) var providerMap = map[string]Git{ diff --git a/pkg/cosign/git/github/github.go b/pkg/cosign/git/github/github.go index 3b8ce918308..93405295ab6 100644 --- a/pkg/cosign/git/github/github.go +++ b/pkg/cosign/git/github/github.go @@ -25,12 +25,12 @@ import ( "os" "strings" - "github.com/google/go-github/v73/github" + "github.com/google/go-github/v88/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" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) const ( @@ -57,12 +57,16 @@ func (g *Gh) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) erro var client *github.Client if host, ok := env.LookupEnv(env.VariableGitHubHost); ok { var err error - client, err = github.NewClient(httpClient).WithEnterpriseURLs(host, host) + client, err = github.NewClient(github.WithHTTPClient(httpClient), github.WithEnterpriseURLs(host, host)) if err != nil { return fmt.Errorf("could not create github enterprise client: %w", err) } } else { - client = github.NewClient(httpClient) + var err error + client, err = github.NewClient(github.WithHTTPClient(httpClient)) + if err != nil { + return fmt.Errorf("could not create github client: %w", err) + } } keys, err := cosign.GenerateKeyPair(pf) @@ -87,7 +91,7 @@ func (g *Gh) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) erro return fmt.Errorf("could not get repository public key: %w", err) } - if getPubKeyResp.StatusCode < 200 && getPubKeyResp.StatusCode >= 300 { + if getPubKeyResp.StatusCode < 200 || getPubKeyResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(getPubKeyResp.Body) return fmt.Errorf("%s", bodyBytes) } @@ -102,7 +106,7 @@ func (g *Gh) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) erro return fmt.Errorf("could not create \"COSIGN_PASSWORD\" github actions secret: %w", err) } - if passwordSecretEnvResp.StatusCode < 200 && passwordSecretEnvResp.StatusCode >= 300 { + if passwordSecretEnvResp.StatusCode < 200 || passwordSecretEnvResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(passwordSecretEnvResp.Body) return fmt.Errorf("%s", bodyBytes) } @@ -119,7 +123,7 @@ func (g *Gh) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) erro return fmt.Errorf("could not create \"COSIGN_PRIVATE_KEY\" github actions secret: %w", err) } - if privateKeySecretEnvResp.StatusCode < 200 && privateKeySecretEnvResp.StatusCode >= 300 { + if privateKeySecretEnvResp.StatusCode < 200 || privateKeySecretEnvResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(privateKeySecretEnvResp.Body) return fmt.Errorf("%s", bodyBytes) } @@ -136,7 +140,7 @@ func (g *Gh) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) erro return fmt.Errorf("could not create \"COSIGN_PUBLIC_KEY\" github actions secret: %w", err) } - if publicKeySecretEnvResp.StatusCode < 200 && publicKeySecretEnvResp.StatusCode >= 300 { + if publicKeySecretEnvResp.StatusCode < 200 || publicKeySecretEnvResp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(publicKeySecretEnvResp.Body) return fmt.Errorf("%s", bodyBytes) } diff --git a/pkg/cosign/git/gitlab/gitlab.go b/pkg/cosign/git/gitlab/gitlab.go index b1246913218..4d4bcf0938f 100644 --- a/pkg/cosign/git/gitlab/gitlab.go +++ b/pkg/cosign/git/gitlab/gitlab.go @@ -21,9 +21,9 @@ import ( "io" "os" - "github.com/sigstore/cosign/v2/internal/ui" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/env" gitlab "gitlab.com/gitlab-org/api/client-go" ) @@ -37,6 +37,25 @@ func New() *Gl { return &Gl{} } +// isGroup checks if the given reference is a GitLab group by attempting to retrieve it. +// It returns true if the reference is a group, false if it's a project, and an error if neither. +func isGroup(client *gitlab.Client, ref string) (bool, error) { + // Try to get as a project first (most common case) + _, resp, err := client.Projects.GetProject(ref, nil) + if err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 { + return false, nil + } + + // If project lookup failed, try as a group + _, resp, err = client.Groups.GetGroup(ref, nil) + if err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 { + return true, nil + } + + // Neither project nor group found + return false, fmt.Errorf("reference %q is neither a valid project nor group", ref) +} + func (g *Gl) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error { keys, err := cosign.GenerateKeyPair(pf) if err != nil { @@ -62,61 +81,126 @@ func (g *Gl) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) erro } } - _, 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("*"), - }) + // Determine if ref is a group or project + isGrp, err := isGroup(client, ref) 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) + return fmt.Errorf("could not determine if reference is a group or project: %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) + var refType string + if isGrp { + refType = "group" + } else { + refType = "project" } - if privateKeyResp.StatusCode < 200 && privateKeyResp.StatusCode >= 300 { - bodyBytes, _ := io.ReadAll(privateKeyResp.Body) - return fmt.Errorf("%s", bodyBytes) + // Create COSIGN_PASSWORD variable + if isGrp { + _, passwordResp, err := client.GroupVariables.CreateVariable(ref, &gitlab.CreateGroupVariableOptions{ + 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 { + 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) + } + } else { + _, 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 { + 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, "Private key written to \"COSIGN_PRIVATE_KEY\" variable") + ui.Infof(ctx, "Password written to \"COSIGN_PASSWORD\" %s variable", refType) - _, 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) + // Create COSIGN_PRIVATE_KEY variable + if isGrp { + _, privateKeyResp, err := client.GroupVariables.CreateVariable(ref, &gitlab.CreateGroupVariableOptions{ + 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) + } + } else { + _, 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) + } } - if publicKeyResp.StatusCode < 200 && publicKeyResp.StatusCode >= 300 { - bodyBytes, _ := io.ReadAll(publicKeyResp.Body) - return fmt.Errorf("%s", bodyBytes) + ui.Infof(ctx, "Private key written to \"COSIGN_PRIVATE_KEY\" %s variable", refType) + + // Create COSIGN_PUBLIC_KEY variable + if isGrp { + _, publicKeyResp, err := client.GroupVariables.CreateVariable(ref, &gitlab.CreateGroupVariableOptions{ + 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) + } + } else { + _, 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") + ui.Infof(ctx, "Public key written to \"COSIGN_PUBLIC_KEY\" %s variable", refType) if err := os.WriteFile("cosign.pub", keys.PublicBytes, 0o600); err != nil { return err @@ -128,9 +212,9 @@ func (g *Gl) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) erro func (g *Gl) GetSecret(_ context.Context, ref string, key string) (string, error) { token, tokenExists := env.LookupEnv(env.VariableGitLabToken) - var varPubKeyValue string + var varValue string if !tokenExists { - return varPubKeyValue, fmt.Errorf("could not find %q", env.VariableGitLabToken.String()) + return varValue, fmt.Errorf("could not find %q", env.VariableGitLabToken.String()) } var client *gitlab.Client @@ -138,26 +222,43 @@ func (g *Gl) GetSecret(_ context.Context, ref string, key string) (string, 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) + return varValue, 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) + return varValue, fmt.Errorf("could not create GitLab client: %w", err) } } - varPubKey, pubKeyResp, err := client.ProjectVariables.GetVariable(ref, key, nil) + // Determine if ref is a group or project + isGrp, err := isGroup(client, ref) if err != nil { - return varPubKeyValue, fmt.Errorf("could not retrieve \"COSIGN_PUBLIC_KEY\" variable: %w", err) + return varValue, fmt.Errorf("could not determine if reference is a group or project: %w", err) } - varPubKeyValue = varPubKey.Value - - if pubKeyResp.StatusCode < 200 && pubKeyResp.StatusCode >= 300 { - bodyBytes, _ := io.ReadAll(pubKeyResp.Body) - return varPubKeyValue, fmt.Errorf("%s", bodyBytes) + // Get variable based on reference type + if isGrp { + groupVar, resp, err := client.GroupVariables.GetVariable(ref, key, nil) + if err != nil { + return varValue, fmt.Errorf("could not retrieve %q group variable: %w", key, err) + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + bodyBytes, _ := io.ReadAll(resp.Body) + return varValue, fmt.Errorf("%s", bodyBytes) + } + varValue = groupVar.Value + } else { + projectVar, resp, err := client.ProjectVariables.GetVariable(ref, key, nil) + if err != nil { + return varValue, fmt.Errorf("could not retrieve %q project variable: %w", key, err) + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + bodyBytes, _ := io.ReadAll(resp.Body) + return varValue, fmt.Errorf("%s", bodyBytes) + } + varValue = projectVar.Value } - return varPubKeyValue, nil + return varValue, nil } diff --git a/pkg/cosign/git/gitlab/gitlab_test.go b/pkg/cosign/git/gitlab/gitlab_test.go new file mode 100644 index 00000000000..e71bccbec15 --- /dev/null +++ b/pkg/cosign/git/gitlab/gitlab_test.go @@ -0,0 +1,129 @@ +// +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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 ( + "net/http" + "net/http/httptest" + "testing" + + gitlab "gitlab.com/gitlab-org/api/client-go" +) + +func TestIsGroup(t *testing.T) { + tests := []struct { + name string + ref string + projectStatus int + groupStatus int + expectedResult bool + expectError bool + }{ + { + name: "valid project reference", + ref: "owner/project", + projectStatus: http.StatusOK, + groupStatus: http.StatusNotFound, + expectedResult: false, + expectError: false, + }, + { + name: "valid group reference", + ref: "mygroup", + projectStatus: http.StatusNotFound, + groupStatus: http.StatusOK, + expectedResult: true, + expectError: false, + }, + { + name: "invalid reference - neither project nor group", + ref: "invalid/reference", + projectStatus: http.StatusNotFound, + groupStatus: http.StatusNotFound, + expectedResult: false, + expectError: true, + }, + { + name: "numeric project ID", + ref: "12345", + projectStatus: http.StatusOK, + groupStatus: http.StatusNotFound, + expectedResult: false, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a test server that simulates GitLab API responses + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Check if it's a project or group API call + if r.URL.Path == "/api/v4/projects/"+tt.ref { + w.WriteHeader(tt.projectStatus) + if tt.projectStatus == http.StatusOK { + w.Write([]byte(`{"id": 1, "name": "test-project"}`)) + } + return + } + if r.URL.Path == "/api/v4/groups/"+tt.ref { + w.WriteHeader(tt.groupStatus) + if tt.groupStatus == http.StatusOK { + w.Write([]byte(`{"id": 1, "name": "test-group"}`)) + } + return + } + w.WriteHeader(http.StatusNotFound) + })) + defer server.Close() + + // Create a GitLab client pointed at the test server + client, err := gitlab.NewClient("test-token", gitlab.WithBaseURL(server.URL+"/api/v4")) + if err != nil { + t.Fatalf("failed to create test client: %v", err) + } + + // Test the isGroup function + result, err := isGroup(client, tt.ref) + + // Check error expectation + if tt.expectError && err == nil { + t.Errorf("expected an error but got none") + } + if !tt.expectError && err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check result + if result != tt.expectedResult { + t.Errorf("expected result %v, got %v", tt.expectedResult, result) + } + }) + } +} + +func TestNew(t *testing.T) { + gl := New() + if gl == nil { + t.Error("New() returned nil") + } +} + +func TestReferenceScheme(t *testing.T) { + expected := "gitlab" + if ReferenceScheme != expected { + t.Errorf("ReferenceScheme = %q, want %q", ReferenceScheme, expected) + } +} diff --git a/pkg/cosign/keys.go b/pkg/cosign/keys.go index ed5bbc16d4b..7493782cae0 100644 --- a/pkg/cosign/keys.go +++ b/pkg/cosign/keys.go @@ -19,7 +19,6 @@ import ( "crypto" "crypto/ecdsa" "crypto/ed25519" - "crypto/elliptic" "crypto/rand" "crypto/rsa" _ "crypto/sha256" // for `crypto.SHA256` @@ -29,11 +28,15 @@ import ( "fmt" "os" "path/filepath" + "sort" "github.com/secure-systems-lab/go-securesystemslib/encrypted" - "github.com/sigstore/cosign/v2/pkg/oci/static" + "github.com/sigstore/cosign/v3/pkg/oci/static" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/cryptoutils/goodkey" "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" ) const ( @@ -49,6 +52,17 @@ const ( RFC3161TimestampKey = static.RFC3161TimestampAnnotationKey ) +var SupportedKeyDetails = []v1.PublicKeyDetails{ + v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256, + v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384, + v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512, + v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256, + v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256, + v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256, + // Ed25519ph is not supported by Fulcio, so we don't support it here for now. + // v1.PublicKeyDetails_PKIX_ED25519_PH, +} + // 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) @@ -70,7 +84,48 @@ func (k *KeysBytes) Password() []byte { // GeneratePrivateKey generates an ECDSA private key with the P-256 curve. func GeneratePrivateKey() (*ecdsa.PrivateKey, error) { - return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + priv, err := GeneratePrivateKeyWithAlgorithm(nil) + if err != nil { + return nil, err + } + return priv.(*ecdsa.PrivateKey), nil +} + +// GeneratePrivateKeyWithAlgorithm generates a private key for the given algorithm +func GeneratePrivateKeyWithAlgorithm(algo *signature.AlgorithmDetails) (crypto.PrivateKey, error) { + var currentAlgo signature.AlgorithmDetails + if algo == nil { + var err error + currentAlgo, err = signature.GetAlgorithmDetails(v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256) + if err != nil { + return nil, fmt.Errorf("error getting algorithm details for default algorithm: %w", err) + } + } else { + currentAlgo = *algo + } + + switch currentAlgo.GetKeyType() { + case signature.ECDSA: + curve, err := currentAlgo.GetECDSACurve() + if err != nil { + return nil, fmt.Errorf("error getting ECDSA curve: %w", err) + } + return ecdsa.GenerateKey(*curve, rand.Reader) + case signature.RSA: + rsaKeySize, err := currentAlgo.GetRSAKeySize() + if err != nil { + return nil, fmt.Errorf("error getting RSA key size: %w", err) + } + return rsa.GenerateKey(rand.Reader, int(rsaKeySize)) + case signature.ED25519: + _, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("error generating ED25519 key: %w", err) + } + return priv, nil + default: + return nil, fmt.Errorf("unsupported key type: %v", currentAlgo.GetKeyType()) + } } // ImportKeyPair imports a key pair from a file containing a PEM-encoded @@ -98,7 +153,7 @@ func ImportKeyPair(keyPath string, pf PassFunc) (*KeysBytes, error) { if err != nil { return nil, fmt.Errorf("error parsing rsa private key: %w", err) } - if err = cryptoutils.ValidatePubKey(rsaPk.Public()); err != nil { + if err = goodkey.ValidatePubKey(rsaPk.Public()); err != nil { return nil, fmt.Errorf("error validating rsa key: %w", err) } pk = rsaPk @@ -107,7 +162,7 @@ func ImportKeyPair(keyPath string, pf PassFunc) (*KeysBytes, error) { if err != nil { return nil, fmt.Errorf("error parsing ecdsa private key") } - if err = cryptoutils.ValidatePubKey(ecdsaPk.Public()); err != nil { + if err = goodkey.ValidatePubKey(ecdsaPk.Public()); err != nil { return nil, fmt.Errorf("error validating ecdsa key: %w", err) } pk = ecdsaPk @@ -118,17 +173,17 @@ func ImportKeyPair(keyPath string, pf PassFunc) (*KeysBytes, error) { } switch k := pkcs8Pk.(type) { case *rsa.PrivateKey: - if err = cryptoutils.ValidatePubKey(k.Public()); err != nil { + if err = goodkey.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 { + if err = goodkey.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 { + if err = goodkey.ValidatePubKey(k.Public()); err != nil { return nil, fmt.Errorf("error validating ed25519 key: %w", err) } pk = k @@ -194,6 +249,19 @@ func GenerateKeyPair(pf PassFunc) (*KeysBytes, error) { return marshalKeyPair(SigstorePrivateKeyPemType, Keys{priv, priv.Public()}, pf) } +func GenerateKeyPairWithAlgorithm(algo *signature.AlgorithmDetails, pf PassFunc) (*KeysBytes, error) { + priv, err := GeneratePrivateKeyWithAlgorithm(algo) + if err != nil { + return nil, err + } + signer, ok := priv.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("private key is not a signer verifier") + } + // Emit SIGSTORE keys by default + return marshalKeyPair(SigstorePrivateKeyPemType, Keys{signer, signer.Public()}, pf) +} + // PemToECDSAKey marshals and returns the PEM-encoded ECDSA public key. func PemToECDSAKey(pemBytes []byte) (*ecdsa.PublicKey, error) { pub, err := cryptoutils.UnmarshalPEMToPublicKey(pemBytes) @@ -209,7 +277,7 @@ func PemToECDSAKey(pemBytes []byte) (*ecdsa.PublicKey, error) { // 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) { +func LoadPrivateKey(key []byte, pass []byte, defaultLoadOptions *[]signature.LoadOption) (signature.SignerVerifier, error) { // Decrypt first p, _ := pem.Decode(key) if p == nil { @@ -227,14 +295,32 @@ func LoadPrivateKey(key []byte, pass []byte) (signature.SignerVerifier, error) { 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") + defaultLoadOptions = GetDefaultLoadOptions(defaultLoadOptions) + return signature.LoadDefaultSignerVerifier(pk, *defaultLoadOptions...) +} + +func GetDefaultLoadOptions(defaultLoadOptions *[]signature.LoadOption) *[]signature.LoadOption { + if defaultLoadOptions == nil { + // Cosign uses ED25519ph by default for ED25519 keys, because that's the + // only available option for hashedrekord entries. This behaviour is + // configurable because we want to maintain compatibility with older + // cosign versions that used PureEd25519 for ED25519 keys (but which did + // not support TLog uploads). + return &[]signature.LoadOption{options.WithED25519ph()} + } + return defaultLoadOptions +} + +// GetSupportedAlgorithms returns a list of supported algorithms sorted alphabetically. +func GetSupportedAlgorithms() []string { + algorithms := make([]string, 0, len(SupportedKeyDetails)) + for _, algorithm := range SupportedKeyDetails { + signatureFlag, err := signature.FormatSignatureAlgorithmFlag(algorithm) + if err != nil { + continue + } + algorithms = append(algorithms, signatureFlag) } + sort.Strings(algorithms) + return algorithms } diff --git a/pkg/cosign/keys_test.go b/pkg/cosign/keys_test.go index f1408da27e5..41ace7df23d 100644 --- a/pkg/cosign/keys_test.go +++ b/pkg/cosign/keys_test.go @@ -339,12 +339,12 @@ func TestLoadECDSAPrivateKey(t *testing.T) { } // Load the private key with the right password - if _, err := LoadPrivateKey(keys.PrivateBytes, []byte("hello")); err != nil { + if _, err := LoadPrivateKey(keys.PrivateBytes, []byte("hello"), nil); 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 { + if _, err := LoadPrivateKey(keys.PrivateBytes, []byte("wrong"), nil); err == nil { t.Error("expected error decrypting key!") } @@ -353,7 +353,7 @@ func TestLoadECDSAPrivateKey(t *testing.T) { if _, err := rand.Read(buf[:]); err != nil { t.Fatal(err) } - if _, err := LoadPrivateKey(buf[:], []byte("wrong")); err == nil { + if _, err := LoadPrivateKey(buf[:], []byte("wrong"), nil); err == nil { t.Error("expected error decrypting key!") } } @@ -384,7 +384,7 @@ func TestReadingPrivatePemTypes(t *testing.T) { for _, tc := range testCases { t.Run(tc.pemType, func(t *testing.T) { - _, err := LoadPrivateKey(tc.pemData, []byte("hello")) + _, err := LoadPrivateKey(tc.pemData, []byte("hello"), nil) if tc.expected == nil { require.NoError(t, err) } else { @@ -497,7 +497,7 @@ func TestImportPrivateKey(t *testing.T) { 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")) + _, err = LoadPrivateKey(keyBytes.PrivateBytes, []byte("hello"), nil) require.Equal(t, tc.expected, err) } else { require.Equal(t, tc.expected.Error(), err.Error()) diff --git a/pkg/cosign/kubernetes/secret.go b/pkg/cosign/kubernetes/secret.go index 11289888192..3aaa0f16364 100644 --- a/pkg/cosign/kubernetes/secret.go +++ b/pkg/cosign/kubernetes/secret.go @@ -21,7 +21,7 @@ import ( "os" "strings" - "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/cosign/kubernetes/secret_test.go b/pkg/cosign/kubernetes/secret_test.go index d626d9eada6..4b257ce31db 100644 --- a/pkg/cosign/kubernetes/secret_test.go +++ b/pkg/cosign/kubernetes/secret_test.go @@ -18,7 +18,7 @@ import ( "reflect" "testing" - "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" diff --git a/pkg/cosign/obsolete.go b/pkg/cosign/obsolete.go index 817f05bead0..66ceed55678 100644 --- a/pkg/cosign/obsolete.go +++ b/pkg/cosign/obsolete.go @@ -19,7 +19,7 @@ import ( "context" "github.com/google/go-containerregistry/pkg/name" - "github.com/sigstore/cosign/v2/internal/ui" + "github.com/sigstore/cosign/v3/internal/ui" "github.com/sigstore/sigstore/pkg/signature/payload" ) diff --git a/pkg/cosign/obsolete_test.go b/pkg/cosign/obsolete_test.go index b03ddb91312..22b2b1c8098 100644 --- a/pkg/cosign/obsolete_test.go +++ b/pkg/cosign/obsolete_test.go @@ -20,7 +20,7 @@ import ( "testing" "github.com/google/go-containerregistry/pkg/name" - "github.com/sigstore/cosign/v2/internal/ui" + "github.com/sigstore/cosign/v3/internal/ui" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/cosign/pkcs11key/pkcs11key.go b/pkg/cosign/pkcs11key/pkcs11key.go index c034a3f4fac..50dfb1ed178 100644 --- a/pkg/cosign/pkcs11key/pkcs11key.go +++ b/pkg/cosign/pkcs11key/pkcs11key.go @@ -34,7 +34,7 @@ import ( "github.com/ThalesIgnite/crypto11" "github.com/miekg/pkcs11" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" "github.com/sigstore/sigstore/pkg/signature" "golang.org/x/term" ) diff --git a/pkg/cosign/pkcs11key/util.go b/pkg/cosign/pkcs11key/util.go index beb74a70770..5298d1dc038 100644 --- a/pkg/cosign/pkcs11key/util.go +++ b/pkg/cosign/pkcs11key/util.go @@ -21,7 +21,7 @@ import ( "strconv" "strings" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) const ( @@ -39,7 +39,7 @@ func percentEncode(input []byte) string { var stringBuilder strings.Builder for i := 0; i < len(input); i++ { stringBuilder.WriteByte('%') - stringBuilder.WriteString(fmt.Sprintf("%.2x", input[i])) + fmt.Fprintf(&stringBuilder, "%.2x", input[i]) } return stringBuilder.String() diff --git a/pkg/cosign/rego/rego.go b/pkg/cosign/rego/rego.go index ccc58ec4672..ffb8853e13e 100644 --- a/pkg/cosign/rego/rego.go +++ b/pkg/cosign/rego/rego.go @@ -138,7 +138,10 @@ func ValidateJSONWithModuleInput(jsonBody []byte, moduleInput string) (warnings 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{}) + rMap, ok := r.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("policy is not compliant for query '%s': unexpected result type %T", query, r) + } 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) diff --git a/pkg/cosign/rekor_factory.go b/pkg/cosign/rekor_factory.go deleted file mode 100644 index 320fcbf3589..00000000000 --- a/pkg/cosign/rekor_factory.go +++ /dev/null @@ -1,41 +0,0 @@ -// -// 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) -} diff --git a/pkg/cosign/remote/index.go b/pkg/cosign/remote/index.go index f14f42911cf..e62d875d0ad 100644 --- a/pkg/cosign/remote/index.go +++ b/pkg/cosign/remote/index.go @@ -27,7 +27,7 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/oci/static" ) type File interface { diff --git a/pkg/cosign/remote/remote.go b/pkg/cosign/remote/remote.go index 5c1b800af1c..a9c3f58789b 100644 --- a/pkg/cosign/remote/remote.go +++ b/pkg/cosign/remote/remote.go @@ -22,9 +22,9 @@ import ( "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/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + "github.com/sigstore/cosign/v3/pkg/oci/static" "github.com/sigstore/sigstore/pkg/signature" ) @@ -141,7 +141,11 @@ func (r *ro) Replace(signatures oci.Signatures, o oci.Signature) (oci.Signatures if !ok { return nil, fmt.Errorf("could not find 'payload' in payload data") } - decodedPayload, err := base64.StdEncoding.DecodeString(val.(string)) + payloadStr, ok := val.(string) + if !ok { + return nil, fmt.Errorf("invalid payload: 'payload' field is not a string (got %T)", val) + } + decodedPayload, err := base64.StdEncoding.DecodeString(payloadStr) if err != nil { return nil, fmt.Errorf("could not decode 'payload': %w", err) } diff --git a/pkg/cosign/remote/remote_test.go b/pkg/cosign/remote/remote_test.go new file mode 100644 index 00000000000..b0cb09a9ac8 --- /dev/null +++ b/pkg/cosign/remote/remote_test.go @@ -0,0 +1,75 @@ +// +// Copyright 2026 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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 ( + "strings" + "testing" + + "github.com/sigstore/cosign/v3/pkg/oci" + ociempty "github.com/sigstore/cosign/v3/pkg/oci/empty" + "github.com/sigstore/cosign/v3/pkg/oci/static" +) + +type mockOCISignatures struct { + oci.Signatures + signatures []oci.Signature +} + +func (m *mockOCISignatures) Get() ([]oci.Signature, error) { + return m.signatures, nil +} + +func TestReplaceOpRejectsNonStringPayloadWithoutPanic(t *testing.T) { + tests := []struct { + name string + payloadDoc string + wantSubstr string + }{ + {name: "missing payload", payloadDoc: `{}`, wantSubstr: "could not find 'payload'"}, + {name: "null payload", payloadDoc: `{"payload":null}`, wantSubstr: "'payload' field is not a string"}, + {name: "object payload", payloadDoc: `{"payload":{}}`, wantSubstr: "'payload' field is not a string"}, + {name: "number payload", payloadDoc: `{"payload":123}`, wantSubstr: "'payload' field is not a string"}, + {name: "invalid base64 payload", payloadDoc: `{"payload":"%%%"}`, wantSubstr: "could not decode 'payload'"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + existing, err := static.NewSignature([]byte(tt.payloadDoc), "") + if err != nil { + t.Fatalf("static.NewSignature() = %v", err) + } + newSig, err := static.NewSignature([]byte(`{"payload":"AA=="}`), "") + if err != nil { + t.Fatalf("static.NewSignature() = %v", err) + } + + sigs := &mockOCISignatures{ + Signatures: ociempty.Signatures(), // satisfies the v1.Image surface + signatures: []oci.Signature{existing}, + } + + replaceOp := NewReplaceOp("https://example.com/predicateType") + _, err = replaceOp.Replace(sigs, newSig) + if err == nil { + t.Fatalf("expected error, got nil") + } + if !strings.Contains(err.Error(), tt.wantSubstr) { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} diff --git a/pkg/cosign/tlog.go b/pkg/cosign/tlog.go index bd3c2a897e6..2f7fd40c66d 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -27,14 +27,12 @@ import ( "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/go-openapi/swag/conv" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/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" @@ -55,6 +53,24 @@ import ( // This is the rekor transparency log public key target name var rekorTargetStr = `rekor.pub` +type NamedHash interface { + hash.Hash + crypto.SignerOpts +} + +type CryptoNamedHash struct { + hash.Hash + hashType crypto.Hash +} + +func (h CryptoNamedHash) HashFunc() crypto.Hash { + return h.hashType +} + +func NewCryptoNamedHash(hashType crypto.Hash) NamedHash { + return CryptoNamedHash{Hash: hashType.New(), hashType: hashType} +} + // 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 { @@ -86,12 +102,11 @@ func GetTransparencyLogID(pub crypto.PublicKey) (string, error) { } 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 := make([][]byte, 0, 1) pubKeyBytes = append(pubKeyBytes, pubKey) return types.NewProposedEntry(ctx, dsse.KIND, dsse_v001.APIVERSION, types.ArtifactProperties{ @@ -101,12 +116,11 @@ func dsseEntry(ctx context.Context, signature, pubKey []byte) (models.ProposedEn } 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 := make([][]byte, 0, 1) pubKeyBytes = append(pubKeyBytes, pubKey) return types.NewProposedEntry(ctx, intoto.KIND, intoto_v001.APIVERSION, types.ArtifactProperties{ @@ -155,92 +169,26 @@ func GetRekorPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) { 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, nil) - } - return nil, err - } - // UUID is at the end of location - for _, p := range resp.Payload { - return &p, nil +func rekorEntryHashAlgorithm(checksum crypto.SignerOpts) string { + switch checksum.HashFunc() { + case crypto.SHA256: + return models.HashedrekordV001SchemaDataHashAlgorithmSha256 + case crypto.SHA384: + return models.HashedrekordV001SchemaDataHashAlgorithmSha384 + case crypto.SHA512: + return models.HashedrekordV001SchemaDataHashAlgorithmSha512 + default: + return models.HashedrekordV001SchemaDataHashAlgorithmSha256 } - 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. +func rekorEntry(checksum NamedHash, signature, pubKey []byte) hashedrekord_v001.V001Entry { 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))), + Algorithm: conv.Pointer(rekorEntryHashAlgorithm(checksum)), + Value: conv.Pointer(hex.EncodeToString(checksum.Sum(nil))), }, }, Signature: &models.HashedrekordV001SchemaSignature{ @@ -254,7 +202,11 @@ func rekorEntry(sha256CheckSum hash.Hash, signature, pubKey []byte) hashedrekord } func ComputeLeafHash(e *models.LogEntryAnon) ([]byte, error) { - entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string)) + bodyStr, ok := e.Body.(string) + if !ok { + return nil, fmt.Errorf("invalid body type: expected string, got %T", e.Body) + } + entryBytes, err := base64.StdEncoding.DecodeString(bodyStr) if err != nil { return nil, err } @@ -276,63 +228,6 @@ func getUUID(entryUUID string) (string, error) { } } -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) @@ -352,27 +247,6 @@ func verifyUUID(entryUUID string, e models.LogEntryAnon) error { 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) @@ -393,13 +267,13 @@ func proposedEntries(b64Sig string, payload, pubKey []byte) ([]models.ProposedEn } proposedEntry = []models.ProposedEntry{dsseEntry, intotoEntry} } else { - sha256CheckSum := sha256.New() + sha256CheckSum := NewCryptoNamedHash(crypto.SHA256) if _, err := sha256CheckSum.Write(payload); err != nil { return nil, err } re := rekorEntry(sha256CheckSum, signature, pubKey) entry := &models.Hashedrekord{ - APIVersion: swag.String(re.APIVersion()), + APIVersion: conv.Pointer(re.APIVersion()), Spec: re.HashedRekordObj, } proposedEntry = []models.ProposedEntry{entry} @@ -448,6 +322,9 @@ func VerifyTLogEntryOffline(ctx context.Context, e *models.LogEntryAnon, rekorPu if e.Verification == nil || e.Verification.InclusionProof == nil { return errors.New("inclusion proof not provided") } + if e.Verification.InclusionProof.RootHash == nil || e.Verification.InclusionProof.LogIndex == nil || e.Verification.InclusionProof.TreeSize == nil { + return errors.New("inclusion proof fields not provided") + } if trustedMaterial == nil && (rekorPubKeys == nil || rekorPubKeys.Keys == nil) { return errors.New("no trusted rekor public keys provided") @@ -460,7 +337,11 @@ func VerifyTLogEntryOffline(ctx context.Context, e *models.LogEntryAnon, rekorPu } rootHash, _ := hex.DecodeString(*e.Verification.InclusionProof.RootHash) - entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string)) + bodyStr, ok := e.Body.(string) + if !ok { + return fmt.Errorf("invalid body type: expected string, got %T", e.Body) + } + entryBytes, err := base64.StdEncoding.DecodeString(bodyStr) if err != nil { return err } diff --git a/pkg/cosign/tlog_test.go b/pkg/cosign/tlog_test.go index 16a1abd58cd..3f5fdb80339 100644 --- a/pkg/cosign/tlog_test.go +++ b/pkg/cosign/tlog_test.go @@ -29,7 +29,7 @@ import ( "testing" "time" - "github.com/go-openapi/swag" + "github.com/go-openapi/swag/conv" ttestdata "github.com/google/certificate-transparency-go/trillian/testdata" "github.com/sigstore/rekor/pkg/generated/models" rtypes "github.com/sigstore/rekor/pkg/types" @@ -64,84 +64,6 @@ func TestGetRekorPubKeys(t *testing.T) { } } -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) @@ -214,9 +136,9 @@ func TestVerifyTLogEntryOfflineFailsWithInvalidPublicKey(t *testing.T) { } lea := &models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString(canonicalEntry), - LogIndex: swag.Int64(0), - LogID: swag.String(logID), - IntegratedTime: swag.Int64(time.Now().Unix()), + LogIndex: conv.Pointer(int64(0)), + LogID: conv.Pointer(logID), + IntegratedTime: conv.Pointer(time.Now().Unix()), } entryUUID, err := ComputeLeafHash(lea) if err != nil { @@ -224,9 +146,9 @@ func TestVerifyTLogEntryOfflineFailsWithInvalidPublicKey(t *testing.T) { } lea.Verification = &models.LogEntryAnonVerification{ InclusionProof: &models.InclusionProof{ - LogIndex: swag.Int64(0), - TreeSize: swag.Int64(1), - RootHash: swag.String(hex.EncodeToString(entryUUID)), + LogIndex: conv.Pointer(int64(0)), + TreeSize: conv.Pointer(int64(1)), + RootHash: conv.Pointer(hex.EncodeToString(entryUUID)), Hashes: []string{}, }, } @@ -239,3 +161,154 @@ func TestVerifyTLogEntryOfflineFailsWithInvalidPublicKey(t *testing.T) { t.Fatalf("Did not get expected error message, wanted 'is not type ecdsa.PublicKey' got: %v", err) } } + +func TestComputeLeafHash(t *testing.T) { + payload := []byte("hello") + expected := sha256.Sum256(append([]byte{0}, payload...)) + + tests := []struct { + name string + body interface{} + wantHash []byte + wantErrSubstr string + }{ + { + name: "success", + body: base64.StdEncoding.EncodeToString(payload), + wantHash: expected[:], + }, + { + name: "invalid base64", + body: "%%%", + wantErrSubstr: "illegal base64 data", + }, + { + name: "nil body", + body: nil, + wantErrSubstr: "invalid body type", + }, + { + name: "bytes body", + body: []byte("not a string"), + wantErrSubstr: "invalid body type", + }, + { + name: "object body", + body: map[string]interface{}{"k": "v"}, + wantErrSubstr: "invalid body type", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ComputeLeafHash(&models.LogEntryAnon{Body: tt.body}) + if tt.wantErrSubstr != "" { + if err == nil { + t.Fatalf("expected error, got nil") + } + if !strings.Contains(err.Error(), tt.wantErrSubstr) { + t.Fatalf("unexpected error: %v", err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(got, tt.wantHash) { + t.Fatalf("unexpected hash") + } + }) + } +} + +func TestVerifyTLogEntryOfflineRejectsMalformedEntryWithoutPanic(t *testing.T) { + t.Setenv("TUF_ROOT", t.TempDir()) + + ctx := context.Background() + rekorPubKeys := NewTrustedTransparencyLogPubKeys() + rekorPubKeys.Keys = map[string]TransparencyLogPubKey{} + + tests := []struct { + name string + lea *models.LogEntryAnon + }{ + { + name: "nil root hash", + lea: &models.LogEntryAnon{ + Body: "", + LogIndex: conv.Pointer(int64(0)), + LogID: conv.Pointer(""), + IntegratedTime: conv.Pointer(time.Now().Unix()), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + LogIndex: conv.Pointer(int64(0)), + TreeSize: conv.Pointer(int64(1)), + RootHash: nil, + }, + }, + }, + }, + { + name: "nil log index", + lea: &models.LogEntryAnon{ + Body: "", + LogIndex: conv.Pointer(int64(0)), + LogID: conv.Pointer(""), + IntegratedTime: conv.Pointer(time.Now().Unix()), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + LogIndex: nil, + TreeSize: conv.Pointer(int64(1)), + RootHash: conv.Pointer(""), + }, + }, + }, + }, + { + name: "nil tree size", + lea: &models.LogEntryAnon{ + Body: "", + LogIndex: conv.Pointer(int64(0)), + LogID: conv.Pointer(""), + IntegratedTime: conv.Pointer(time.Now().Unix()), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + LogIndex: conv.Pointer(int64(0)), + TreeSize: nil, + RootHash: conv.Pointer(""), + }, + }, + }, + }, + { + name: "non-string body", + lea: &models.LogEntryAnon{ + Body: []byte("not a string"), + LogIndex: conv.Pointer(int64(0)), + LogID: conv.Pointer(""), + IntegratedTime: conv.Pointer(time.Now().Unix()), + Verification: &models.LogEntryAnonVerification{ + InclusionProof: &models.InclusionProof{ + LogIndex: conv.Pointer(int64(0)), + TreeSize: conv.Pointer(int64(1)), + RootHash: conv.Pointer(""), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("unexpected panic: %v", r) + } + }() + + if err := VerifyTLogEntryOffline(ctx, tt.lea, &rekorPubKeys, nil); err == nil { + t.Fatalf("expected error, got nil") + } + }) + } +} diff --git a/pkg/cosign/tsa.go b/pkg/cosign/tsa.go index c2032f396e8..31021d9566d 100644 --- a/pkg/cosign/tsa.go +++ b/pkg/cosign/tsa.go @@ -21,7 +21,7 @@ import ( "fmt" "os" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/tuf" ) diff --git a/pkg/cosign/tuf.go b/pkg/cosign/tuf.go index 2a7049feec4..6a9c60cccd1 100644 --- a/pkg/cosign/tuf.go +++ b/pkg/cosign/tuf.go @@ -21,7 +21,7 @@ import ( "os" "path/filepath" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/tuf" ) @@ -38,6 +38,34 @@ func TrustedRoot() (root.TrustedMaterial, error) { return tr, nil } +func SigningConfig() (*root.SigningConfig, error) { + opts, err := setTUFOpts() + if err != nil { + return nil, fmt.Errorf("error setting TUF options: %w", err) + } + sc, err := root.FetchSigningConfigWithOptions(opts) + if err != nil { + return nil, fmt.Errorf("error getting signing config from TUF: %w", err) + } + return sc, nil +} + +func SigningConfigRekorV2() (*root.SigningConfig, error) { + opts, err := setTUFOpts() + if err != nil { + return nil, fmt.Errorf("error setting TUF options: %w", err) + } + client, err := tuf.New(opts) + if err != nil { + return nil, fmt.Errorf("error creating TUF client: %w", err) + } + jsonBytes, err := client.GetTarget("signing_config_rekor_v2.v0.2.json") + if err != nil { + return nil, fmt.Errorf("error getting signing config from TUF: %w", err) + } + return root.NewSigningConfigFromJSON(jsonBytes) +} + // setTUFOpts sets the TUF cache directory, the mirror URL, and the root.json in the TUF options. // The cache directory is provided by the user as an environment variable TUF_ROOT, or the default $HOME/.sigstore/root is used. // The mirror URL is provided by the user as an environment variable TUF_MIRROR. If not overridden by the user, the value set during `cosign initialize` in remote.json in the cache directory is used. diff --git a/pkg/cosign/verifiers.go b/pkg/cosign/verifiers.go index 41f82491907..c05de486b10 100644 --- a/pkg/cosign/verifiers.go +++ b/pkg/cosign/verifiers.go @@ -22,10 +22,10 @@ import ( "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/cosign/v3/pkg/cosign/attestation" + "github.com/sigstore/cosign/v3/pkg/oci" "github.com/sigstore/sigstore/pkg/signature/payload" ) @@ -56,7 +56,7 @@ func SimpleClaimVerifier(sig oci.Signature, imageDigest v1.Hash, annotations map } // 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 { +func IntotoSubjectClaimVerifier(sig oci.Signature, imageDigest v1.Hash, annotations map[string]interface{}) error { p, err := sig.Payload() if err != nil { return err @@ -72,8 +72,8 @@ func IntotoSubjectClaimVerifier(sig oci.Signature, imageDigest v1.Hash, _ map[st return err } - st := in_toto.Statement{} - if err := json.Unmarshal(stBytes, &st); err != nil { + st := &attestation.Statement{} + if err := st.UnmarshalJSON(stBytes); err != nil { return err } for _, subj := range st.Subject { @@ -82,9 +82,13 @@ func IntotoSubjectClaimVerifier(sig oci.Signature, imageDigest v1.Hash, _ map[st continue } subjDigest := "sha256:" + dgst - if subjDigest == imageDigest.String() { - return nil + if subjDigest != imageDigest.String() { + continue + } + if !correctAnnotations(annotations, subj.Annotations.AsMap()) { + return errors.New("missing or incorrect annotation") } + return nil } return errors.New("no matching subject digest found") } diff --git a/pkg/cosign/verifiers_test.go b/pkg/cosign/verifiers_test.go index 81633dcbff5..c16f38a1880 100644 --- a/pkg/cosign/verifiers_test.go +++ b/pkg/cosign/verifiers_test.go @@ -18,7 +18,7 @@ import ( "testing" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/pkg/oci/static" + "github.com/sigstore/cosign/v3/pkg/oci/static" ) /* @@ -41,15 +41,33 @@ The following JSON is the payload in valid attestation: } */ +/* payload for a valid attestation with a string as a predicate: +{ + "_type": "https://in-toto.io/Statement/v0.1", + "predicateType": "https://spdx.dev/Document", + "subject": [ + { + "name": "localhost:5000/wolfi-base4", + "digest": { + "sha256": "9925d3017788558fa8f27e8bb160b791e56202b60c91fbcc5c867de3175986c8" + } + } + ], + "predicate": "{\"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\":[]}\n" +} +*/ + 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="}]}` + validIntotoStatementMissingSubject = `{"payloadType":"application/vnd.in-toto+json","payload":"ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImNvc2lnbi5zaWdzdG9yZS5kZXYvYXR0ZXN0YXRpb24vdjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJyZWdpc3RyeS5sb2NhbDo1MDAwL2tuYXRpdmUvZGVtbyIsCiAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgIjk5OSI6ICI2YzZmZDZhNDExNWM2ZTk5OGZmMzU3Y2Q5MTQ2ODA5MzFiYjlhNmMxYTdjZDVmNWNiMmY1ZTFjMDkzMmFiNmVkIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlIjogewogICAgIkRhdGEiOiAiZm9vYmFyIHRlc3QgYXR0ZXN0YXRpb24iLAogICAgIlRpbWVzdGFtcCI6ICIyMDIyLTA0LTA3VDE5OjIyOjI1WiIKICB9Cn0K","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}` + validIntotoStatementStringPredicate = `{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3NwZHguZGV2L0RvY3VtZW50Iiwic3ViamVjdCI6W3sibmFtZSI6ImxvY2FsaG9zdDo1MDAwL3dvbGZpLWJhc2U0IiwiZGlnZXN0Ijp7InNoYTI1NiI6Ijk5MjVkMzAxNzc4ODU1OGZhOGYyN2U4YmIxNjBiNzkxZTU2MjAyYjYwYzkxZmJjYzVjODY3ZGUzMTc1OTg2YzgifX1dLCJwcmVkaWNhdGUiOiJ7XCJzcGR4VmVyc2lvblwiOlwiU1BEWC0yLjJcIixcImRhdGFMaWNlbnNlXCI6XCJDQzAtMS4wXCIsXCJTUERYSURcIjpcIlNQRFhSZWYtRE9DVU1FTlRcIixcIm5hbWVcIjpcIlNCT00tU1BEWC0zNGYxYTdmNS0wM2ZmLTQyNzctOTAyMS04YzA0Zjg3Nzc4MDNcIixcImRvY3VtZW50TmFtZXNwYWNlXCI6XCJodHRwczovL3NwZHgub3JnL3NwZHhkb2NzL2s4cy1yZWxlbmctYm9tLTE2ZjRlMjg4LTZiZGYtNGI4OS1hNzlhLTlmZmQ1NmFkMzNlMFwiLFwiY3JlYXRpb25JbmZvXCI6e1wibGljZW5zZUxpc3RWZXJzaW9uXCI6XCJcIixcImNyZWF0b3JzXCI6W1wiT3JnYW5pemF0aW9uOiBLdWJlcm5ldGVzIFJlbGVhc2UgRW5naW5lZXJpbmdcIixcIlRvb2w6IHNpZ3MuazhzLmlvL2JvbS9wa2cvc3BkeFwiXSxcImNyZWF0ZWRcIjpcIjIwMjItMDYtMDdUMjI6MTQ6NTZaXCIsXCJjb21tZW50XCI6XCJcIn0sXCJwYWNrYWdlc1wiOltdfVxuIn0=","signatures":[{"keyid":"","sig":"MEUCIQC/slGQVpRKgw4Jo8tcbgo85WNG/FOJfxcvQFvTEnG9swIgP4LeOmID+biUNwLLeylBQpAEgeV6GVcEpyG6r8LVnfY="}]}` ) var validDigest = v1.Hash{Algorithm: "sha256", Hex: "6c6fd6a4115c6e998ff357cd914680931bb9a6c1a7cd5f5cb2f5e1c0932ab6ed"} var invalidDigest = v1.Hash{Algorithm: "sha256", Hex: "6c6fd6a4115c6e998ff357cd914680931bb9a6c1a7cd5f5cb2f5e1c0932xxxxx"} +var validDigestStringPredicate = v1.Hash{Algorithm: "sha256", Hex: "9925d3017788558fa8f27e8bb160b791e56202b60c91fbcc5c867de3175986c8"} func Test_IntotoSubjectClaimVerifier(t *testing.T) { tests := []struct { @@ -63,6 +81,7 @@ func Test_IntotoSubjectClaimVerifier(t *testing.T) { {payload: validIntotoStatement, digest: invalidDigest, shouldFail: true}, {payload: validIntotoStatementMissingSubject, digest: validDigest, shouldFail: true}, {payload: validIntotoStatement, digest: validDigest, shouldFail: false}, + {payload: validIntotoStatementStringPredicate, digest: validDigestStringPredicate, shouldFail: false}, } for _, tc := range tests { ociSig, err := static.NewSignature([]byte(tc.payload), "") diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 00206560992..56f1599a82f 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -32,6 +32,7 @@ import ( "log" "net/http" "os" + "path/filepath" "regexp" "strings" "time" @@ -41,19 +42,20 @@ import ( "github.com/go-openapi/runtime" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + ggcrlayout "github.com/google/go-containerregistry/pkg/v1/layout" "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" + "github.com/sigstore/cosign/v3/internal/pkg/cosign" + ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote" + "github.com/sigstore/cosign/v3/internal/ui" + "github.com/sigstore/cosign/v3/pkg/blob" + cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/layout" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "github.com/sigstore/cosign/v3/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" @@ -73,7 +75,7 @@ import ( "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" + tsaverification "github.com/sigstore/timestamp-authority/v2/pkg/verification" ) // Identity specifies an issuer/subject to verify a signature against. @@ -190,8 +192,8 @@ func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstra // 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") + if co.TrustedMaterial == nil && co.SigVerifier == nil { + return nil, nil, nil, fmt.Errorf("a trusted root is required for identity-based verification") } policyOptions = make([]verify.PolicyOption, 0) @@ -247,13 +249,29 @@ func (co *CheckOpts) verificationOptions() (trustedMaterial root.TrustedMaterial } if !co.IgnoreTlog { - verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1)) + verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1)) + // If you aren't using a signed timestamp, use the time from the transparency log + // to verify Fulcio certificates, or require no timestamp to verify a key. + // For Rekor v2, a signed timestamp must be provided. + if !co.UseSignedTimestamps { + if co.SigVerifier == nil { + verifierOptions = append(verifierOptions, verify.WithIntegratedTimestamps(1)) + } else { + verifierOptions = append(verifierOptions, verify.WithNoObserverTimestamps()) + } + } } if co.UseSignedTimestamps { verifierOptions = append(verifierOptions, verify.WithSignedTimestamps(1)) } + // A time verification policy must be provided. Without a signed timestamp or integrated timestamp, + // verify a certificate with the current time, or require no timestamp to verify a key. if co.IgnoreTlog && !co.UseSignedTimestamps { - verifierOptions = append(verifierOptions, verify.WithCurrentTime()) + if co.SigVerifier == nil { + verifierOptions = append(verifierOptions, verify.WithCurrentTime()) + } else { + verifierOptions = append(verifierOptions, verify.WithNoObserverTimestamps()) + } } return vTrustedMaterial, verifierOptions, policyOptions, nil @@ -311,20 +329,36 @@ func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig pa return verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx)) } +type verifierWithCertChain struct { + signature.Verifier + cert *x509.Certificate + chain []*x509.Certificate +} + +func (v *verifierWithCertChain) GetCert() *x509.Certificate { + return v.cert +} + +func (v *verifierWithCertChain) GetChain() []*x509.Certificate { + return v.chain +} + // 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) + verifier, _, err := ValidateAndUnpackCertWithIntermediates(cert, co, co.IntermediateCerts) + return verifier, err } // 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) { +// Optionally verifies the subject and issuer of the certificate. Returns the chain built from the +// certificate pools. Clients must verify the validity of this chain against a provided timestamp. +func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpts, intermediateCerts *x509.CertPool) (signature.Verifier, []*x509.Certificate, error) { verifier, err := signature.LoadVerifier(cert.PublicKey, crypto.SHA256) if err != nil { - return nil, fmt.Errorf("invalid certificate found on signature: %w", err) + return nil, nil, fmt.Errorf("invalid certificate found on signature: %w", err) } // Handle certificates where the Subject Alternative Name is not set to a supported @@ -347,31 +381,37 @@ func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpt var chains [][]*x509.Certificate if co.TrustedMaterial != nil { if chains, err = verify.VerifyLeafCertificate(cert.NotBefore, cert, co.TrustedMaterial); err != nil { - return nil, err + return nil, nil, err } } else { // If the trusted root is not available, use the verifiers from cosign (legacy). chains, err = TrustedCert(cert, co.RootCerts, intermediateCerts) if err != nil { - return nil, err + return nil, nil, err } } + // 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 for further verification.\n") + } + chain := chains[0] + err = CheckCertificatePolicy(cert, co) if err != nil { - return nil, err + return nil, nil, err } // If IgnoreSCT is set, skip the SCT check if co.IgnoreSCT { - return verifier, nil + return &verifierWithCertChain{Verifier: verifier, cert: cert, chain: chains[0]}, chains[0], nil } contains, err := ContainsSCT(cert.Raw) if err != nil { - return nil, err + return nil, nil, err } if !contains && len(co.SCT) == 0 { - return nil, &VerificationFailure{ + return nil, nil, &VerificationFailure{ fmt.Errorf("certificate does not include required embedded SCT and no detached SCT was set"), } } @@ -379,38 +419,33 @@ func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpt // If trusted root is available and the SCT is embedded, use the verifiers from sigstore-go (preferred). if co.TrustedMaterial != nil && contains { if err := verify.VerifySignedCertificateTimestamp(chains, 1, co.TrustedMaterial); err != nil { - return nil, err + return nil, nil, err } - return verifier, nil + return &verifierWithCertChain{Verifier: verifier, cert: cert, chain: chain}, chain, nil } - // 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 len(chain) < 2 { + return nil, nil, errors.New("certificate chain must contain at least a certificate and its issuer") } if contains { - if err := VerifyEmbeddedSCT(context.Background(), chains[0], co.CTLogPubKeys); err != nil { - return nil, err + if err := VerifyEmbeddedSCT(context.Background(), chain, co.CTLogPubKeys); err != nil { + return nil, nil, err } - return verifier, nil - } - chain := chains[0] - if len(chain) < 2 { - return nil, errors.New("certificate chain must contain at least a certificate and its issuer") + return &verifierWithCertChain{Verifier: verifier, cert: cert, chain: chain}, chain, nil } certPEM, err := cryptoutils.MarshalCertificateToPEM(chain[0]) if err != nil { - return nil, err + return nil, nil, err } chainPEM, err := cryptoutils.MarshalCertificatesToPEM(chain[1:]) if err != nil { - return nil, err + return nil, nil, err } if err := VerifySCT(context.Background(), certPEM, chainPEM, co.SCT, co.CTLogPubKeys); err != nil { - return nil, err + return nil, nil, err } - return verifier, nil + return &verifierWithCertChain{Verifier: verifier, cert: cert, chain: chain}, chain, nil } // CheckCertificatePolicy checks that the certificate subject and issuer match @@ -834,6 +869,7 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, } verifier := co.SigVerifier + var verifierChain []*x509.Certificate if verifier == nil { // If we don't have a public key to check against, we can try a root cert. cert, err := sig.Cert() @@ -865,10 +901,12 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, if pool == nil { pool = co.IntermediateCerts } - verifier, err = ValidateAndUnpackCertWithIntermediates(cert, co, pool) + verifier, chain, err = ValidateAndUnpackCertWithIntermediates(cert, co, pool) if err != nil { return false, err } + // Remove the end-entity certificate from the chain, as that will come from sig.Cert() + verifierChain = chain[1:] } // 1. Perform cryptographic verification of the signature using the certificate's public key. @@ -894,14 +932,14 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, if acceptableRFC3161Time != nil { // Verify the cert against the timestamp time. - if err := CheckExpiry(cert, *acceptableRFC3161Time); err != nil { + if err := CheckExpiry(cert, verifierChain, *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 { + if err := CheckExpiry(cert, verifierChain, *acceptableRekorBundleTime); err != nil { return false, fmt.Errorf("checking expiry on certificate with bundle: %w", err) } expirationChecked = true @@ -909,7 +947,7 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, // if no timestamp has been provided, use the current time if !expirationChecked { - if err := CheckExpiry(cert, time.Now()); err != nil { + if err := CheckExpiry(cert, verifierChain, 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{ @@ -929,15 +967,23 @@ func keyBytes(sig oci.Signature, co *CheckOpts) ([]byte, error) { if err != nil { return nil, err } - // We have a public key. + var pub crypto.PublicKey if co.SigVerifier != nil { - pub, err := co.SigVerifier.PublicKey(co.PKOpts...) + pub, err = co.SigVerifier.PublicKey(co.PKOpts...) if err != nil { return nil, err } - return cryptoutils.MarshalPublicKeyToPEM(pub) } - return cryptoutils.MarshalCertificateToPEM(cert) + if cert != nil && co.SigVerifier != nil { + if err := cryptoutils.EqualKeys(cert.PublicKey, pub); err != nil { + return nil, fmt.Errorf("both public key and certificate were provided but did not match") + } + } + + if cert != nil { + return cryptoutils.MarshalCertificateToPEM(cert) + } + return cryptoutils.MarshalPublicKeyToPEM(pub) } // VerifyBlobSignature verifies a blob signature. @@ -986,7 +1032,33 @@ func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name } } - sig, err := static.NewSignature(payload, b64sig) + var opts []static.Option + if co.SigVerifier != nil { + if cb, ok := co.SigVerifier.(interface{ GetCert() *x509.Certificate }); ok { + if cert := cb.GetCert(); cert != nil { + var chain []*x509.Certificate + if ch, ok := co.SigVerifier.(interface{ GetChain() []*x509.Certificate }); ok { + chain = ch.GetChain() + } + + certPEM, err := cryptoutils.MarshalCertificateToPEM(cert) + if err != nil { + return nil, err + } + var chainPEM []byte + if len(chain) > 0 { + chainPEM, err = cryptoutils.MarshalCertificatesToPEM(chain) + if err != nil { + return nil, err + } + } + + opts = append(opts, static.WithCertChain(certPEM, chainPEM)) + } + } + } + + sig, err := static.NewSignature(payload, b64sig, opts...) if err != nil { return nil, err } @@ -997,13 +1069,13 @@ func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name // 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) { +func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) (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) + return verifyImageAttestationsSigstoreBundle(ctx, signedImgRef, co, nameOpts...) } // This is a carefully optimized sequence for fetching the attestations of @@ -1038,6 +1110,11 @@ func VerifyLocalImageAttestations(ctx context.Context, path string, co *CheckOpt return nil, false, errors.New("one of verifier, root certs, or trusted root is required") } + // Check for v3 bundles first (if NewBundleFormat is enabled) + if co.NewBundleFormat { + return verifyLocalImageAttestationsSigstoreBundle(ctx, path, co) + } + se, err := layout.SignedImageIndex(path) if err != nil { return nil, false, err @@ -1081,6 +1158,9 @@ func VerifyBlobAttestation(ctx context.Context, att oci.Signature, h v1.Hash, co } func VerifyImageAttestation(ctx context.Context, atts oci.Signatures, h v1.Hash, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { + if atts == nil { + return nil, false, errors.New("no attestations provided") + } sl, err := atts.Get() if err != nil { return nil, false, err @@ -1142,21 +1222,36 @@ func VerifyImageAttestation(ctx context.Context, atts oci.Signatures, h v1.Hash, 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 { +// CheckExpiry confirms the time provided is within the valid period of the certificate and optionally +// all issuing CA certificates. +func CheckExpiry(cert *x509.Certificate, issuingChain []*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", + fmt.Errorf("certificate expired before observed time: %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)), + fmt.Errorf("certificate was issued after observed time: %s is after %s", + ft(cert.NotBefore), ft(it)), + } + } + for _, c := range issuingChain { + if c.NotAfter.Before(it) { + return &VerificationFailure{ + fmt.Errorf("issuing CA certificate expired before observed time: %s is before %s", + ft(c.NotAfter), ft(it)), + } + } + if c.NotBefore.After(it) { + return &VerificationFailure{ + fmt.Errorf("issuing CA certificate was issued after observed time: %s is after %s", + ft(c.NotBefore), ft(it)), + } } } return nil @@ -1186,13 +1281,48 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) { return false, errors.New("no trusted rekor public keys provided") } + bundleBody, ok := bundle.Payload.Body.(string) + if !ok { + return false, errors.New("bundle payload body is not a string") + } + + if err := compareSigs(bundleBody, sig); err != nil { + return false, err + } + + if err := comparePublicKey(bundleBody, sig, co); err != nil { + return false, err + } + + 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(bundleBody, 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) + } + if co.TrustedMaterial != nil { payload := bundle.Payload logID, err := hex.DecodeString(payload.LogID) if err != nil { return false, fmt.Errorf("decoding log ID: %w", err) } - body, _ := base64.StdEncoding.DecodeString(payload.Body.(string)) + body, _ := base64.StdEncoding.DecodeString(bundleBody) entry, err := tlog.NewEntry(body, payload.IntegratedTime, payload.LogIndex, logID, bundle.SignedEntryTimestamp, nil) if err != nil { return false, fmt.Errorf("converting tlog entry: %w", err) @@ -1200,6 +1330,7 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) { if err := tlog.VerifySET(entry, co.TrustedMaterial.RekorLogs()); err != nil { return false, fmt.Errorf("verifying bundle with trusted root: %w", err) } + return true, nil } // Make sure all the rekorPubKeys are ecsda.PublicKeys @@ -1209,14 +1340,6 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) { } } - 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{ @@ -1231,27 +1354,6 @@ func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) { 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 } @@ -1293,6 +1395,9 @@ func (s *sigContent) MessageSignatureContent() verify.MessageSignatureContent { // 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. +// +// Note: This function does not perform CRL/OCSP certificate revocation checks. +// Callers are responsible for validating the TSA certificate and trusted material provided via CheckOpts according to their policy. func VerifyRFC3161Timestamp(sig oci.Signature, co *CheckOpts) (*timestamp.Timestamp, error) { ts, err := sig.RFC3161Timestamp() switch { @@ -1338,6 +1443,9 @@ func VerifyRFC3161Timestamp(sig oci.Signature, co *CheckOpts) (*timestamp.Timest if len(verifyErrs) > 0 { log.Printf("Warning: subset of signed timestamps failed to verify: %v", verifyErrs) } + if len(verifiedTimestamps) == 0 { + return nil, fmt.Errorf("expected at least one verified timestamp") + } return ×tamp.Timestamp{Time: verifiedTimestamps[0].Time}, nil } @@ -1602,10 +1710,10 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name return verifySignatures(ctx, sigs, h, co) } -func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) { +func GetBundles(_ context.Context, signedImgRef name.Reference, registryClientOpts []ociremote.Option, nameOpts ...name.Option) ([]*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...) + digest, err := ociremote.ResolveDigest(signedImgRef, registryClientOpts...) if err != nil { if terr := (&transport.Error{}); errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound { return nil, nil, &ErrImageTagNotFound{ @@ -1619,17 +1727,23 @@ func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ( return nil, nil, err } - index, err := ociremote.Referrers(digest, "", co.RegistryClientOpts...) + index, err := ociremote.Referrers(digest, "", registryClientOpts...) if err != nil { return nil, nil, err } + + bundleRepo := digest.Repository + if targetRepo := ociremote.TargetRepositoryFromOptions(registryClientOpts...); (targetRepo != name.Repository{}) { + bundleRepo = targetRepo + } + 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())) + st, err := name.ParseReference(fmt.Sprintf("%s@%s", bundleRepo, result.Digest.String()), nameOpts...) if err != nil { return nil, nil, err } - bundle, err := ociremote.Bundle(st, co.RegistryClientOpts...) + bundle, err := ociremote.Bundle(st, 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)? @@ -1647,9 +1761,149 @@ func getBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ( return bundles, &h, nil } +// bundleDescriptor holds the digest and path to a bundle blob in a local OCI layout +type bundleDescriptor struct { + digest v1.Hash + blobPath string +} + +// HasLocalBundles checks if a local OCI layout has v3 sigstore bundles. +// V3 bundles are stored as separate images with layers having +// media type "application/vnd.dev.sigstore.bundle". +func HasLocalBundles(path string) (bool, error) { + return hasLocalSigstoreBundles(path) +} + +// HasLocalAttestationBundles checks if a local OCI layout has v3 sigstore bundles for attestations. +// For v3, both signatures and attestations use the same bundle format. +func HasLocalAttestationBundles(path string) (bool, error) { + return hasLocalSigstoreBundles(path) +} + +// GetLocalBundles retrieves v3 sigstore bundles from a local OCI layout. +// Returns bundles, target image hash, and error. Invalid bundles are logged and skipped. +func GetLocalBundles(path string) ([]*sgbundle.Bundle, *v1.Hash, error) { + descriptors, hash, err := getLocalBundleDescriptors(path) + if err != nil { + return nil, nil, err + } + + bundles := make([]*sgbundle.Bundle, 0, len(descriptors)) + for _, descriptor := range descriptors { + bundleBytes, err := os.ReadFile(filepath.Join(path, descriptor.blobPath)) + if err != nil { + ui.Warnf(context.Background(), "Failed to read bundle blob %s: %v", descriptor.digest.Hex, err) + continue + } + + bundle := &sgbundle.Bundle{} + if err := bundle.UnmarshalJSON(bundleBytes); err != nil { + ui.Warnf(context.Background(), "Failed to unmarshal bundle %s: %v", descriptor.digest.Hex, err) + continue + } + + if !bundle.MinVersion("v0.3") { + ui.Warnf(context.Background(), "Bundle %s version too old (requires v0.3+)", descriptor.digest.Hex) + continue + } + + bundles = append(bundles, bundle) + } + + if len(bundles) == 0 { + return nil, nil, &ErrNoMatchingAttestations{ + fmt.Errorf("no valid bundles found in local layout"), + } + } + + return bundles, hash, nil +} + +func hasLocalSigstoreBundles(path string) (bool, error) { + descriptors, _, err := getLocalBundleDescriptors(path) + if err != nil { + return false, err + } + return len(descriptors) > 0, nil +} + +func getLocalBundleDescriptors(path string) ([]bundleDescriptor, *v1.Hash, error) { + p, err := ggcrlayout.FromPath(path) + if err != nil { + return nil, nil, fmt.Errorf("loading OCI layout from %s: %w", path, err) + } + + ii, err := p.ImageIndex() + if err != nil { + return nil, nil, fmt.Errorf("getting image index: %w", err) + } + + manifest, err := ii.IndexManifest() + if err != nil { + return nil, nil, fmt.Errorf("getting index manifest: %w", err) + } + + // Find the target image digest from the index manifest + var targetDigest v1.Hash + for _, m := range manifest.Manifests { + if val, ok := m.Annotations["kind"]; ok && val == "dev.cosignproject.cosign/image" { + targetDigest = m.Digest + break + } + } + if targetDigest.String() == "" { + return nil, nil, nil + } + + // Scan blobs/sha256 directory for referrer manifests + blobsDir := filepath.Join(path, "blobs", "sha256") + entries, err := os.ReadDir(blobsDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, nil, nil + } + return nil, nil, fmt.Errorf("reading blobs directory: %w", err) + } + + var descriptors []bundleDescriptor + for _, entry := range entries { + if entry.IsDir() { + continue + } + blobPath := filepath.Join(blobsDir, entry.Name()) + data, err := os.ReadFile(blobPath) + if err != nil { + continue + } + + // Try to parse as manifest (skip blobs that aren't manifests) + blobManifest, err := v1.ParseManifest(bytes.NewReader(data)) + if err != nil { + continue + } + + // Check if this is a referrer manifest pointing to our target + if blobManifest.Subject != nil && blobManifest.Subject.Digest == targetDigest { + // Collect bundle layer descriptors from this referrer manifest + for _, layer := range blobManifest.Layers { + if strings.HasPrefix(string(layer.MediaType), "application/vnd.dev.sigstore.bundle") { + // layer.Digest.Hex is validated by go-containerregistry to be a valid hex hash, + // but use filepath.Clean for defense-in-depth against path traversal + descriptors = append(descriptors, bundleDescriptor{ + digest: layer.Digest, + blobPath: filepath.Clean(filepath.Join("blobs", "sha256", layer.Digest.Hex)), + }) + } + } + } + } + + return descriptors, &targetDigest, 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) +func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) (checkedAttestations []oci.Signature, atLeastOneBundleVerified bool, err error) { + bundles, hash, err := GetBundles(ctx, signedImgRef, co.RegistryClientOpts, nameOpts...) if err != nil { return nil, false, err } @@ -1697,6 +1951,11 @@ func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef nam if err != nil { return err } + if co.ClaimVerifier != nil { + if err := co.ClaimVerifier(att, *hash, co.Annotations); err != nil { + return err + } + } bundlesVerified[index] = true return err @@ -1731,3 +1990,72 @@ func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef nam return checkedAttestations, atLeastOneBundleVerified, nil } + +// verifyLocalImageAttestationsSigstoreBundle verifies attestations from local sigstore bundles +func verifyLocalImageAttestationsSigstoreBundle(ctx context.Context, path string, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { + bundles, hash, err := GetLocalBundles(path) + 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) + + // For local bundles, we verify sequentially (local I/O is fast, no need for parallel throttler) + var atLeastOneBundleVerified bool + var errs []error + for _, bundle := range bundles { + _, err := VerifyNewBundle(ctx, co, artifactPolicyOption, bundle) + if err != nil { + // Log error and accumulate for final error message + errs = append(errs, err) + ui.Warnf(ctx, "Failed to verify bundle: %v", err) + continue + } + + dsse, ok := bundle.Content.(*protobundle.Bundle_DsseEnvelope) + if !ok { + err := fmt.Errorf("bundle does not contain a DSSE envelope") + errs = append(errs, err) + ui.Warnf(ctx, "%v", err) + continue + } + + payload, err := json.Marshal(dsse.DsseEnvelope) + if err != nil { + errs = append(errs, fmt.Errorf("marshaling DSSE envelope: %w", err)) + ui.Warnf(ctx, "Failed to marshal DSSE envelope: %v", err) + continue + } + + att, err := static.NewAttestation(payload) + if err != nil { + errs = append(errs, fmt.Errorf("creating attestation: %w", err)) + ui.Warnf(ctx, "Failed to create attestation: %v", err) + continue + } + + if co.ClaimVerifier != nil { + if err := co.ClaimVerifier(att, *hash, co.Annotations); err != nil { + errs = append(errs, fmt.Errorf("claim verification: %w", err)) + ui.Warnf(ctx, "Claim verification failed: %v", err) + continue + } + } + + checkedAttestations = append(checkedAttestations, att) + atLeastOneBundleVerified = true + } + + if len(checkedAttestations) == 0 { + return nil, false, &ErrNoMatchingAttestations{ + fmt.Errorf("no matching attestations: %w", errors.Join(errs...)), + } + } + + return checkedAttestations, atLeastOneBundleVerified, nil +} diff --git a/pkg/cosign/verify_bundle.go b/pkg/cosign/verify_bundle.go index 9c6daf88140..81eadb7d2e3 100644 --- a/pkg/cosign/verify_bundle.go +++ b/pkg/cosign/verify_bundle.go @@ -21,9 +21,17 @@ import ( "github.com/sigstore/sigstore-go/pkg/verify" ) -// VerifyNewBundle verifies a SigstoreBundle with the given parameters +// VerifyNewBundle verifies a Sigstore bundle 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() + // Copy co so rekorV2Bundle's UseSignedTimestamps write stays per-call and + // doesn't race a *CheckOpts shared across goroutines. + // TODO(cody)(cosign v4): Consider changing function signature to take a + // non-pointer CheckOpts and avoid this copy. + coCopy := *co + if err := rekorV2Bundle(bundle, &coCopy); err != nil { + return nil, err + } + trustedMaterial, verifierOptions, policyOptions, err := coCopy.verificationOptions() if err != nil { return nil, err } @@ -33,3 +41,41 @@ func VerifyNewBundle(_ context.Context, co *CheckOpts, artifactPolicyOption veri } return verifier.Verify(bundle, verify.NewPolicy(artifactPolicyOption, policyOptions...)) } + +// rekorV2Bundle checks if a bundle contains only Rekor v2 entries, and if so, mandates that +// a signed timestamp is provided when verifying a certificate. Unlike Rekor v1, Rekor v2 does +// not provide timestamps, and so when verifying a short-lived certificates, users either explicitly +// need to specify --use-signed-timestamps, or we'll opportunistically set it here. +// This check does nothing if users skip transparency log verification or provide a key, since +// a trusted timestamp is unneeded. +// If a bundle were to contain both a Rekor v1 and Rekor v2 entry, but with trust material that +// will only successfully verify the Rekor v1 entry, this would cause a verification failure. +// Therefore, if we have a mixed bundle, we will do nothing and require that the user explicitly +// opt in to checking for signed timestamps. +// There is one edge case that isn't handled - A bundle with a Rekor v2 entry with a certificate +// that should be validated using the current time rather than a provided timestamp. If someone runs +// into this, we can add a flag like --use-current-time. +func rekorV2Bundle(bundle verify.SignedEntity, co *CheckOpts) error { + // Without a transparency log entry, users will need to opt in to verifying timestamps. + // With a key, there's no need for timestamps. + if co.IgnoreTlog || co.SigVerifier != nil { + return nil + } + logEntries, err := bundle.TlogEntries() + if err != nil { + return err + } + var hasRekorV1, hasRekorV2 bool + for _, logEntry := range logEntries { + // Rekor v2 entries do not specify an integrated time + if logEntry.IntegratedTime().IsZero() { + hasRekorV2 = true + } else { + hasRekorV1 = true + } + } + if hasRekorV2 && !hasRekorV1 { + co.UseSignedTimestamps = true + } + return nil +} diff --git a/pkg/cosign/verify_bundle_test.go b/pkg/cosign/verify_bundle_test.go index a1c705c0362..53a5cf6ad53 100644 --- a/pkg/cosign/verify_bundle_test.go +++ b/pkg/cosign/verify_bundle_test.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cosign_test +package cosign import ( "bytes" @@ -25,11 +25,13 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "io" + "sync" "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" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/testing/ca" "github.com/sigstore/sigstore-go/pkg/tlog" @@ -43,6 +45,7 @@ type bundleMutator struct { eraseTSA bool eraseTlog bool + eraseSET bool } func (b *bundleMutator) Timestamps() ([][]byte, error) { @@ -56,6 +59,21 @@ func (b *bundleMutator) TlogEntries() ([]*tlog.Entry, error) { if b.eraseTlog { return []*tlog.Entry{}, nil } + if b.eraseSET { + var entries []*tlog.Entry + oldEntries, err := b.SignedEntity.TlogEntries() + if err != nil { + return nil, err + } + for _, entry := range oldEntries { + mutEntry, err := tlog.NewEntry([]byte(entry.Body().(string)), entry.IntegratedTime().Unix(), entry.LogIndex(), []byte(entry.LogKeyID()), []byte{}, nil) + if err != nil { + return nil, err + } + entries = append(entries, mutEntry) + } + return entries, nil + } return b.SignedEntity.TlogEntries() } @@ -73,7 +91,7 @@ func TestVerifyBundle(t *testing.T) { identity := "foo@example.com" issuer := "example issuer" - standardIdentities := []cosign.Identity{ + standardIdentities := []Identity{ { Issuer: issuer, Subject: identity, @@ -92,14 +110,14 @@ func TestVerifyBundle(t *testing.T) { for _, tc := range []struct { name string - checkOpts *cosign.CheckOpts + checkOpts *CheckOpts artifactPolicyOption verify.ArtifactPolicyOption entity verify.SignedEntity wantErr bool }{ { name: "valid", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, @@ -111,7 +129,7 @@ func TestVerifyBundle(t *testing.T) { }, { name: "valid blob signature", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, @@ -123,7 +141,7 @@ func TestVerifyBundle(t *testing.T) { }, { name: "invalid, wrong artifact", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, @@ -135,7 +153,7 @@ func TestVerifyBundle(t *testing.T) { }, { name: "invalid blob signature, wrong artifact", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, @@ -147,8 +165,8 @@ func TestVerifyBundle(t *testing.T) { }, { name: "valid, pattern match issuer", - checkOpts: &cosign.CheckOpts{ - Identities: []cosign.Identity{ + checkOpts: &CheckOpts{ + Identities: []Identity{ { IssuerRegExp: ".*issuer", Subject: "foo@example.com", @@ -164,8 +182,8 @@ func TestVerifyBundle(t *testing.T) { }, { name: "valid, pattern match subject", - checkOpts: &cosign.CheckOpts{ - Identities: []cosign.Identity{ + checkOpts: &CheckOpts{ + Identities: []Identity{ { Issuer: "example issuer", SubjectRegExp: ".*@example.com", @@ -181,8 +199,8 @@ func TestVerifyBundle(t *testing.T) { }, { name: "invalid, pattern match issuer", - checkOpts: &cosign.CheckOpts{ - Identities: []cosign.Identity{ + checkOpts: &CheckOpts{ + Identities: []Identity{ { IssuerRegExp: ".* not my issuer", Subject: "foo@example.com", @@ -198,8 +216,8 @@ func TestVerifyBundle(t *testing.T) { }, { name: "invalid, pattern match subject", - checkOpts: &cosign.CheckOpts{ - Identities: []cosign.Identity{ + checkOpts: &CheckOpts{ + Identities: []Identity{ { Issuer: "example issuer", SubjectRegExp: ".*@otherexample.com", @@ -215,7 +233,7 @@ func TestVerifyBundle(t *testing.T) { }, { name: "invalid trusted material", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, TrustedMaterial: virtualSigstore2, @@ -226,7 +244,7 @@ func TestVerifyBundle(t *testing.T) { }, { name: "do not require tlog, missing tlog", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, IgnoreTlog: true, @@ -239,7 +257,7 @@ func TestVerifyBundle(t *testing.T) { }, { name: "do not require tsa, missing tsa", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, IgnoreTlog: false, @@ -252,7 +270,7 @@ func TestVerifyBundle(t *testing.T) { }, { name: "require tlog, missing tlog", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, @@ -262,9 +280,22 @@ func TestVerifyBundle(t *testing.T) { entity: &bundleMutator{SignedEntity: attestation, eraseTlog: true}, wantErr: true, }, + { + name: "require SET, missing set", + checkOpts: &CheckOpts{ + Identities: standardIdentities, + IgnoreSCT: true, + IgnoreTlog: false, + UseSignedTimestamps: false, // both set to false requires an SET + TrustedMaterial: virtualSigstore, + }, + artifactPolicyOption: verify.WithArtifact(bytes.NewReader(artifact)), + entity: &bundleMutator{SignedEntity: attestation, eraseSET: true}, + wantErr: true, + }, { name: "require tsa, missing tsa", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ Identities: standardIdentities, IgnoreSCT: true, UseSignedTimestamps: true, @@ -276,7 +307,7 @@ func TestVerifyBundle(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - _, err = cosign.VerifyNewBundle(context.Background(), tc.checkOpts, tc.artifactPolicyOption, tc.entity) + _, err = VerifyNewBundle(context.Background(), tc.checkOpts, tc.artifactPolicyOption, tc.entity) if tc.wantErr { assert.Error(t, err) } else { @@ -333,14 +364,14 @@ func TestVerifyBundleWithSigVerifier(t *testing.T) { for _, tc := range []struct { name string - checkOpts *cosign.CheckOpts + checkOpts *CheckOpts artifactPolicyOption verify.ArtifactPolicyOption entity verify.SignedEntity wantErr bool }{ { name: "valid", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ UseSignedTimestamps: true, IgnoreTlog: true, TrustedMaterial: virtualSigstore, @@ -352,7 +383,7 @@ func TestVerifyBundleWithSigVerifier(t *testing.T) { }, { name: "invalid, wrong artifact", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ UseSignedTimestamps: true, IgnoreTlog: true, TrustedMaterial: virtualSigstore, @@ -364,7 +395,7 @@ func TestVerifyBundleWithSigVerifier(t *testing.T) { }, { name: "invalid, sigverifier not set", - checkOpts: &cosign.CheckOpts{ + checkOpts: &CheckOpts{ UseSignedTimestamps: true, IgnoreTlog: true, TrustedMaterial: virtualSigstore, @@ -375,7 +406,7 @@ func TestVerifyBundleWithSigVerifier(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - _, err = cosign.VerifyNewBundle(context.Background(), tc.checkOpts, tc.artifactPolicyOption, tc.entity) + _, err = VerifyNewBundle(context.Background(), tc.checkOpts, tc.artifactPolicyOption, tc.entity) if tc.wantErr { assert.Error(t, err) } else { @@ -384,3 +415,180 @@ func TestVerifyBundleWithSigVerifier(t *testing.T) { }) } } + +type mockSignedEntity struct { + verify.SignedEntity + tlogEntries []*tlog.Entry +} + +func (m *mockSignedEntity) TlogEntries() ([]*tlog.Entry, error) { + return m.tlogEntries, nil +} + +type mockVerifierForBundle struct{} + +func (m *mockVerifierForBundle) PublicKey(_ ...signature.PublicKeyOption) (crypto.PublicKey, error) { + return nil, nil +} + +func (m *mockVerifierForBundle) VerifySignature(_, _ io.Reader, _ ...signature.VerifyOption) error { + return nil +} + +func makeTlogEntry(t *testing.T, integratedTime int64) *tlog.Entry { + body := []byte(`{ + "kind": "hashedrekord", + "apiVersion": "0.0.1", + "spec": { + "signature": { + "content": "MEQCIFrwIdVX8n5RM+Fy9fgCmaBc20jmksfL0XL08y1zx3XpAiB95HkXz37kTUzdykwuNStwCc5B9NKHtioD+3GYMuWU/w==", + "publicKey": {"content": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFNHlQQ080MStjeGxEdENBUndTNDNvQU1YVWs3NApyWGZ5eGhKSldJZ05KbTUyTlppZllHaDNnYzNaakJVOVJhRXJLb0NidGVxdW1IWU9CSnN6RmNIUGFBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="} + }, + "data": { + "hash": {"algorithm": "sha256", "value": "0c0f699f002f4de2ab43b04a0c930ceb57be35d2c81b31648d88b021713e9477"} + } + } + }`) + tle := &v1.TransparencyLogEntry{ + LogIndex: 1, + LogId: &protocommon.LogId{ + KeyId: []byte("ignored"), + }, + KindVersion: &v1.KindVersion{ + Kind: "ignored", + Version: "ignored", + }, + IntegratedTime: integratedTime, + CanonicalizedBody: body, + } + entry, err := tlog.NewTlogEntry(tle) + if err != nil { + t.Fatal(err) + } + return entry +} + +// rekorV2Entity wraps a real SignedEntity but reports caller-supplied tlog +// entries, letting a test force the Rekor v2 code path (an entry with no +// integrated time) that makes rekorV2Bundle write co.UseSignedTimestamps. +type rekorV2Entity struct { + verify.SignedEntity + entries []*tlog.Entry +} + +func (e *rekorV2Entity) TlogEntries() ([]*tlog.Entry, error) { + return e.entries, nil +} + +// TestVerifyNewBundleConcurrentNoDataRace guards against the data race where the +// attestation verification fan-out shares one *CheckOpts across goroutines: +// VerifyNewBundle -> rekorV2Bundle writes co.UseSignedTimestamps for a Rekor v2 +// bundle while sibling goroutines read it in co.verificationOptions(). Run with +// -race; without VerifyNewBundle copying co, the detector fires. +func TestVerifyNewBundleConcurrentNoDataRace(t *testing.T) { + virtualSigstore, err := ca.NewVirtualSigstore() + if err != nil { + t.Fatal(err) + } + + artifact := []byte("artifact") + digest := sha256.Sum256(artifact) + digestHex := hex.EncodeToString(digest[:]) + statement := []byte(fmt.Sprintf(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"https://example.com/predicateType","subject":[{"name":"subject","digest":{"sha256":"%s"}}],"predicate":{}}`, digestHex)) + attestation, err := virtualSigstore.Attest("foo@example.com", "example issuer", statement) + if err != nil { + t.Fatal(err) + } + + // Report a single Rekor v2 entry (zero integrated time, no v1 entry) so + // rekorV2Bundle sets co.UseSignedTimestamps during verification. + bundle := &rekorV2Entity{SignedEntity: attestation, entries: []*tlog.Entry{makeTlogEntry(t, 0)}} + + // One *CheckOpts shared across goroutines, exactly as verifyImageAttestationsSigstoreBundle shares it. + co := &CheckOpts{ + Identities: []Identity{{Issuer: "example issuer", Subject: "foo@example.com"}}, + IgnoreSCT: true, + TrustedMaterial: virtualSigstore, + } + artifactPolicyOption := verify.WithArtifact(bytes.NewReader(artifact)) + + const goroutines = 50 + start := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(goroutines) + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + <-start // release all goroutines together to widen the race window + // Verification against the synthetic tlog entry is expected to fail; + // the test only asserts the concurrent calls don't race on co. + _, _ = VerifyNewBundle(context.Background(), co, artifactPolicyOption, bundle) + }() + } + close(start) + wg.Wait() +} + +func TestRekorV2Bundle(t *testing.T) { + rekorV1Entry := makeTlogEntry(t, 1234567890) + rekorV2Entry := makeTlogEntry(t, 0) + + tests := []struct { + name string + co *CheckOpts + entries []*tlog.Entry + expectedUseSignedTimestamps bool + }{ + { + name: "IgnoreTlog true", + co: &CheckOpts{ + IgnoreTlog: true, + }, + entries: []*tlog.Entry{rekorV2Entry}, + expectedUseSignedTimestamps: false, + }, + { + name: "SigVerifier set", + co: &CheckOpts{ + SigVerifier: &mockVerifierForBundle{}, + }, + entries: []*tlog.Entry{rekorV2Entry}, + expectedUseSignedTimestamps: false, + }, + { + name: "Rekor v1 entry", + co: &CheckOpts{}, + entries: []*tlog.Entry{rekorV1Entry}, + expectedUseSignedTimestamps: false, + }, + { + name: "Rekor v2 entry", + co: &CheckOpts{}, + entries: []*tlog.Entry{rekorV2Entry}, + expectedUseSignedTimestamps: true, + }, + { + name: "Mixed entries", + co: &CheckOpts{}, + entries: []*tlog.Entry{rekorV1Entry, rekorV2Entry}, + expectedUseSignedTimestamps: false, + }, + { + name: "Already set with Rekor v1", + co: &CheckOpts{ + UseSignedTimestamps: true, + }, + entries: []*tlog.Entry{rekorV1Entry}, + expectedUseSignedTimestamps: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + bundle := &mockSignedEntity{tlogEntries: tc.entries} + err := rekorV2Bundle(bundle, tc.co) + assert.NoError(t, err) + assert.Equal(t, tc.expectedUseSignedTimestamps, tc.co.UseSignedTimestamps) + }) + } +} diff --git a/pkg/cosign/verify_oci_test.go b/pkg/cosign/verify_oci_test.go index aa80eabf981..afcf1ee7e06 100644 --- a/pkg/cosign/verify_oci_test.go +++ b/pkg/cosign/verify_oci_test.go @@ -30,7 +30,7 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" - ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" ) @@ -53,7 +53,7 @@ func TestGetBundles_Empty(t *testing.T) { assert.NoError(t, err) // If tag doesn't exist, should return ErrImageTagNotFound - bundles, hash, err := getBundles(context.Background(), ref, &CheckOpts{}) + bundles, hash, err := GetBundles(context.Background(), ref, []ociremote.Option{}) imgTagNotFound := &ErrImageTagNotFound{} assert.ErrorAs(t, err, &imgTagNotFound) assert.Len(t, bundles, 0) @@ -65,7 +65,7 @@ func TestGetBundles_Empty(t *testing.T) { assert.NoError(t, remote.Write(ref, img)) // Check that no matching attestation error is returned - bundles, hash, err = getBundles(context.Background(), ref, &CheckOpts{}) + bundles, hash, err = GetBundles(context.Background(), ref, []ociremote.Option{}) var noMatchErr *ErrNoMatchingAttestations assert.ErrorAs(t, err, &noMatchErr) assert.Len(t, bundles, 0) @@ -81,7 +81,7 @@ func TestGetBundles_Empty(t *testing.T) { 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{}) + bundles, hash, err = GetBundles(context.Background(), ref, []ociremote.Option{}) assert.ErrorAs(t, err, &noMatchErr) assert.Len(t, bundles, 0) assert.Nil(t, hash) @@ -111,7 +111,7 @@ func TestGetBundles_Valid(t *testing.T) { assert.NoError(t, err) // Retrieve the attestation - bundles, hash, err := getBundles(context.Background(), ref, &CheckOpts{}) + bundles, hash, err := GetBundles(context.Background(), ref, []ociremote.Option{}) assert.NoError(t, err) assert.Len(t, bundles, 1) assert.NotNil(t, hash) @@ -125,6 +125,60 @@ func TestGetBundles_Valid(t *testing.T) { } } +func TestGetBundles_WithTargetRepository(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) + host := u.Host + + sourceRef, err := name.ParseReference(fmt.Sprintf("%s/source-repo:tag", host)) + assert.NoError(t, err) + + targetRepo, err := name.NewRepository(fmt.Sprintf("%s/target-repo", host)) + assert.NoError(t, err) + + // Write the source image. + assert.NoError(t, remote.Write(sourceRef, empty.Image)) + + // Get the source image digest. + desc, err := remote.Head(sourceRef) + assert.NoError(t, err) + + // Write the attestation referrer into the target repo. + // Pass the source digest as subject (so WriteReferrer can HEAD it) and + // WithTargetRepository so the referrer manifest is stored in target-repo. + sourceDigestRef := sourceRef.Context().Digest(desc.Digest.String()) + err = ociremote.WriteAttestationNewBundleFormat(sourceDigestRef, testAttestation, "https://cosign.sigstore.dev/attestation/v1", + ociremote.WithTargetRepository(targetRepo), + ) + assert.NoError(t, err) + + // Without WithTargetRepository, the bundle is not found in the source repo. + bundles, hash, err := GetBundles(context.Background(), sourceRef, []ociremote.Option{}) + var noMatchErr *ErrNoMatchingAttestations + assert.ErrorAs(t, err, &noMatchErr) + assert.Len(t, bundles, 0) + assert.Nil(t, hash) + + // With WithTargetRepository, the bundle is found in the target repo. + bundles, hash, err = GetBundles(context.Background(), sourceRef, []ociremote.Option{ + ociremote.WithTargetRepository(targetRepo), + }) + assert.NoError(t, err) + assert.Len(t, bundles, 1) + assert.NotNil(t, hash) + + 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) { diff --git a/pkg/cosign/verify_sct.go b/pkg/cosign/verify_sct.go index 444c488149c..fc5e7d75051 100644 --- a/pkg/cosign/verify_sct.go +++ b/pkg/cosign/verify_sct.go @@ -25,7 +25,7 @@ import ( 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/cosign/v3/pkg/cosign/fulcioverifier/ctutil" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/tuf" diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index a1dad623084..f08234acba8 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -25,34 +25,45 @@ import ( "crypto/sha256" "crypto/x509" "crypto/x509/pkix" + "encoding/asn1" "encoding/base64" "encoding/hex" "encoding/json" "encoding/pem" "errors" + "fmt" "io" + "math/big" "net" "net/url" "os" + "path/filepath" "strings" "testing" "time" "github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer" + "github.com/digitorus/timestamp" "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" + "github.com/go-openapi/swag/conv" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + ggcrlayout "github.com/google/go-containerregistry/pkg/v1/layout" + gcrMutate "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/stream" "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/cosign/v3/internal/pkg/cosign/rekor/mock" + tsaMock "github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/mock" + "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/layout" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + "github.com/sigstore/cosign/v3/pkg/oci/signed" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "github.com/sigstore/cosign/v3/pkg/types" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" @@ -105,7 +116,11 @@ func (m *mockAttestation) Base64Signature() (string, error) { } func appendSlices(slices [][]byte) []byte { - var tmp []byte + totalLen := 0 + for _, s := range slices { + totalLen += len(s) + } + tmp := make([]byte, 0, totalLen) for _, s := range slices { tmp = append(tmp, s...) } @@ -326,6 +341,57 @@ func TestVerifyImageSignatureWithNoChain(t *testing.T) { t.Fatalf("expected verified=true, got verified=false") } } + +func TestVerifyImageSignatureWithKeyAndCert(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) + sig, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + + // Create a fake bundle + pe, _ := proposedEntries(base64.StdEncoding.EncodeToString(sig), 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(sig), opts...) + + leafSV, err := signature.LoadECDSASignerVerifier(privKey, crypto.SHA256) + if err != nil { + t.Fatal(err) + } + + verified, err := VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, + &CheckOpts{ + SigVerifier: leafSV, + 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() @@ -393,6 +459,53 @@ func TestVerifyImageSignatureWithInvalidPublicKeyType(t *testing.T) { } } +func TestVerifyImageSignatureWithInvalidBundleBodyType(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) + // Set Body to an invalid type + rekorBundle.Payload.Body = 12345 + + 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...) + + _, 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(), "bundle payload body is not a string") { + t.Errorf("did not get expected failure message, wanted 'bundle payload body is not a string' got: %v", err) + } +} + func TestVerifyImageSignatureWithOnlyRoot(t *testing.T) { rootCert, rootKey, _ := test.GenerateRootCa() leafCert, privKey, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) @@ -649,23 +762,32 @@ func TestVerifyImageSignatureWithSigVerifierAndTSA(t *testing.T) { 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) + leaves, intermediates, roots, err := 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)) + sigBytes, err := sv.SignMessage(bytes.NewReader(payload)) if err != nil { - t.Fatalf("error signing the payload with the tsa client server: %v", err) + t.Fatalf("error signing the payload: %v", err) + } + + client.Message = sigBytes + timestampResponse, err := client.GetTimestampResponse(nil) + if err != nil { + t.Fatalf("error getting timestamp response: %v", err) + } + + b64sig := base64.StdEncoding.EncodeToString(sigBytes) + sig, err := static.NewSignature(payload, b64sig, static.WithRFC3161Timestamp(bundle.TimestampToRFC3161Timestamp(timestampResponse))) + if err != nil { + t.Fatalf("error creating oci signature: %v", err) } if bundleVerified, err := VerifyImageSignature(context.TODO(), sig, v1.Hash{}, &CheckOpts{ SigVerifier: sv, @@ -695,23 +817,32 @@ func TestVerifyImageSignatureWithSigVerifierAndRekorTSA(t *testing.T) { 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) + leaves, intermediates, roots, err := 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)) + sigBytes, err := sv.SignMessage(bytes.NewReader(payload)) + if err != nil { + t.Fatalf("error signing the payload: %v", err) + } + + client.Message = sigBytes + timestampResponse, err := client.GetTimestampResponse(nil) + if err != nil { + t.Fatalf("error getting timestamp response: %v", err) + } + + b64sig := base64.StdEncoding.EncodeToString(sigBytes) + sig, err := static.NewSignature(payload, b64sig, static.WithRFC3161Timestamp(bundle.TimestampToRFC3161Timestamp(timestampResponse))) if err != nil { - t.Fatalf("error signing the payload with the rekor and tsa clients: %v", err) + t.Fatalf("error creating oci signature: %v", err) } if _, err := VerifyImageSignature(context.TODO(), sig, v1.Hash{}, &CheckOpts{ SigVerifier: sv, @@ -731,6 +862,84 @@ func TestVerifyImageSignatureWithSigVerifierAndRekorTSA(t *testing.T) { } } +func TestVerifyImageSignatureWithMismatchedBundleAndTrustedRoot(t *testing.T) { + ctx := context.Background() + var ca root.FulcioCertificateAuthority + rootCert, rootKey, _ := test.GenerateRootCa() + ca.Root = rootCert + 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) + signature1, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + + // Create a fake bundle + pe, _ := proposedEntries(base64.StdEncoding.EncodeToString(signature1), 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) + + tlogs := make(map[string]*root.TransparencyLog) + for k, v := range rekorPubKeys.Keys { + tlogs[k] = &root.TransparencyLog{PublicKey: v.PubKey, HashFunc: crypto.SHA256, ValidityPeriodStart: time.Now().Add(-1 * time.Minute)} + } + + trustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, []root.CertificateAuthority{&ca}, nil, nil, tlogs) + if err != nil { + t.Fatal(err) + } + + // Create a different bundle for a different signature + signature2, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256) + pe2, _ := proposedEntries(base64.StdEncoding.EncodeToString(signature2), payload, pemLeaf) + entry2, _ := rtypes.UnmarshalEntry(pe2[0]) + leaf2, _ := entry2.Canonicalize(ctx) + rekorBundle2 := CreateTestBundle(ctx, t, sv, leaf2) + + opts := []static.Option{static.WithCertChain(pemLeaf, []byte{}), static.WithBundle(rekorBundle2)} + // Create a signed entity for the original signature but with the wrong bundle for that signature + ociSig, _ := static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature1), opts...) + + _, err = VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, + &CheckOpts{ + RootCerts: rootPool, + IgnoreSCT: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, + TrustedMaterial: trustedRoot}) + if err == nil || !strings.Contains(err.Error(), "signature in bundle does not match signature being verified") { + t.Fatalf("expected error for mismatched signature and bundle, got %v", err) + } + + // Create a signed entity with a different key from the bundle + leafCert2, _, _ := test.GenerateLeafCert("subject@mail.com", "oidc-issuer", rootCert, rootKey) + pemLeaf2 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert2.Raw}) + + opts = []static.Option{static.WithCertChain(pemLeaf2, []byte{}), static.WithBundle(rekorBundle)} + ociSig, _ = static.NewSignature(payload, base64.StdEncoding.EncodeToString(signature1), opts...) + + _, err = VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, + &CheckOpts{ + RootCerts: rootPool, + IgnoreSCT: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, + TrustedMaterial: trustedRoot}) + if err == nil || !strings.Contains(err.Error(), "error verifying bundle: comparing public key PEMs") { + t.Fatal(err) + } +} + func TestValidateAndUnpackCertSuccess(t *testing.T) { subject := "email@email" oidcIssuer := "https://accounts.google.com" @@ -1364,10 +1573,13 @@ func TestValidateAndUnpackCertWithIntermediatesSuccess(t *testing.T) { Identities: []Identity{{Subject: subject, Issuer: oidcIssuer}}, } - _, err := ValidateAndUnpackCertWithIntermediates(leafCert, co, subPool) + _, chain, err := ValidateAndUnpackCertWithIntermediates(leafCert, co, subPool) if err != nil { t.Errorf("ValidateAndUnpackCertWithIntermediates expected no error, got err = %v", err) } + if len(chain) == 0 { + t.Errorf("expected certificate chain") + } err = CheckCertificatePolicy(leafCert, co) if err != nil { t.Errorf("CheckCertificatePolicy expected no error, got err = %v", err) @@ -1550,7 +1762,7 @@ func TestVerifyRFC3161Timestamp(t *testing.T) { t.Fatal(err) } - tsBytes, err := tsa.GetTimestampedSignature(signature, client) + tsBytes, err := getTimestampedSignature(signature, client) if err != nil { t.Fatalf("unexpected error creating timestamp: %v", err) } @@ -1561,7 +1773,7 @@ func TestVerifyRFC3161Timestamp(t *testing.T) { t.Fatalf("unexpected error marshalling cert chain: %v", err) } - leaves, intermediates, roots, err := tsa.SplitPEMCertificateChain(certChainPEM) + leaves, intermediates, roots, err := splitPEMCertificateChain(certChainPEM) if err != nil { t.Fatal("error splitting response into certificate chain") } @@ -1580,12 +1792,12 @@ func TestVerifyRFC3161Timestamp(t *testing.T) { if err != nil { t.Fatalf("unexpected error verifying timestamp with signature: %v", err) } - if err := CheckExpiry(leafCert, ts.Time); err != nil { + if err := CheckExpiry(leafCert, nil, 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) + tsBytes, err = getTimestampedSignature(payload, client) if err != nil { t.Fatalf("unexpected error creating timestamp: %v", err) } @@ -1618,7 +1830,7 @@ func TestVerifyRFC3161Timestamp(t *testing.T) { } // failure with mismatched signature - tsBytes, err = tsa.GetTimestampedSignature(signature, client) + tsBytes, err = getTimestampedSignature(signature, client) if err != nil { t.Fatalf("unexpected error creating timestamp: %v", err) } @@ -1646,6 +1858,188 @@ func TestVerifyRFC3161Timestamp(t *testing.T) { 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) } + + // failure with empty TrustedRoot + emptyTrustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, nil, nil, nil, nil) + if err != nil { + t.Fatalf("unexpected error creating empty trusted root: %v", err) + } + _, err = VerifyRFC3161Timestamp(ociSig, &CheckOpts{ + TrustedMaterial: emptyTrustedRoot, + }) + if err == nil || !strings.Contains(err.Error(), "expected at least one verified timestamp") { + t.Fatalf("expected error verifying with empty trusted root, got: %v", err) + } +} + +// This test verifies that artifact verification rejects signatures +// where a CA certificate in the issuing chain is expired. This is a contrived +// example because CAs shouldn't issue certificates where the leaf's validity +// outlives any certificate in the chain, but this is checked for thoroughness. +func TestVerifyImageSignatureExpiredCACertificate(t *testing.T) { + now := time.Now().UTC() + + rootCert, rootKey, _ := test.GenerateRootCa() // Valid +/- 5 hours + + subTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "sigstore-sub-expired", + Organization: []string{"sigstore.dev"}, + }, + NotBefore: now.Add(-2 * time.Hour), // Valid during root validity + NotAfter: now.Add(-5 * time.Minute), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + BasicConstraintsValid: true, + IsCA: true, + } + subKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generating subordinate key: %v", err) + } + subBytes, err := x509.CreateCertificate(rand.Reader, subTemplate, rootCert, &subKey.PublicKey, rootKey) + if err != nil { + t.Fatalf("creating subordinate cert: %v", err) + } + subCert, err := x509.ParseCertificate(subBytes) + if err != nil { + t.Fatalf("parsing subordinate cert: %v", err) + } + + leafTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(2), + EmailAddresses: []string{"subject@mail.com"}, + NotBefore: now.Add(-30 * time.Minute), // Valid during intermediate... + NotAfter: now.Add(30 * time.Minute), // but is valid past intermediate expiration + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + IsCA: false, + ExtraExtensions: []pkix.Extension{{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, + Critical: false, + Value: []byte("oidc-issuer"), + }}, + } + leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("generating leaf key: %v", err) + } + leafBytes, err := x509.CreateCertificate(rand.Reader, leafTemplate, subCert, &leafKey.PublicKey, subKey) + if err != nil { + t.Fatalf("creating leaf cert: %v", err) + } + leafCert, err := x509.ParseCertificate(leafBytes) + if err != nil { + t.Fatalf("parsing leaf cert: %v", err) + } + 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) + sigBytes, _ := leafKey.Sign(rand.Reader, h[:], crypto.SHA256) + + ociSig, _ := static.NewSignature(payload, + base64.StdEncoding.EncodeToString(sigBytes), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub, pemRoot}))) + + co := &CheckOpts{ + RootCerts: rootPool, + IgnoreSCT: true, + IgnoreTlog: true, + Identities: []Identity{{Subject: "subject@mail.com", Issuer: "oidc-issuer"}}, + } + + // Verify expected failure, where the current time is used + _, err = VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, co) + if err == nil { + t.Fatalf("expected error verifying signature with expired intermediate") + } + var vf *VerificationFailure + if !errors.As(err, &vf) { + t.Fatalf("expected %T, got %T (%v)", &VerificationFailure{}, err, err) + } + + // Verify expected failure with time provided by a signed timestamp + client, err := tsaMock.NewTSAClient((tsaMock.TSAClientOptions{Time: time.Now()})) + if err != nil { + t.Fatal(err) + } + tsBytes, err := getTimestampedSignature(payload, 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 := splitPEMCertificateChain(certChainPEM) + if err != nil { + t.Fatal("error splitting response into certificate chain") + } + co.TSACertificate = leaves[0] + co.TSAIntermediateCertificates = intermediates + co.TSARootCertificates = roots + + ociSig, _ = static.NewSignature(payload, + base64.StdEncoding.EncodeToString(sigBytes), + static.WithCertChain(pemLeaf, appendSlices([][]byte{pemSub, pemRoot})), + static.WithRFC3161Timestamp(&rfc3161TS)) + _, err = VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, co) + if err == nil { + t.Fatalf("expected error verifying signature with expired intermediate") + } + if !errors.As(err, &vf) { + t.Fatalf("expected %T, got %T (%v)", &VerificationFailure{}, err, err) + } + + // Verify expected failure where the chain is provided via trusted material + // rather than bundled with the image + tm := &trustedMaterialWithFulcioCAs{ + cas: []root.CertificateAuthority{ + &root.FulcioCertificateAuthority{ + Root: rootCert, + Intermediates: []*x509.Certificate{subCert}, + }, + }, + } + co.TrustedMaterial = tm + co.RootCerts = nil + + ociSig, _ = static.NewSignature(payload, + base64.StdEncoding.EncodeToString(sigBytes), + static.WithCertChain(pemLeaf, appendSlices([][]byte{})), + static.WithRFC3161Timestamp(&rfc3161TS)) + _, err = VerifyImageSignature(context.TODO(), ociSig, v1.Hash{}, co) + if err == nil { + t.Fatalf("expected error verifying signature with expired intermediate") + } + if !errors.As(err, &vf) { + t.Fatalf("expected %T, got %T (%v)", &VerificationFailure{}, err, err) + } +} + +type trustedMaterialWithFulcioCAs struct { + root.BaseTrustedMaterial + cas []root.CertificateAuthority +} + +func (tm *trustedMaterialWithFulcioCAs) FulcioCertificateAuthorities() []root.CertificateAuthority { + return tm.cas +} + +func TestVerifyImageAttestation(t *testing.T) { + if _, _, err := VerifyImageAttestation(context.TODO(), nil, v1.Hash{}, nil); err == nil { + t.Error("VerifyImageAttestation() should error when given nil attestations") + } } // Mock Rekor client @@ -1689,9 +2083,9 @@ func createRekorEntry(ctx context.Context, t *testing.T, logID string, signer si integratedTime := time.Now().Unix() logEntry := models.LogEntryAnon{ Body: base64.StdEncoding.EncodeToString(canonicalEntry), - IntegratedTime: swag.Int64(integratedTime), - LogIndex: swag.Int64(0), - LogID: swag.String(logID), + IntegratedTime: conv.Pointer(integratedTime), + LogIndex: conv.Pointer(int64(0)), + LogID: conv.Pointer(logID), } // Canonicalize the log entry and sign it @@ -1711,9 +2105,9 @@ func createRekorEntry(ctx context.Context, t *testing.T, logID string, signer si logEntry.Verification = &models.LogEntryAnonVerification{ SignedEntryTimestamp: signedEntryTimestamp, InclusionProof: &models.InclusionProof{ - LogIndex: swag.Int64(0), - TreeSize: swag.Int64(1), - RootHash: swag.String(hex.EncodeToString(entryUUID)), + LogIndex: conv.Pointer(int64(0)), + TreeSize: conv.Pointer(int64(1)), + RootHash: conv.Pointer(hex.EncodeToString(entryUUID)), Hashes: []string{}, }, } @@ -1752,3 +2146,449 @@ func calculateLogID(t *testing.T, pub crypto.PublicKey) string { digest := sha256.Sum256(pubBytes) return hex.EncodeToString(digest[:]) } + +func TestHasLocalBundles_V2Signatures(t *testing.T) { + // Create a signed image with v2-style signatures (no bundle annotation) + si := createSignedImageWithSignatures(t, false /* withBundle */) + tmp := t.TempDir() + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + hasBundles, err := HasLocalBundles(tmp) + require.NoError(t, err) + assert.False(t, hasBundles, "expected false for v2 signatures without bundles") +} + +func TestHasLocalBundles_V3Bundles(t *testing.T) { + // Create a layout with v3-style sigstore bundles + tmp := createV3BundleLayout(t) + + hasBundles, err := HasLocalBundles(tmp) + require.NoError(t, err) + assert.True(t, hasBundles, "expected true for v3 signatures with bundles") +} + +func TestHasLocalBundles_NoSignatures(t *testing.T) { + // Create an image without any signatures + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + tmp := t.TempDir() + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + hasBundles, err := HasLocalBundles(tmp) + require.NoError(t, err) + assert.False(t, hasBundles, "expected false for image without signatures") +} + +func TestHasLocalBundles_MixedFormats(t *testing.T) { + // Create a layout with v3-style sigstore bundles (mixed = has bundles) + tmp := createV3BundleLayout(t) + + hasBundles, err := HasLocalBundles(tmp) + require.NoError(t, err) + assert.True(t, hasBundles, "expected true when at least one v3 bundle exists") +} + +func TestHasLocalBundles_InvalidPath(t *testing.T) { + _, err := HasLocalBundles("/nonexistent/path") + require.Error(t, err, "expected error for non-existent path") +} + +// createSignedImageWithSignatures creates a test signed image with signatures. +// If withBundle is true, this creates a v3-style layout with sigstore bundle media type. +func createSignedImageWithSignatures(t *testing.T, withBundle bool) oci.SignedImage { + return createTestSignedImage(t, withBundle, false) +} + +func createSignedImageWithAttestations(t *testing.T, withBundle bool) oci.SignedImage { + return createTestSignedImage(t, withBundle, true) +} + +func createTestSignedImage(t *testing.T, withBundle, attestation bool) oci.SignedImage { + t.Helper() + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + + // For v2-style signatures, attach them to the image + if !withBundle { + sig, err := static.NewSignature(nil, "test-payload") + require.NoError(t, err) + + if attestation { + si, err = mutate.AttachAttestationToImage(si, sig) + } else { + si, err = mutate.AttachSignatureToImage(si, sig) + } + require.NoError(t, err) + } + // For v3-style bundles, they need to be created separately with proper media type + // The calling test should handle this differently + + return si +} + +// createV3BundleLayout creates a layout directory with a v3 sigstore bundle. +// V3 bundles are stored as separate images with layers having the sigstore bundle media type. +func createV3BundleLayout(t *testing.T) string { + t.Helper() + tmp := t.TempDir() + + // Create a basic image + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + + // Write the signed image with proper annotations + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + // Get the layout and image index + p, err := ggcrlayout.FromPath(tmp) + require.NoError(t, err) + + ii, err := p.ImageIndex() + require.NoError(t, err) + + manifest, err := ii.IndexManifest() + require.NoError(t, err) + + // Find the target digest + var targetDigest v1.Hash + for _, m := range manifest.Manifests { + // Look for the image entry + if m.Annotations["kind"] == "dev.cosignproject.cosign/image" { + targetDigest = m.Digest + break + } + } + require.NotEmpty(t, targetDigest.String(), "target digest should be found") + + // Create a bundle layer with the sigstore bundle media type + bundleContent := []byte(`{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json"}`) + bundleLayer := stream.NewLayer(io.NopCloser(bytes.NewReader(bundleContent)), + stream.WithMediaType("application/vnd.dev.sigstore.bundle.v0.3+json")) + + // Build the referrer manifest + referrerImg := empty.Image + referrerImg, err = gcrMutate.AppendLayers(referrerImg, bundleLayer) + require.NoError(t, err) + + // Append image to materialize stream layers before calling Manifest() + err = p.AppendImage(referrerImg) + require.NoError(t, err) + + // Get the manifest and add Subject field + referrerManifest, err := referrerImg.Manifest() + require.NoError(t, err) + + // Set Subject to point to target image + referrerManifest.Subject = &v1.Descriptor{ + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: targetDigest, + Size: 0, + } + + // Write the referrer manifest to blobs/sha256 + blobsDir := tmp + "/blobs/sha256" + err = os.MkdirAll(blobsDir, 0755) + require.NoError(t, err) + + manifestBytes, err := json.Marshal(referrerManifest) + require.NoError(t, err) + + manifestHash := v1.Hash{Algorithm: "sha256", Hex: fmt.Sprintf("%x", sha256.Sum256(manifestBytes))} + manifestPath := filepath.Join(blobsDir, manifestHash.Hex) + err = os.WriteFile(manifestPath, manifestBytes, 0644) + require.NoError(t, err) + + return tmp +} + +func TestHasLocalAttestationBundles_V2Attestations(t *testing.T) { + si := createSignedImageWithAttestations(t, false) + tmp := t.TempDir() + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + hasBundles, err := HasLocalAttestationBundles(tmp) + require.NoError(t, err) + assert.False(t, hasBundles, "expected false for v2 attestations without bundles") +} + +func TestHasLocalAttestationBundles_V3Bundles(t *testing.T) { + // V3 bundles are the same for signatures and attestations + tmp := createV3BundleLayout(t) + + hasBundles, err := HasLocalAttestationBundles(tmp) + require.NoError(t, err) + assert.True(t, hasBundles, "expected true for v3 attestations with bundles") +} + +func TestHasLocalSigstoreBundles_OCIReferrers(t *testing.T) { + // Create a layout with OCI referrers pointing to target image with bundle layers + tmp := t.TempDir() + + // Create base image + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + + // Write the signed image with proper annotations + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + // Get the layout and image index + p, err := ggcrlayout.FromPath(tmp) + require.NoError(t, err) + + ii, err := p.ImageIndex() + require.NoError(t, err) + + manifest, err := ii.IndexManifest() + require.NoError(t, err) + + // Find the target digest + var targetDigest v1.Hash + for _, m := range manifest.Manifests { + // Look for the image entry + if m.Annotations["kind"] == "dev.cosignproject.cosign/image" { + targetDigest = m.Digest + break + } + } + require.NotEmpty(t, targetDigest.String(), "target digest should be found") + + // Create a referrer manifest with Subject pointing to target + bundleContent := []byte(`{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json"}`) + bundleLayer := stream.NewLayer(io.NopCloser(bytes.NewReader(bundleContent)), + stream.WithMediaType("application/vnd.dev.sigstore.bundle.v0.3+json")) + + // Build the referrer manifest + referrerImg := empty.Image + referrerImg, err = gcrMutate.AppendLayers(referrerImg, bundleLayer) + require.NoError(t, err) + + // Append image to materialize stream layers before calling Manifest() + err = p.AppendImage(referrerImg) + require.NoError(t, err) + + // Get the manifest and add Subject field + referrerManifest, err := referrerImg.Manifest() + require.NoError(t, err) + + // Set Subject to point to target image + referrerManifest.Subject = &v1.Descriptor{ + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: targetDigest, + Size: 0, + } + + // Write the referrer manifest to blobs/sha256 + blobsDir := tmp + "/blobs/sha256" + err = os.MkdirAll(blobsDir, 0755) + require.NoError(t, err) + + manifestBytes, err := json.Marshal(referrerManifest) + require.NoError(t, err) + + manifestHash := v1.Hash{Algorithm: "sha256", Hex: fmt.Sprintf("%x", sha256.Sum256(manifestBytes))} + manifestPath := filepath.Join(blobsDir, manifestHash.Hex) + err = os.WriteFile(manifestPath, manifestBytes, 0644) + require.NoError(t, err) + + // Test that hasLocalSigstoreBundles detects the referrer + hasBundles, err := hasLocalSigstoreBundles(tmp) + require.NoError(t, err) + assert.True(t, hasBundles, "expected true for OCI referrers with bundle layers") +} + +func TestHasLocalSigstoreBundles_NoBlobsDir(t *testing.T) { + // Create a layout without blobs/sha256 directory + tmp := t.TempDir() + + // Create base image + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + // Remove blobs directory to simulate missing directory + blobsDir := tmp + "/blobs/sha256" + err = os.RemoveAll(blobsDir) + require.NoError(t, err) + + // Should return false without error + hasBundles, err := hasLocalSigstoreBundles(tmp) + require.NoError(t, err) + assert.False(t, hasBundles, "expected false when blobs/sha256 directory missing") +} + +func TestHasLocalSigstoreBundles_ReferrerDifferentSubject(t *testing.T) { + // Create a layout with a referrer pointing to a different subject + tmp := t.TempDir() + + // Create base image + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + // Create a referrer manifest with Subject pointing to different digest + bundleContent := []byte(`{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json"}`) + + // Calculate the bundle layer descriptor + bundleDigest := v1.Hash{Algorithm: "sha256", Hex: fmt.Sprintf("%x", sha256.Sum256(bundleContent))} + bundleDescriptor := v1.Descriptor{ + MediaType: "application/vnd.dev.sigstore.bundle.v0.3+json", + Digest: bundleDigest, + Size: int64(len(bundleContent)), + } + + // Create a minimal config + configContent := []byte("{}") + configDigest := v1.Hash{Algorithm: "sha256", Hex: fmt.Sprintf("%x", sha256.Sum256(configContent))} + configDescriptor := v1.Descriptor{ + MediaType: "application/vnd.oci.image.config.v1+json", + Digest: configDigest, + Size: int64(len(configContent)), + } + + // Set Subject to a different digest + differentDigest := v1.Hash{Algorithm: "sha256", Hex: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} + + // Build the manifest structure directly + referrerManifest := &v1.Manifest{ + SchemaVersion: 2, + MediaType: "application/vnd.oci.image.manifest.v1+json", + Config: configDescriptor, + Layers: []v1.Descriptor{bundleDescriptor}, + Subject: &v1.Descriptor{ + MediaType: "application/vnd.oci.image.manifest.v1+json", + Digest: differentDigest, + Size: 0, + }, + } + + // Write the referrer manifest to blobs/sha256 + blobsDir := tmp + "/blobs/sha256" + + manifestBytes, err := json.Marshal(referrerManifest) + require.NoError(t, err) + + manifestHash := v1.Hash{Algorithm: "sha256", Hex: fmt.Sprintf("%x", sha256.Sum256(manifestBytes))} + manifestPath := filepath.Join(blobsDir, manifestHash.Hex) + err = os.WriteFile(manifestPath, manifestBytes, 0644) + require.NoError(t, err) + + // Should return false since referrer points to different subject + hasBundles, err := hasLocalSigstoreBundles(tmp) + require.NoError(t, err) + assert.False(t, hasBundles, "expected false when referrer points to different subject") +} + +func TestHasLocalSigstoreBundles_EmptyBlobsDir(t *testing.T) { + // Create a layout with empty blobs/sha256 directory + tmp := t.TempDir() + + // Create base image + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + // Clear the blobs directory (but keep it existing) + blobsDir := tmp + "/blobs/sha256" + entries, err := os.ReadDir(blobsDir) + require.NoError(t, err) + + for _, entry := range entries { + err = os.Remove(filepath.Join(blobsDir, entry.Name())) + require.NoError(t, err) + } + + // Should return false without error + hasBundles, err := hasLocalSigstoreBundles(tmp) + require.NoError(t, err) + assert.False(t, hasBundles, "expected false for empty blobs directory") +} + +func TestGetLocalBundles_MissingBlobsDir(t *testing.T) { + tmp := t.TempDir() + + // Create base image + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + // Remove blobs directory + blobsDir := tmp + "/blobs/sha256" + err = os.RemoveAll(blobsDir) + require.NoError(t, err) + + bundles, hash, err := GetLocalBundles(tmp) + assert.Error(t, err, "expected ErrNoMatchingAttestations when no bundles exist") + assert.Nil(t, hash) + assert.Nil(t, bundles) + var noMatchErr *ErrNoMatchingAttestations + assert.ErrorAs(t, err, &noMatchErr, "expected ErrNoMatchingAttestations") +} + +func TestGetLocalBundles_ZeroBundles(t *testing.T) { + tmp := t.TempDir() + + // Create base image without any bundles + img, err := random.Image(100, 3) + require.NoError(t, err) + si := signed.Image(img) + + if err := layout.WriteSignedImage(tmp, si); err != nil { + t.Fatalf("WriteSignedImage() = %v", err) + } + + bundles, hash, err := GetLocalBundles(tmp) + assert.Error(t, err, "expected error when zero bundles exist") + assert.Nil(t, hash) + assert.Nil(t, bundles) + var noMatchErr *ErrNoMatchingAttestations + assert.ErrorAs(t, err, &noMatchErr, "expected ErrNoMatchingAttestations") +} + +func TestGetLocalBundles_InvalidPath(t *testing.T) { + bundles, hash, err := GetLocalBundles("/nonexistent/path") + require.Error(t, err) + assert.Nil(t, hash) + assert.Nil(t, bundles) +} + +func getTimestampedSignature(sigBytes []byte, tsaClient *tsaMock.TSAClient) ([]byte, error) { + requestBytes, err := timestamp.CreateRequest(bytes.NewReader(sigBytes), ×tamp.RequestOptions{ + Hash: crypto.SHA256, + Certificates: true, + }) + if err != nil { + return nil, fmt.Errorf("error creating timestamp request: %w", err) + } + + return tsaClient.GetTimestampResponse(requestBytes) +} diff --git a/pkg/oci/empty/empty.go b/pkg/oci/empty/empty.go index 599ad08f8ef..65d86542378 100644 --- a/pkg/oci/empty/empty.go +++ b/pkg/oci/empty/empty.go @@ -21,7 +21,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/sigstore/cosign/v2/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci" ) // Signatures constructs an empty oci.Signatures. diff --git a/pkg/oci/empty/empty_test.go b/pkg/oci/empty/empty_test.go index c9aad1fc23d..ba123ca61ff 100644 --- a/pkg/oci/empty/empty_test.go +++ b/pkg/oci/empty/empty_test.go @@ -21,7 +21,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) func TestEmptyImage(t *testing.T) { diff --git a/pkg/oci/empty/signed.go b/pkg/oci/empty/signed.go deleted file mode 100644 index 9847e128c4a..00000000000 --- a/pkg/oci/empty/signed.go +++ /dev/null @@ -1,70 +0,0 @@ -// -// 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 -} diff --git a/pkg/oci/empty/signed_test.go b/pkg/oci/empty/signed_test.go deleted file mode 100644 index 691a1df2972..00000000000 --- a/pkg/oci/empty/signed_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// -// 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) - } - } -} diff --git a/pkg/oci/internal/signature/layer.go b/pkg/oci/internal/signature/layer.go index 4bd5e456c77..28b8d9828a0 100644 --- a/pkg/oci/internal/signature/layer.go +++ b/pkg/oci/internal/signature/layer.go @@ -24,9 +24,9 @@ import ( "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" + payloadsize "github.com/sigstore/cosign/v3/internal/pkg/cosign/payload/size" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" "github.com/sigstore/sigstore/pkg/cryptoutils" ) @@ -108,6 +108,9 @@ func (s *sigLayer) Cert() (*x509.Certificate, error) { if err != nil { return nil, err } + if len(certs) == 0 { + return nil, nil + } return certs[0], nil } diff --git a/pkg/oci/internal/signature/layer_test.go b/pkg/oci/internal/signature/layer_test.go index d3895f9c42a..a30638a8c81 100644 --- a/pkg/oci/internal/signature/layer_test.go +++ b/pkg/oci/internal/signature/layer_test.go @@ -28,7 +28,7 @@ import ( 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" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" ) func mustDecode(s string) []byte { @@ -126,6 +126,19 @@ func TestSignature(t *testing.T) { }, wantSig: "blah", wantCertErr: errors.New(`error during PEM decoding`), + }, { + name: "min plus empty cert", + l: &sigLayer{ + Layer: layer, + desc: v1.Descriptor{ + Digest: digest, + Annotations: map[string]string{ + sigkey: "blah", + certkey: " ", + }, + }, + }, + wantSig: "blah", }, { name: "min plus bad chain", l: &sigLayer{ diff --git a/pkg/oci/layout/index.go b/pkg/oci/layout/index.go index 1242740dc69..e122301a6af 100644 --- a/pkg/oci/layout/index.go +++ b/pkg/oci/layout/index.go @@ -20,8 +20,8 @@ import ( 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" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/signed" ) const ( diff --git a/pkg/oci/layout/signatures.go b/pkg/oci/layout/signatures.go index 80541f11a07..ca169d46757 100644 --- a/pkg/oci/layout/signatures.go +++ b/pkg/oci/layout/signatures.go @@ -17,8 +17,8 @@ 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" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/internal/signature" ) const maxLayers = 1000 diff --git a/pkg/oci/layout/write.go b/pkg/oci/layout/write.go index c3c8c2055d7..2917d5f5e44 100644 --- a/pkg/oci/layout/write.go +++ b/pkg/oci/layout/write.go @@ -21,7 +21,7 @@ import ( 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" + "github.com/sigstore/cosign/v3/pkg/oci" ) // WriteSignedImage writes the image and all related signatures, attestations and attachments diff --git a/pkg/oci/layout/write_test.go b/pkg/oci/layout/write_test.go index 823a27329f0..8e47254c358 100644 --- a/pkg/oci/layout/write_test.go +++ b/pkg/oci/layout/write_test.go @@ -23,15 +23,15 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + "github.com/sigstore/cosign/v3/pkg/oci/signed" + "github.com/sigstore/cosign/v3/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") + t.Skip("test is flaky on windows, see https://github.com/sigstore/cosign/issues/1389") } // write random signed image to disk si := randomSignedImage(t) diff --git a/pkg/oci/mediatypes.go b/pkg/oci/mediatypes.go index a189047720b..c23862f9b69 100644 --- a/pkg/oci/mediatypes.go +++ b/pkg/oci/mediatypes.go @@ -18,7 +18,7 @@ package oci import ( "strconv" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) const ( diff --git a/pkg/oci/mutate/map.go b/pkg/oci/mutate/map.go index 8c31fc1892b..8af264aa4c8 100644 --- a/pkg/oci/mutate/map.go +++ b/pkg/oci/mutate/map.go @@ -24,7 +24,7 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/oci" ) // Fn is the signature of the callback supplied to Map. diff --git a/pkg/oci/mutate/map_test.go b/pkg/oci/mutate/map_test.go index b243de17c85..d1cbe585ff3 100644 --- a/pkg/oci/mutate/map_test.go +++ b/pkg/oci/mutate/map_test.go @@ -23,8 +23,8 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/signed" ) func TestMapImage(t *testing.T) { diff --git a/pkg/oci/mutate/mutate.go b/pkg/oci/mutate/mutate.go index 59ba2c0c0ff..f7e83087b3b 100644 --- a/pkg/oci/mutate/mutate.go +++ b/pkg/oci/mutate/mutate.go @@ -21,9 +21,9 @@ import ( 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" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/empty" + "github.com/sigstore/cosign/v3/pkg/oci/signed" ) // Appendable is our signed version of mutate.Appendable diff --git a/pkg/oci/mutate/mutate_test.go b/pkg/oci/mutate/mutate_test.go index b0e85be2a43..f6e93e39055 100644 --- a/pkg/oci/mutate/mutate_test.go +++ b/pkg/oci/mutate/mutate_test.go @@ -25,9 +25,9 @@ import ( 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" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/signed" + "github.com/sigstore/cosign/v3/pkg/oci/static" ) func TestAppendManifests(t *testing.T) { diff --git a/pkg/oci/mutate/options.go b/pkg/oci/mutate/options.go index 342eea4e7c5..9299cfd223a 100644 --- a/pkg/oci/mutate/options.go +++ b/pkg/oci/mutate/options.go @@ -17,8 +17,8 @@ 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" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" ) // DupeDetector scans a list of signatures looking for a duplicate. diff --git a/pkg/oci/mutate/signature.go b/pkg/oci/mutate/signature.go index f9b36a03abb..ad1cd018518 100644 --- a/pkg/oci/mutate/signature.go +++ b/pkg/oci/mutate/signature.go @@ -23,9 +23,9 @@ import ( 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/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/static" "github.com/sigstore/sigstore/pkg/cryptoutils" ) diff --git a/pkg/oci/mutate/signature_test.go b/pkg/oci/mutate/signature_test.go index 578fe4fcbd7..ba4a1f6daf3 100644 --- a/pkg/oci/mutate/signature_test.go +++ b/pkg/oci/mutate/signature_test.go @@ -21,9 +21,9 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/static" ) var ( diff --git a/pkg/oci/mutate/signatures.go b/pkg/oci/mutate/signatures.go index 75a1053802e..ede9f678ffd 100644 --- a/pkg/oci/mutate/signatures.go +++ b/pkg/oci/mutate/signatures.go @@ -18,9 +18,9 @@ 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" + "github.com/sigstore/cosign/v3/internal/pkg/now" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/empty" ) const maxLayers = 1000 diff --git a/pkg/oci/mutate/signatures_test.go b/pkg/oci/mutate/signatures_test.go index 1a0bf6d5a69..6c525e11eee 100644 --- a/pkg/oci/mutate/signatures_test.go +++ b/pkg/oci/mutate/signatures_test.go @@ -21,9 +21,9 @@ import ( 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" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/empty" + "github.com/sigstore/cosign/v3/pkg/oci/static" ) func TestAppendSignatures(t *testing.T) { diff --git a/pkg/oci/platform/platform.go b/pkg/oci/platform/platform.go index a2939d73660..28cdc7a3c24 100644 --- a/pkg/oci/platform/platform.go +++ b/pkg/oci/platform/platform.go @@ -1,4 +1,4 @@ -// Copyright 2023 the Sigstore Authors. +// 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. @@ -19,7 +19,7 @@ import ( "strings" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci" ) type List []struct { @@ -28,7 +28,7 @@ type List []struct { } func (pl *List) String() string { - r := []string{} + r := make([]string, 0, len(*pl)) for _, p := range *pl { r = append(r, p.Platform.String()) } diff --git a/pkg/oci/remote/image.go b/pkg/oci/remote/image.go index 8c6eda5ff0e..30d30a7d53d 100644 --- a/pkg/oci/remote/image.go +++ b/pkg/oci/remote/image.go @@ -23,7 +23,7 @@ import ( 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/v3/pkg/oci" ) var ErrImageNotFound = errors.New("image not found in registry") diff --git a/pkg/oci/remote/index.go b/pkg/oci/remote/index.go index 6269e9bfaaf..0aad7480d7f 100644 --- a/pkg/oci/remote/index.go +++ b/pkg/oci/remote/index.go @@ -22,7 +22,7 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/oci" ) // SignedImageIndex provides access to a remote index reference, and its signatures. diff --git a/pkg/oci/remote/index_test.go b/pkg/oci/remote/index_test.go index 93e841808c9..8ef16012c35 100644 --- a/pkg/oci/remote/index_test.go +++ b/pkg/oci/remote/index_test.go @@ -25,7 +25,7 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/oci" ) func TestSignedImageIndex(t *testing.T) { diff --git a/pkg/oci/remote/options.go b/pkg/oci/remote/options.go index 6eeaadd0105..616a92a8eff 100644 --- a/pkg/oci/remote/options.go +++ b/pkg/oci/remote/options.go @@ -21,7 +21,7 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) const ( @@ -129,6 +129,13 @@ func WithTargetRepository(repo name.Repository) Option { } } +// TargetRepositoryFromOptions extracts the TargetRepository that a +// WithTargetRepository option would have set in the provided options. +// Returns the zero name.Repository if no override was provided. +func TargetRepositoryFromOptions(opts ...Option) name.Repository { + return makeOptions(name.Repository{}, opts...).TargetRepository +} + // 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. diff --git a/pkg/oci/remote/referrers.go b/pkg/oci/remote/referrers.go index 1b67cdf392a..4f21e23ea3e 100644 --- a/pkg/oci/remote/referrers.go +++ b/pkg/oci/remote/referrers.go @@ -21,14 +21,20 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" ) +var remoteReferrers = remote.Referrers + // Referrers fetches references using registry options. func Referrers(d name.Digest, artifactType string, opts ...Option) (*v1.IndexManifest, error) { o := makeOptions(name.Repository{}, opts...) + if (o.TargetRepository != name.Repository{}) { + d = o.TargetRepository.Digest(d.DigestStr()) + } + rOpt := o.ROpt if artifactType != "" { rOpt = append(rOpt, remote.WithFilter("artifactType", artifactType)) } - idx, err := remote.Referrers(d, rOpt...) + idx, err := remoteReferrers(d, rOpt...) if err != nil { return nil, err } diff --git a/pkg/oci/remote/referrers_test.go b/pkg/oci/remote/referrers_test.go new file mode 100644 index 00000000000..ed66760781f --- /dev/null +++ b/pkg/oci/remote/referrers_test.go @@ -0,0 +1,121 @@ +// +// 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 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/remote" +) + +const testDigestStr = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + +func TestReferrers_NoTargetRepository(t *testing.T) { + orig := remoteReferrers + t.Cleanup(func() { remoteReferrers = orig }) + + var capturedDigest name.Digest + remoteReferrers = func(d name.Digest, _ ...remote.Option) (v1.ImageIndex, error) { + capturedDigest = d + return empty.Index, nil + } + + inputDigest, err := name.NewDigest("gcr.io/source-repo/image@" + testDigestStr) + if err != nil { + t.Fatalf("name.NewDigest: %v", err) + } + + if _, err = Referrers(inputDigest, ""); err != nil { + t.Fatalf("Referrers() = %v", err) + } + + if got, want := capturedDigest.Repository.String(), inputDigest.Repository.String(); got != want { + t.Errorf("repository = %q, want %q", got, want) + } + if got, want := capturedDigest.DigestStr(), testDigestStr; got != want { + t.Errorf("digest = %q, want %q", got, want) + } +} + +func TestReferrers_WithTargetRepository(t *testing.T) { + orig := remoteReferrers + t.Cleanup(func() { remoteReferrers = orig }) + + var capturedDigest name.Digest + remoteReferrers = func(d name.Digest, _ ...remote.Option) (v1.ImageIndex, error) { + capturedDigest = d + return empty.Index, nil + } + + inputDigest, err := name.NewDigest("gcr.io/source-repo/image@" + testDigestStr) + if err != nil { + t.Fatalf("name.NewDigest: %v", err) + } + targetRepo, err := name.NewRepository("gcr.io/target-repo/other") + if err != nil { + t.Fatalf("name.NewRepository: %v", err) + } + + if _, err = Referrers(inputDigest, "", WithTargetRepository(targetRepo)); err != nil { + t.Fatalf("Referrers() = %v", err) + } + + // The digest must be redirected to the target repository. + if got, want := capturedDigest.Repository.String(), targetRepo.String(); got != want { + t.Errorf("repository = %q, want %q", got, want) + } + // The digest hash itself must be preserved. + if got, want := capturedDigest.DigestStr(), testDigestStr; got != want { + t.Errorf("digest = %q, want %q", got, want) + } +} + +func TestReferrers_ArtifactTypeFilter(t *testing.T) { + orig := remoteReferrers + t.Cleanup(func() { remoteReferrers = orig }) + + inputDigest, err := name.NewDigest("gcr.io/source-repo/image@" + testDigestStr) + if err != nil { + t.Fatalf("name.NewDigest: %v", err) + } + + // Capture baseline option count (no artifact type filter). + var baselineOptCount int + remoteReferrers = func(_ name.Digest, opts ...remote.Option) (v1.ImageIndex, error) { + baselineOptCount = len(opts) + return empty.Index, nil + } + if _, err = Referrers(inputDigest, ""); err != nil { + t.Fatalf("Referrers() baseline = %v", err) + } + + // Now with an artifact type filter — should add one extra option. + var capturedOptCount int + remoteReferrers = func(_ name.Digest, opts ...remote.Option) (v1.ImageIndex, error) { + capturedOptCount = len(opts) + return empty.Index, nil + } + if _, err = Referrers(inputDigest, "application/vnd.example.type"); err != nil { + t.Fatalf("Referrers() with filter = %v", err) + } + + if capturedOptCount != baselineOptCount+1 { + t.Errorf("expected %d options (baseline %d + 1 filter), got %d", baselineOptCount+1, baselineOptCount, capturedOptCount) + } +} diff --git a/pkg/oci/remote/remote.go b/pkg/oci/remote/remote.go index eab4e1f9b01..ad554ced1ea 100644 --- a/pkg/oci/remote/remote.go +++ b/pkg/oci/remote/remote.go @@ -26,17 +26,20 @@ import ( "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" + payloadsize "github.com/sigstore/cosign/v3/internal/pkg/cosign/payload/size" + ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote" + "github.com/sigstore/cosign/v3/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 + remoteImage = remote.Image + remoteIndex = remote.Index + remoteGet = remote.Get + remoteWrite = remote.Write + remoteHead = remote.Head + remoteWriteLayer = remote.WriteLayer + remotePut = remote.Put ) // EntityNotFoundError is the error that SignedEntity returns when the diff --git a/pkg/oci/remote/signatures.go b/pkg/oci/remote/signatures.go index bde786ae28c..dba27827a72 100644 --- a/pkg/oci/remote/signatures.go +++ b/pkg/oci/remote/signatures.go @@ -25,9 +25,9 @@ import ( 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" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/empty" + "github.com/sigstore/cosign/v3/pkg/oci/internal/signature" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" ) @@ -76,6 +76,7 @@ func Bundle(ref name.Reference, opts ...Option) (*sgbundle.Bundle, error) { if err != nil { return nil, err } + defer layer0.Close() bundleBytes, err := io.ReadAll(layer0) if err != nil { return nil, err diff --git a/pkg/oci/remote/unknown.go b/pkg/oci/remote/unknown.go index 90a0fc9acb1..8ddb9f5a045 100644 --- a/pkg/oci/remote/unknown.go +++ b/pkg/oci/remote/unknown.go @@ -18,7 +18,7 @@ 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" + "github.com/sigstore/cosign/v3/pkg/oci" ) // SignedUnknown provides access to signed metadata without directly accessing diff --git a/pkg/oci/remote/write.go b/pkg/oci/remote/write.go index d353c6b0883..9c88a442c9f 100644 --- a/pkg/oci/remote/write.go +++ b/pkg/oci/remote/write.go @@ -20,24 +20,29 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" + "strings" "time" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/remote" + goremote "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" + ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" + ctypes "github.com/sigstore/cosign/v3/pkg/types" sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" ) +const BundlePredicateType string = "dev.sigstore.bundle.predicateType" + // 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 { +func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, directory string, opts ...Option) error { repo := ref.Context() o := makeOptions(repo, opts...) @@ -47,7 +52,7 @@ func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, o return fmt.Errorf("signed image index: %w", err) } if ii != nil { - if err := remote.WriteIndex(ref, ii, o.ROpt...); err != nil { + if err := goremote.WriteIndex(ref, ii, o.ROpt...); err != nil { return fmt.Errorf("writing index: %w", err) } } @@ -90,6 +95,75 @@ func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, o } return remoteWrite(attsTag, atts, o.ROpt...) } + + // Look for any referring artifacts + digest, ok := ref.(name.Digest) + if !ok { + var err error + digest, err = ResolveDigest(ref, opts...) + if err != nil { + return fmt.Errorf("resolving digest: %w", err) + } + } + blobPath := filepath.Join(directory, "blobs", "sha256") + + files, err := os.ReadDir(blobPath) + if err != nil { + return err + } + + for _, file := range files { + fd, err := os.Open(filepath.Join(blobPath, file.Name())) + if err != nil { + return err + } + manifest, err := v1.ParseManifest(fd) + fd.Close() + if err != nil || manifest.Subject == nil { + continue + } + if strings.Compare(manifest.Subject.Digest.String(), digest.DigestStr()) == 0 { + // Get the predicate type + predicateType := "" + if manifest.Annotations != nil { + if v, ok := manifest.Annotations[BundlePredicateType]; ok { + predicateType = v + } + } + if predicateType != "" { + // Write the empty layer + _, _, err := writeEmptyConfigLayer(o) + if err != nil { + return err + } + + // Write the manifest + m := referrerManifest{*manifest, bundle.BundleV03MediaType} + targetRef, err := m.targetRef(o.TargetRepository, opts...) + if err != nil { + return fmt.Errorf("failed to create target reference: %w", err) + } + if err := remotePut(targetRef, m, o.ROpt...); err != nil { + return fmt.Errorf("failed to upload manifest: %w", err) + } + + // Write bundle layers + for _, layer := range manifest.Layers { + bundlePath := filepath.Join(directory, "blobs", "sha256", layer.Digest.Hex) + bundleBytes, err := os.ReadFile(bundlePath) + if err != nil { + return err + } + layer := static.NewLayer(bundleBytes, types.MediaType(bundle.BundleV03MediaType)) + err = remoteWriteLayer(o.TargetRepository, layer, o.ROpt...) + if err != nil { + return err + } + } + } + } + } + return nil } @@ -146,7 +220,7 @@ func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ... if err != nil { return err } - desc, err := remote.Head(ref, o.ROpt...) + desc, err := remoteHead(ref, o.ROpt...) if err != nil { return err } @@ -161,7 +235,7 @@ func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ... return err } for _, v := range s { - if err := remote.WriteLayer(d.Repository, v, o.ROpt...); err != nil { + if err := remoteWriteLayer(d.Repository, v, o.ROpt...); err != nil { return err } } @@ -176,7 +250,7 @@ func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ... return err } configLayer := static.NewLayer(configBytes, configDesc.MediaType) - if err := remote.WriteLayer(d.Repository, configLayer, o.ROpt...); err != nil { + if err := remoteWriteLayer(d.Repository, configLayer, o.ROpt...); err != nil { return err } @@ -208,7 +282,7 @@ func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ... // 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...) + return remotePut(targetRef, &taggableManifest{raw: b, mediaType: m.MediaType}, o.ROpt...) } type taggableManifest struct { @@ -224,55 +298,77 @@ 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 +func writeEmptyConfigLayer(o *options) (v1.Hash, int64, error) { 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) + return v1.Hash{}, 0, fmt.Errorf("failed to calculate digest: %w", err) } configSize, err := configLayer.Size() if err != nil { - return fmt.Errorf("failed to calculate size: %w", err) + return v1.Hash{}, 0, fmt.Errorf("failed to calculate size: %w", err) } - err = remote.WriteLayer(d.Repository, configLayer, o.ROpt...) + err = remoteWriteLayer(o.TargetRepository, configLayer, o.ROpt...) if err != nil { - return fmt.Errorf("failed to upload layer: %w", err) + return v1.Hash{}, 0, fmt.Errorf("failed to upload layer: %w", err) } + return configDigest, configSize, nil +} - // generate bundle media type string - bundleMediaType, err := sgbundle.MediaTypeString("0.3") +// WriteReferrer writes a referrer manifest for a given subject digest. +// It uploads the provided layers and creates a manifest that refers to the subject. +func WriteReferrer(d name.Digest, artifactType string, layers []v1.Layer, annotations map[string]string, opts ...Option) error { + o := makeOptions(d.Repository, opts...) + + signTarget := d.String() + ref, err := name.ParseReference(signTarget, o.NameOpts...) if err != nil { - return fmt.Errorf("failed to generate bundle media type string: %w", err) + return err } - - // Write the bundle layer - layer := static.NewLayer(bundleBytes, types.MediaType(bundleMediaType)) - blobDigest, err := layer.Digest() + desc, err := remoteHead(ref, o.ROpt...) if err != nil { - return fmt.Errorf("failed to calculate digest: %w", err) + return err } - blobSize, err := layer.Size() + // Write the empty config layer + configDigest, configSize, err := writeEmptyConfigLayer(o) if err != nil { - return fmt.Errorf("failed to calculate size: %w", err) + return err } - err = remote.WriteLayer(d.Repository, layer, o.ROpt...) - if err != nil { - return fmt.Errorf("failed to upload layer: %w", err) + layerDescriptors := make([]v1.Descriptor, len(layers)) + for i, layer := range layers { + mediaType, err := layer.MediaType() + if err != nil { + return fmt.Errorf("failed to get media type: %w", err) + } + layerDigest, err := layer.Digest() + if err != nil { + return fmt.Errorf("failed to calculate digest: %w", err) + } + layerSize, err := layer.Size() + if err != nil { + return fmt.Errorf("failed to calculate size: %w", err) + } + + err = remoteWriteLayer(o.TargetRepository, layer, o.ROpt...) + if err != nil { + return fmt.Errorf("failed to upload layer: %w", err) + } + layerDescriptors[i] = v1.Descriptor{ + MediaType: mediaType, + Digest: layerDigest, + Size: layerSize, + } + // Preserve per-layer annotations when available (e.g. from oci.Signature + // objects that carry certificate, chain, and RFC3161 timestamp annotations). + if al, ok := layer.(interface { + Annotations() (map[string]string, error) + }); ok { + if ann, err := al.Annotations(); err == nil && len(ann) > 0 { + layerDescriptors[i].Annotations = ann + } + } } // Create a manifest that includes the blob as a layer @@ -281,42 +377,83 @@ func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicat MediaType: types.OCIManifestSchema1, Config: v1.Descriptor{ MediaType: types.MediaType("application/vnd.oci.empty.v1+json"), - ArtifactType: bundleMediaType, + ArtifactType: artifactType, Digest: configDigest, Size: configSize, }, - Layers: []v1.Descriptor{ - { - MediaType: types.MediaType(bundleMediaType), - Digest: blobDigest, - Size: blobSize, - }, - }, + Layers: layerDescriptors, 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} + Annotations: annotations, + }, artifactType} - targetRef, err := manifest.targetRef(d.Repository) + targetRef, err := manifest.targetRef(o.TargetRepository, opts...) if err != nil { return fmt.Errorf("failed to create target reference: %w", err) } - if err := remote.Put(targetRef, manifest, o.ROpt...); err != nil { + if err := remotePut(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. +func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicateType string, opts ...Option) error { + // 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)) + + annotations := map[string]string{ + "org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339), + "dev.sigstore.bundle.content": "dsse-envelope", + BundlePredicateType: predicateType, + } + + return WriteReferrer(d, bundleMediaType, []v1.Layer{layer}, annotations, opts...) +} + +// WriteAttestationsReferrer publishes the attestations attached to the given entity +// into the provided repository using the referrers API. +func WriteAttestationsReferrer(d name.Digest, se oci.SignedEntity, opts ...Option) error { + atts, err := se.Attestations() + if err != nil { + return err + } + // Use Get() instead of Layers() to retrieve oci.Signature objects that + // carry per-layer descriptor annotations (certificate, chain, RFC3161 + // timestamp). Layers() returns raw v1.Layer objects without annotations. + sigs, err := atts.Get() + if err != nil { + return err + } + layers := make([]v1.Layer, len(sigs)) + for i, sig := range sigs { + layers[i] = sig + } + + annotations := map[string]string{ + "org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339), + } + + // We have to pick an artifactType for the referrer manifest. The attestation + // layers themselves are DSSE envelopes, which wrap in-toto statements. + // For discovery, the artifactType should describe the semantic content (the + // in-toto statement) rather than the wrapper format (the DSSE envelope). + // Using the in-toto media type is the most appropriate and conventional choice, + // as policy engines and other tools will query for attestations using this type. + return WriteReferrer(d, ctypes.IntotoPayloadType, layers, annotations, opts...) +} + +// referrerManifest implements Taggable for use in remotePut. // 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. @@ -331,7 +468,8 @@ func (r referrerManifest) RawManifest() ([]byte, error) { return json.Marshal(r) } -func (r referrerManifest) targetRef(repo name.Repository) (name.Reference, error) { +func (r referrerManifest) targetRef(repo name.Repository, opts ...Option) (name.Reference, error) { + o := makeOptions(repo, opts...) manifestBytes, err := r.RawManifest() if err != nil { return nil, err @@ -340,7 +478,7 @@ func (r referrerManifest) targetRef(repo name.Repository) (name.Reference, error if err != nil { return nil, err } - return name.ParseReference(fmt.Sprintf("%s/%s@%s", repo.RegistryStr(), repo.RepositoryStr(), digest.String())) + return name.ParseReference(fmt.Sprintf("%s/%s@%s", repo.RegistryStr(), repo.RepositoryStr(), digest.String()), o.NameOpts...) } func (r referrerManifest) MediaType() (types.MediaType, error) { diff --git a/pkg/oci/remote/write_test.go b/pkg/oci/remote/write_test.go index 32be283fff1..af5f18ab364 100644 --- a/pkg/oci/remote/write_test.go +++ b/pkg/oci/remote/write_test.go @@ -17,15 +17,19 @@ package remote import ( "fmt" + "strings" "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" + "github.com/google/go-containerregistry/pkg/v1/static" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + "github.com/sigstore/cosign/v3/pkg/oci/signed" + cosignstatic "github.com/sigstore/cosign/v3/pkg/oci/static" + ctypes "github.com/sigstore/cosign/v3/pkg/types" ) func TestWriteSignatures(t *testing.T) { @@ -41,7 +45,7 @@ func TestWriteSignatures(t *testing.T) { want := 6 // Add 6 signatures for i := 0; i < want; i++ { - sig, err := static.NewSignature(nil, fmt.Sprintf("%d", i)) + sig, err := cosignstatic.NewSignature(nil, fmt.Sprintf("%d", i)) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } @@ -83,7 +87,7 @@ func TestWriteAttestations(t *testing.T) { want := 6 // Add 6 attestations for i := 0; i < want; i++ { - sig, err := static.NewAttestation([]byte(fmt.Sprintf("%d", i))) + sig, err := cosignstatic.NewAttestation([]byte(fmt.Sprintf("%d", i))) if err != nil { t.Fatalf("static.NewSignature() = %v", err) } @@ -111,3 +115,429 @@ func TestWriteAttestations(t *testing.T) { t.Fatalf("WriteAttestations() = %v", err) } } + +func TestReferrerManifest(t *testing.T) { + // Test referrerManifest.RawManifest() + rm := referrerManifest{ + Manifest: v1.Manifest{ + SchemaVersion: 2, + MediaType: types.OCIManifestSchema1, + Config: v1.Descriptor{ + MediaType: "application/vnd.oci.empty.v1+json", + Digest: v1.Hash{Algorithm: "sha256", Hex: "abc123"}, + Size: 100, + }, + Layers: []v1.Descriptor{}, + }, + ArtifactType: "test.artifact.type", + } + + manifestBytes, err := rm.RawManifest() + if err != nil { + t.Fatalf("RawManifest() = %v", err) + } + + if len(manifestBytes) == 0 { + t.Error("RawManifest returned empty bytes") + } + + // Test referrerManifest.MediaType() + mediaType, err := rm.MediaType() + if err != nil { + t.Fatalf("MediaType() = %v", err) + } + if mediaType != types.OCIManifestSchema1 { + t.Errorf("MediaType() = %s, want %s", mediaType, types.OCIManifestSchema1) + } + + // Test referrerManifest.targetRef() + repo := name.MustParseReference("gcr.io/test/repo").Context() + targetRef, err := rm.targetRef(repo) + if err != nil { + t.Fatalf("targetRef() = %v", err) + } + if targetRef == nil { + t.Error("targetRef returned nil") + } +} + +func TestTaggableManifest(t *testing.T) { + // Test taggableManifest.RawManifest() + tm := taggableManifest{ + raw: []byte(`{"test":"manifest"}`), + mediaType: types.DockerManifestSchema2, + } + + manifestBytes, err := tm.RawManifest() + if err != nil { + t.Fatalf("RawManifest() = %v", err) + } + if string(manifestBytes) != `{"test":"manifest"}` { + t.Errorf("RawManifest() = %s, want %s", string(manifestBytes), `{"test":"manifest"}`) + } + + // Test taggableManifest.MediaType() + mediaType, err := tm.MediaType() + if err != nil { + t.Fatalf("MediaType() = %v", err) + } + if mediaType != types.DockerManifestSchema2 { + t.Errorf("MediaType() = %s, want %s", mediaType, types.DockerManifestSchema2) + } +} + +func TestWriteAttestationNewBundleFormat(t *testing.T) { + // Save original functions + origHead := remoteHead + origWriteLayer := remoteWriteLayer + origPut := remotePut + t.Cleanup(func() { + remoteHead = origHead + remoteWriteLayer = origWriteLayer + remotePut = origPut + }) + + bundleBytes := []byte(`{"payload":"test","signatures":[]}`) + predicateType := "https://test.predicate.type" + digest := name.MustParseReference("gcr.io/test/image@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").(name.Digest) + + // Mock remoteHead to return a descriptor + remoteHead = func(name.Reference, ...remote.Option) (*v1.Descriptor, error) { + return &v1.Descriptor{ + MediaType: types.DockerManifestSchema2, + Digest: v1.Hash{Algorithm: "sha256", Hex: "abcdef1234567890"}, + Size: 100, + }, nil + } + + // Mock remoteWriteLayer to succeed + remoteWriteLayer = func(name.Repository, v1.Layer, ...remote.Option) error { + return nil + } + + // Mock remotePut to capture the manifest + var capturedManifest remote.Taggable + remotePut = func(_ name.Reference, manifest remote.Taggable, _ ...remote.Option) error { + capturedManifest = manifest + return nil + } + + err := WriteAttestationNewBundleFormat(digest, bundleBytes, predicateType) + if err != nil { + t.Fatalf("WriteAttestationNewBundleFormat() = %v", err) + } + + // Verify that a manifest was uploaded + if capturedManifest == nil { + t.Error("Expected manifest to be uploaded, but none was captured") + } + + // Verify it's a referrerManifest + refManifest, ok := capturedManifest.(referrerManifest) + if !ok { + t.Errorf("Expected referrerManifest, got %T", capturedManifest) + return + } + + // Verify the artifact type contains bundle media type + if refManifest.ArtifactType == "" { + t.Error("Expected ArtifactType to be set") + } + + // Verify annotations are set correctly + if refManifest.Annotations["dev.sigstore.bundle.content"] != "dsse-envelope" { + t.Errorf("Expected bundle.content annotation to be 'dsse-envelope', got %s", refManifest.Annotations["dev.sigstore.bundle.content"]) + } + if refManifest.Annotations["dev.sigstore.bundle.predicateType"] != predicateType { + t.Errorf("Expected predicateType annotation to be %s, got %s", predicateType, refManifest.Annotations["dev.sigstore.bundle.predicateType"]) + } +} + +func TestWriteAttestationsReferrer(t *testing.T) { + // Save original functions + origHead := remoteHead + origWriteLayer := remoteWriteLayer + origPut := remotePut + t.Cleanup(func() { + remoteHead = origHead + remoteWriteLayer = origWriteLayer + remotePut = origPut + }) + + digest := name.MustParseReference("gcr.io/test/image@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").(name.Digest) + + // Create a test signed entity with attestations + i, err := random.Image(300, 1) + if err != nil { + t.Fatalf("random.Image() = %v", err) + } + si := signed.Image(i) + + // Add an attestation + att, err := cosignstatic.NewAttestation([]byte("test-attestation")) + if err != nil { + t.Fatalf("static.NewAttestation() = %v", err) + } + si, err = mutate.AttachAttestationToImage(si, att) + if err != nil { + t.Fatalf("AttachAttestationToImage() = %v", err) + } + + // Mock remoteHead to return a descriptor + remoteHead = func(name.Reference, ...remote.Option) (*v1.Descriptor, error) { + return &v1.Descriptor{ + MediaType: types.DockerManifestSchema2, + Digest: v1.Hash{Algorithm: "sha256", Hex: "abcdef1234567890"}, + Size: 100, + }, nil + } + + // Mock remoteWriteLayer to succeed + remoteWriteLayer = func(name.Repository, v1.Layer, ...remote.Option) error { + return nil + } + + // Mock remotePut to capture the manifest + var capturedManifest remote.Taggable + remotePut = func(_ name.Reference, manifest remote.Taggable, _ ...remote.Option) error { + capturedManifest = manifest + return nil + } + + err = WriteAttestationsReferrer(digest, si) + if err != nil { + t.Fatalf("WriteAttestationsReferrer() = %v", err) + } + + // Verify that a manifest was uploaded + if capturedManifest == nil { + t.Error("Expected manifest to be uploaded, but none was captured") + } + + // Verify it's a referrerManifest + refManifest, ok := capturedManifest.(referrerManifest) + if !ok { + t.Errorf("Expected referrerManifest, got %T", capturedManifest) + return + } + + // Verify the artifact type is set to in-toto payload type + if refManifest.ArtifactType != ctypes.IntotoPayloadType { + t.Errorf("Expected ArtifactType to be %s, got %s", ctypes.IntotoPayloadType, refManifest.ArtifactType) + } + + // Verify annotations include created timestamp + if _, exists := refManifest.Annotations["org.opencontainers.image.created"]; !exists { + t.Error("Expected created annotation to be set") + } + + // Verify we have at least one layer + if len(refManifest.Layers) == 0 { + t.Error("Expected at least one layer in manifest") + } +} + +func TestWriteAttestationsReferrerPreservesAnnotations(t *testing.T) { + // Save original functions + origHead := remoteHead + origWriteLayer := remoteWriteLayer + origPut := remotePut + t.Cleanup(func() { + remoteHead = origHead + remoteWriteLayer = origWriteLayer + remotePut = origPut + }) + + digest := name.MustParseReference("gcr.io/test/image@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").(name.Digest) + + // Create a test signed entity with attestations that have annotations + i, err := random.Image(300, 1) + if err != nil { + t.Fatalf("random.Image() = %v", err) + } + si := signed.Image(i) + + // Create attestation with certificate chain and custom annotations + certPEM := []byte("-----BEGIN CERTIFICATE-----\ntest-cert\n-----END CERTIFICATE-----\n") + chainPEM := []byte("-----BEGIN CERTIFICATE-----\ntest-chain\n-----END CERTIFICATE-----\n") + att, err := cosignstatic.NewAttestation([]byte("test-attestation"), + cosignstatic.WithCertChain(certPEM, chainPEM), + cosignstatic.WithAnnotations(map[string]string{ + "predicateType": "https://cosign.sigstore.dev/attestation/v1", + }), + ) + if err != nil { + t.Fatalf("static.NewAttestation() = %v", err) + } + si, err = mutate.AttachAttestationToImage(si, att) + if err != nil { + t.Fatalf("AttachAttestationToImage() = %v", err) + } + + // Mock remoteHead to return a descriptor + remoteHead = func(name.Reference, ...remote.Option) (*v1.Descriptor, error) { + return &v1.Descriptor{ + MediaType: types.DockerManifestSchema2, + Digest: v1.Hash{Algorithm: "sha256", Hex: "abcdef1234567890"}, + Size: 100, + }, nil + } + + // Mock remoteWriteLayer to succeed + remoteWriteLayer = func(name.Repository, v1.Layer, ...remote.Option) error { + return nil + } + + // Mock remotePut to capture the manifest + var capturedManifest remote.Taggable + remotePut = func(_ name.Reference, manifest remote.Taggable, _ ...remote.Option) error { + capturedManifest = manifest + return nil + } + + err = WriteAttestationsReferrer(digest, si) + if err != nil { + t.Fatalf("WriteAttestationsReferrer() = %v", err) + } + + // Verify that a manifest was uploaded + if capturedManifest == nil { + t.Fatal("Expected manifest to be uploaded, but none was captured") + } + + refManifest, ok := capturedManifest.(referrerManifest) + if !ok { + t.Fatalf("Expected referrerManifest, got %T", capturedManifest) + } + + if len(refManifest.Layers) == 0 { + t.Fatal("Expected at least one layer in manifest") + } + + // Verify per-layer annotations are preserved (this is the bug fix) + layerAnnotations := refManifest.Layers[0].Annotations + if layerAnnotations == nil { + t.Fatal("Expected layer annotations to be preserved, got nil") + } + + expectedKeys := []string{ + "dev.sigstore.cosign/certificate", + "dev.sigstore.cosign/chain", + "predicateType", + } + for _, key := range expectedKeys { + if _, exists := layerAnnotations[key]; !exists { + t.Errorf("Expected layer annotation %q to be present", key) + } + } +} + +func TestWriteReferrer(t *testing.T) { + // Save original functions + origHead := remoteHead + origWriteLayer := remoteWriteLayer + origPut := remotePut + t.Cleanup(func() { + remoteHead = origHead + remoteWriteLayer = origWriteLayer + remotePut = origPut + }) + + digest := name.MustParseReference("gcr.io/test/image@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").(name.Digest) + + // Create a test layer + testLayer := static.NewLayer([]byte("test-data"), "application/octet-stream") + layers := []v1.Layer{testLayer} + annotations := map[string]string{ + "test.annotation": "test-value", + } + artifactType := "test.artifact.type" + + // Mock remoteHead to return a descriptor + remoteHead = func(name.Reference, ...remote.Option) (*v1.Descriptor, error) { + return &v1.Descriptor{ + MediaType: types.DockerManifestSchema2, + Digest: v1.Hash{Algorithm: "sha256", Hex: "abcdef1234567890"}, + Size: 100, + }, nil + } + + // Mock remoteWriteLayer to succeed + remoteWriteLayer = func(name.Repository, v1.Layer, ...remote.Option) error { + return nil + } + + // Mock remotePut to capture the manifest + var capturedManifest remote.Taggable + remotePut = func(_ name.Reference, manifest remote.Taggable, _ ...remote.Option) error { + capturedManifest = manifest + return nil + } + + err := WriteReferrer(digest, artifactType, layers, annotations) + if err != nil { + t.Fatalf("WriteReferrer() = %v", err) + } + + // Verify that a manifest was uploaded + if capturedManifest == nil { + t.Error("Expected manifest to be uploaded, but none was captured") + } + + // Verify it's a referrerManifest + refManifest, ok := capturedManifest.(referrerManifest) + if !ok { + t.Errorf("Expected referrerManifest, got %T", capturedManifest) + return + } + + // Verify the artifact type is set correctly + if refManifest.ArtifactType != artifactType { + t.Errorf("Expected ArtifactType to be %s, got %s", artifactType, refManifest.ArtifactType) + } + + // Verify annotations are passed through + if refManifest.Annotations["test.annotation"] != "test-value" { + t.Errorf("Expected annotation to be 'test-value', got %s", refManifest.Annotations["test.annotation"]) + } + + // Verify we have the expected number of layers + if len(refManifest.Layers) != 1 { + t.Errorf("Expected 1 layer, got %d", len(refManifest.Layers)) + } + + // Verify the subject is set + if refManifest.Subject == nil { + t.Error("Expected Subject to be set") + } + + // Verify config descriptor + if refManifest.Config.ArtifactType != artifactType { + t.Errorf("Expected Config.ArtifactType to be %s, got %s", artifactType, refManifest.Config.ArtifactType) + } +} + +func TestWriteReferrerErrorHandling(t *testing.T) { + // Save original functions + origHead := remoteHead + t.Cleanup(func() { + remoteHead = origHead + }) + + digest := name.MustParseReference("gcr.io/test/image@sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").(name.Digest) + layers := []v1.Layer{} + annotations := map[string]string{} + + // Mock remoteHead to return an error + remoteHead = func(name.Reference, ...remote.Option) (*v1.Descriptor, error) { + return nil, fmt.Errorf("remote head failed") + } + + err := WriteReferrer(digest, "test.type", layers, annotations) + if err == nil { + t.Error("Expected error from WriteReferrer when remoteHead fails") + } + if !strings.Contains(err.Error(), "remote head failed") { + t.Errorf("Expected error to contain 'remote head failed', got %v", err) + } +} diff --git a/pkg/oci/signature/layer.go b/pkg/oci/signature/layer.go deleted file mode 100644 index 4bd5e456c77..00000000000 --- a/pkg/oci/signature/layer.go +++ /dev/null @@ -1,151 +0,0 @@ -// -// 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 -} diff --git a/pkg/oci/signature/layer_test.go b/pkg/oci/signature/layer_test.go deleted file mode 100644 index e88157d2150..00000000000 --- a/pkg/oci/signature/layer_test.go +++ /dev/null @@ -1,507 +0,0 @@ -// -// 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") } diff --git a/pkg/oci/signatures.go b/pkg/oci/signatures.go index 32f2f890c03..92526319506 100644 --- a/pkg/oci/signatures.go +++ b/pkg/oci/signatures.go @@ -19,7 +19,7 @@ import ( "crypto/x509" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" ) // Signatures represents a set of signatures that are associated with a particular diff --git a/pkg/oci/signed/image.go b/pkg/oci/signed/image.go index 2bcade64b02..ccdc383efab 100644 --- a/pkg/oci/signed/image.go +++ b/pkg/oci/signed/image.go @@ -20,8 +20,8 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/pkg/oci" - "github.com/sigstore/cosign/v2/pkg/oci/empty" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/empty" ) // Image returns an oci.SignedImage form of the v1.Image with no signatures. diff --git a/pkg/oci/signed/index.go b/pkg/oci/signed/index.go index b686b4f62e5..61da79403c2 100644 --- a/pkg/oci/signed/index.go +++ b/pkg/oci/signed/index.go @@ -20,8 +20,8 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/pkg/oci" - "github.com/sigstore/cosign/v2/pkg/oci/empty" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/empty" ) // ImageIndex returns an oci.SignedImageIndex form of the v1.ImageIndex with diff --git a/pkg/oci/signed/index_test.go b/pkg/oci/signed/index_test.go index 11523187df8..55c71b1baf5 100644 --- a/pkg/oci/signed/index_test.go +++ b/pkg/oci/signed/index_test.go @@ -22,7 +22,7 @@ import ( "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/v3/pkg/oci" ) func TestImageIndex(t *testing.T) { diff --git a/pkg/oci/static/file.go b/pkg/oci/static/file.go index 18ec65c3af8..5297d8666d0 100644 --- a/pkg/oci/static/file.go +++ b/pkg/oci/static/file.go @@ -22,10 +22,10 @@ import ( "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" + payloadsize "github.com/sigstore/cosign/v3/internal/pkg/cosign/payload/size" + "github.com/sigstore/cosign/v3/internal/pkg/now" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/signed" ) // NewFile constructs a new v1.Image with the provided payload. diff --git a/pkg/oci/static/options.go b/pkg/oci/static/options.go index b240fb228ae..f0515992ea4 100644 --- a/pkg/oci/static/options.go +++ b/pkg/oci/static/options.go @@ -19,8 +19,8 @@ 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" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + ctypes "github.com/sigstore/cosign/v3/pkg/types" ) // Option is a functional option for customizing static signatures. diff --git a/pkg/oci/static/options_test.go b/pkg/oci/static/options_test.go index d63ec0fb01a..0f07dee2b0d 100644 --- a/pkg/oci/static/options_test.go +++ b/pkg/oci/static/options_test.go @@ -21,8 +21,8 @@ import ( "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" + cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + ctypes "github.com/sigstore/cosign/v3/pkg/types" ) func TestOptions(t *testing.T) { diff --git a/pkg/oci/static/signature.go b/pkg/oci/static/signature.go index 406386347f2..817c7c6786f 100644 --- a/pkg/oci/static/signature.go +++ b/pkg/oci/static/signature.go @@ -23,8 +23,8 @@ import ( 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/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" "github.com/sigstore/sigstore/pkg/cryptoutils" ) diff --git a/pkg/oci/static/signature_test.go b/pkg/oci/static/signature_test.go index 1ca8e96e3df..07096a8275d 100644 --- a/pkg/oci/static/signature_test.go +++ b/pkg/oci/static/signature_test.go @@ -24,7 +24,7 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" ) func TestNewSignatureBasic(t *testing.T) { diff --git a/pkg/oci/walk/walk.go b/pkg/oci/walk/walk.go index 097d05bfa30..ec40c62b8da 100644 --- a/pkg/oci/walk/walk.go +++ b/pkg/oci/walk/walk.go @@ -18,8 +18,8 @@ package walk import ( "context" - "github.com/sigstore/cosign/v2/pkg/oci" - "github.com/sigstore/cosign/v2/pkg/oci/mutate" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" ) // Fn is the signature of the callback supplied to SignedEntity. diff --git a/pkg/oci/walk/walk_test.go b/pkg/oci/walk/walk_test.go index 8ba5b225508..3f2d3d7a56f 100644 --- a/pkg/oci/walk/walk_test.go +++ b/pkg/oci/walk/walk_test.go @@ -22,8 +22,8 @@ import ( "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/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/signed" ) func TestMapImage(t *testing.T) { diff --git a/pkg/policy/attestation.go b/pkg/policy/attestation.go index 63377d44221..b23631ac06b 100644 --- a/pkg/policy/attestation.go +++ b/pkg/policy/attestation.go @@ -23,9 +23,9 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" + "github.com/sigstore/cosign/v3/pkg/oci" ) // PayloadProvider is a subset of oci.Signature that only provides the @@ -77,18 +77,22 @@ func AttestationToPayloadJSON(_ context.Context, predicateType string, verifiedA } 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 { + val, ok := payloadData["payload"] + if !ok { return nil, "", fmt.Errorf("could not find payload in payload data") } + payloadStr, ok := val.(string) + if !ok { + return nil, "", fmt.Errorf("invalid payload: payload field is not a string (got %T)", val) + } + decodedPayload, err = base64.StdEncoding.DecodeString(payloadStr) + if err != nil { + return nil, "", fmt.Errorf("decoding payload: %w", err) + } // Only apply the policy against the requested predicate type - var statement in_toto.Statement - if err := json.Unmarshal(decodedPayload, &statement); err != nil { + statement := &attestation.Statement{} + if err := statement.UnmarshalJSON(decodedPayload); err != nil { return nil, "", fmt.Errorf("unmarshal in-toto statement: %w", err) } if statement.PredicateType != predicateURI { @@ -102,7 +106,7 @@ func AttestationToPayloadJSON(_ context.Context, predicateType string, verifiedA var payload []byte switch predicateType { case options.PredicateCustom: - payload, err = json.Marshal(statement) + payload, err = statement.MarshalJSON() if err != nil { return nil, statement.PredicateType, fmt.Errorf("generating CosignStatement: %w", err) } @@ -153,7 +157,7 @@ func AttestationToPayloadJSON(_ context.Context, predicateType string, verifiedA } default: // Valid URI type reaches here. - payload, err = json.Marshal(statement) + payload, err = statement.MarshalJSON() if err != nil { return nil, statement.PredicateType, fmt.Errorf("generating Statement: %w", err) } diff --git a/pkg/policy/attestation_test.go b/pkg/policy/attestation_test.go index 908f0696081..668ce36db6b 100644 --- a/pkg/policy/attestation_test.go +++ b/pkg/policy/attestation_test.go @@ -28,11 +28,12 @@ import ( 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" + in_toto_attest "github.com/in-toto/attestation/go/v1" + "github.com/sigstore/cosign/v3/pkg/cosign/attestation" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/static" + "google.golang.org/protobuf/encoding/protojson" ) type failingAttestation struct { @@ -107,6 +108,7 @@ func TestFailures(t *testing.T) { }{{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: `{"payload":null}`, wantErrSubstring: "payload field is not a string"}, {payload: `{"payloadType":"finebutnopayload"}`, wantErrSubstring: "could not find payload"}, {payload: invalidTotoStatement, wantErrSubstring: "decoding payload: illegal base64"}, } @@ -148,8 +150,8 @@ func TestAttestationToPayloadJson(t *testing.T) { } switch fileName { case "custom": - var intoto in_toto.Statement - if err := json.Unmarshal(jsonBytes, &intoto); err != nil { + var intoto in_toto_attest.Statement + if err := protojson.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) @@ -232,7 +234,7 @@ func getDirFiles(t *testing.T, dir string) []string { if err != nil { t.Fatalf("Failed to read dir : %s ReadFile() = %s", dir, err) } - ret := []string{} + ret := make([]string, 0, len(files)) for _, file := range files { ret = append(ret, file.Name()) } diff --git a/pkg/policy/eval.go b/pkg/policy/eval.go index 9e33a8a005d..b29d86b342a 100644 --- a/pkg/policy/eval.go +++ b/pkg/policy/eval.go @@ -20,7 +20,7 @@ import ( "fmt" "cuelang.org/go/cue/cuecontext" - "github.com/sigstore/cosign/v2/pkg/cosign/rego" + "github.com/sigstore/cosign/v3/pkg/cosign/rego" ) // EvaluatePolicyAgainstJson is used to run a policy engine against JSON bytes. diff --git a/pkg/providers/all/all.go b/pkg/providers/all/all.go index 6f5952e7e98..7082e4c6e81 100644 --- a/pkg/providers/all/all.go +++ b/pkg/providers/all/all.go @@ -16,20 +16,20 @@ package all import ( - "github.com/sigstore/cosign/v2/pkg/providers" + "github.com/sigstore/cosign/v3/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" + _ "github.com/sigstore/cosign/v3/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" + _ "github.com/sigstore/cosign/v3/pkg/providers/buildkite" + _ "github.com/sigstore/cosign/v3/pkg/providers/envvar" + _ "github.com/sigstore/cosign/v3/pkg/providers/filesystem" + _ "github.com/sigstore/cosign/v3/pkg/providers/google" + _ "github.com/sigstore/cosign/v3/pkg/providers/spiffe" ) // Alias these methods, so that folks can import this to get all providers. diff --git a/pkg/providers/buildkite/buildkite.go b/pkg/providers/buildkite/buildkite.go index f225e68d1f7..63991710184 100644 --- a/pkg/providers/buildkite/buildkite.go +++ b/pkg/providers/buildkite/buildkite.go @@ -22,8 +22,8 @@ import ( "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" + "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/providers" ) func init() { diff --git a/pkg/providers/envvar/env.go b/pkg/providers/envvar/env.go index 67de28fa953..d6c9df85a83 100644 --- a/pkg/providers/envvar/env.go +++ b/pkg/providers/envvar/env.go @@ -18,8 +18,8 @@ package envvar import ( "context" - "github.com/sigstore/cosign/v2/pkg/cosign/env" - "github.com/sigstore/cosign/v2/pkg/providers" + "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/providers" ) func init() { diff --git a/pkg/providers/envvar/env_test.go b/pkg/providers/envvar/env_test.go index cdc8f2ae9f3..2ecd28a8e9d 100644 --- a/pkg/providers/envvar/env_test.go +++ b/pkg/providers/envvar/env_test.go @@ -20,7 +20,7 @@ import ( "fmt" "testing" - "github.com/sigstore/cosign/v2/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/env" ) func TestEnvVar(t *testing.T) { diff --git a/pkg/providers/filesystem/filesystem.go b/pkg/providers/filesystem/filesystem.go index 56b57c34720..334aaf67dd5 100644 --- a/pkg/providers/filesystem/filesystem.go +++ b/pkg/providers/filesystem/filesystem.go @@ -19,7 +19,7 @@ import ( "context" "os" - "github.com/sigstore/cosign/v2/pkg/providers" + "github.com/sigstore/cosign/v3/pkg/providers" ) func init() { diff --git a/pkg/providers/github/github.go b/pkg/providers/github/github.go index f7427d57857..62d08512328 100644 --- a/pkg/providers/github/github.go +++ b/pkg/providers/github/github.go @@ -24,8 +24,8 @@ import ( "strings" "time" - "github.com/sigstore/cosign/v2/pkg/cosign/env" - "github.com/sigstore/cosign/v2/pkg/providers" + "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/providers" ) const ( @@ -68,7 +68,7 @@ func (ga *githubActions) Provide(ctx context.Context, audience string) (string, // Retry up to 3 times. for i := 0; ; i++ { - req.Header.Add("Authorization", "bearer "+env.Getenv(env.VariableGitHubRequestToken)) + req.Header.Set("Authorization", "bearer "+env.Getenv(env.VariableGitHubRequestToken)) resp, err := client.Do(req) if err != nil { if i == 2 { diff --git a/pkg/providers/github/github_test.go b/pkg/providers/github/github_test.go new file mode 100644 index 00000000000..dec22e6b964 --- /dev/null +++ b/pkg/providers/github/github_test.go @@ -0,0 +1,75 @@ +// +// 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 github + +import ( + "context" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + + "github.com/sigstore/cosign/v3/pkg/cosign/env" +) + +func TestProvide_NoDuplicateAuthHeaders(t *testing.T) { + var attempts atomic.Int32 + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n := attempts.Add(1) + + // Verify exactly one Authorization header on every attempt. + authHeaders := r.Header.Values("Authorization") + if len(authHeaders) != 1 { + t.Errorf("attempt %d: want 1 Authorization header, got %d: %v", n, len(authHeaders), authHeaders) + } + + if n < 3 { + // Force retries by closing the connection. + hj, ok := w.(http.Hijacker) + if !ok { + t.Fatal("server does not support hijacking") + } + conn, _, err := hj.Hijack() + if err != nil { + t.Fatal(err) + } + conn.Close() + return + } + + // Succeed on the 3rd attempt. + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"value":"test-token"}`)) //nolint:errcheck + })) + defer ts.Close() + + t.Setenv(env.VariableGitHubRequestToken.String(), "fake-token") + // The provider appends "&audience=..." so the base URL needs a query param. + t.Setenv(env.VariableGitHubRequestURL.String(), ts.URL+"?test=1") + + ga := &githubActions{} + token, err := ga.Provide(context.Background(), "sigstore") + if err != nil { + t.Fatalf("Provide: %v", err) + } + if token != "test-token" { + t.Errorf("want token %q, got %q", "test-token", token) + } + if got := attempts.Load(); got != 3 { + t.Errorf("want 3 attempts, got %d", got) + } +} diff --git a/pkg/providers/google/google.go b/pkg/providers/google/google.go index fc186dcfc50..b5484c6f273 100644 --- a/pkg/providers/google/google.go +++ b/pkg/providers/google/google.go @@ -17,14 +17,14 @@ 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" + "cloud.google.com/go/compute/metadata" + + "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/providers" ) func init() { @@ -36,19 +36,8 @@ 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" { + if metadata.OnGCE() { // 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") diff --git a/pkg/providers/spiffe/spiffe.go b/pkg/providers/spiffe/spiffe.go index 2e134ca7af0..3672f1e6960 100644 --- a/pkg/providers/spiffe/spiffe.go +++ b/pkg/providers/spiffe/spiffe.go @@ -21,8 +21,8 @@ import ( "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/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/providers" "github.com/spiffe/go-spiffe/v2/workloadapi" ) diff --git a/pkg/signature/annotations.go b/pkg/signature/annotations.go index 105d71e92d2..4003ebc6c4e 100644 --- a/pkg/signature/annotations.go +++ b/pkg/signature/annotations.go @@ -39,7 +39,7 @@ func (a *AnnotationsMap) Set(s string) error { } func (a *AnnotationsMap) String() string { - s := []string{} + s := make([]string, 0, len(a.Annotations)) for k, v := range a.Annotations { s = append(s, fmt.Sprintf("%s=%s", k, v)) } diff --git a/pkg/signature/keys.go b/pkg/signature/keys.go index dfac964725d..3b7879a6f4f 100644 --- a/pkg/signature/keys.go +++ b/pkg/signature/keys.go @@ -21,12 +21,12 @@ import ( "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/cosign/v3/pkg/blob" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/git" + "github.com/sigstore/cosign/v3/pkg/cosign/git/gitlab" + "github.com/sigstore/cosign/v3/pkg/cosign/kubernetes" + "github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" @@ -72,7 +72,7 @@ func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto. return signature.LoadVerifier(pubKey, hashAlgorithm) } -func loadKey(keyPath string, pf cosign.PassFunc) (signature.SignerVerifier, error) { +func loadKey(keyPath string, pf cosign.PassFunc, defaultLoadOptions *[]signature.LoadOption) (signature.SignerVerifier, error) { kb, err := blob.LoadFileOrURL(keyPath) if err != nil { return nil, err @@ -84,7 +84,7 @@ func loadKey(keyPath string, pf cosign.PassFunc) (signature.SignerVerifier, erro return nil, err } } - return cosign.LoadPrivateKey(kb, pass) + return cosign.LoadPrivateKey(kb, pass, defaultLoadOptions) } // LoadPublicKeyRaw loads a verifier from a PEM-encoded public key @@ -97,10 +97,10 @@ func LoadPublicKeyRaw(raw []byte, hashAlgorithm crypto.Hash) (signature.Verifier } func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) { - return SignerVerifierFromKeyRef(ctx, keyRef, pf) + return SignerVerifierFromKeyRef(ctx, keyRef, pf, nil) } -func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.SignerVerifier, error) { +func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc, defaultLoadOptions *[]signature.LoadOption) (signature.SignerVerifier, error) { switch { case strings.HasPrefix(keyRef, pkcs11key.ReferenceScheme): pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig() @@ -129,7 +129,7 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass } if len(s.Data) > 0 { - return cosign.LoadPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"]) + return cosign.LoadPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"], defaultLoadOptions) } case strings.HasPrefix(keyRef, gitlab.ReferenceScheme): split := strings.Split(keyRef, "://") @@ -150,7 +150,7 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass return nil, err } - return cosign.LoadPrivateKey([]byte(pk), []byte(pass)) + return cosign.LoadPrivateKey([]byte(pk), []byte(pass), defaultLoadOptions) } if strings.Contains(keyRef, "://") { @@ -165,7 +165,7 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass // ProviderNotFoundError is okay; loadKey handles other URL schemes } - return loadKey(keyRef, pf) + return loadKey(keyRef, pf, defaultLoadOptions) } func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier, error) { diff --git a/pkg/signature/keys_test.go b/pkg/signature/keys_test.go index 0365aa34911..7e2710ab2bc 100644 --- a/pkg/signature/keys_test.go +++ b/pkg/signature/keys_test.go @@ -21,8 +21,8 @@ import ( "os" "testing" - "github.com/sigstore/cosign/v2/pkg/blob" - "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/blob" + "github.com/sigstore/cosign/v3/pkg/cosign" sigsignature "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" ) @@ -135,7 +135,7 @@ func TestSignerVerifierFromEnvVar(t *testing.T) { 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 { + if _, err := SignerVerifierFromKeyRef(ctx, "env://MY_ENV_VAR", passFunc, nil); err != nil { t.Fatalf("SignerVerifierFromKeyRef returned error: %v", err) } } diff --git a/internal/pkg/cosign/dsse.go b/pkg/types/predicate.go similarity index 56% rename from internal/pkg/cosign/dsse.go rename to pkg/types/predicate.go index 690e534508a..8a187192018 100644 --- a/internal/pkg/cosign/dsse.go +++ b/pkg/types/predicate.go @@ -1,3 +1,4 @@ +// // Copyright 2021 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,19 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cosign - -import ( - "context" - "crypto" - "io" +package types //nolint: revive // that is a valid package name :) - "github.com/sigstore/cosign/v2/pkg/oci" +const ( + CosignSignPredicateType = "https://sigstore.dev/cosign/sign/v1" ) - -// 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) -} diff --git a/release/cloudbuild.yaml b/release/cloudbuild.yaml index 0edc5bda010..2b14903efe8 100644 --- a/release/cloudbuild.yaml +++ b/release/cloudbuild.yaml @@ -32,20 +32,19 @@ steps: echo "Checking out ${_GIT_TAG}" git checkout ${_GIT_TAG} - - name: 'ghcr.io/sigstore/cosign/cosign:v2.5.2-dev@sha256:14a20131240190350e18f002bdd61345d2803eff370913737392281e834ee22a' + - name: 'ghcr.io/sigstore/cosign/cosign:v3.0.6-dev@sha256:fd2e25faec7ea4c47bfbe6f50f00ff0e465fcea1f6d121eed7491b79278b19f7' dir: "go/src/sigstore/cosign" env: - TUF_ROOT=/tmp args: - 'verify' - - 'ghcr.io/gythialy/golang-cross:v1.24.5-0@sha256:492c51e60ed27ff597511b0a24e6c5acb6e3e2e97bb68d7bd35f81a7e3dfa4d0' + - 'ghcr.io/gythialy/golang-cross:v1.26.3-0@sha256:9f7a53d7205e2f1f2742d624ff0b6e1531e8c22863dfb24ca966be5efbdee48b' - '--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.5-0" - + - "https://github.com/gythialy/golang-cross/.github/workflows/release-golang-cross.yml@refs/tags/v1.26.3-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.5-0@sha256:492c51e60ed27ff597511b0a24e6c5acb6e3e2e97bb68d7bd35f81a7e3dfa4d0 + - name: ghcr.io/gythialy/golang-cross:v1.26.3-0@sha256:9f7a53d7205e2f1f2742d624ff0b6e1531e8c22863dfb24ca966be5efbdee48b entrypoint: /bin/sh dir: "go/src/sigstore/cosign" env: @@ -68,12 +67,13 @@ steps: gcloud auth configure-docker \ && make release - - name: ghcr.io/gythialy/golang-cross:v1.24.5-0@sha256:492c51e60ed27ff597511b0a24e6c5acb6e3e2e97bb68d7bd35f81a7e3dfa4d0 + - name: ghcr.io/gythialy/golang-cross:v1.26.3-0@sha256:9f7a53d7205e2f1f2742d624ff0b6e1531e8c22863dfb24ca966be5efbdee48b entrypoint: 'bash' dir: "go/src/sigstore/cosign" env: - "GOPATH=/workspace/go" - "GOBIN=/workspace/bin" + - "DOCKER_CONFIG=/workspace/.docker" - PROJECT_ID=${PROJECT_ID} - KEY_LOCATION=${_KEY_LOCATION} - KEY_RING=${_KEY_RING} @@ -91,7 +91,44 @@ steps: - '-c' - | echo $$GITHUB_TOKEN | docker login ghcr.io -u $$GITHUB_USER --password-stdin \ - && make sign-release-images && make copy-signed-release-to-ghcr || true + && make sign-release-images || true + + - name: 'ghcr.io/oras-project/oras:v1.3.2@sha256:46c55ba0eac848ade573594ef23fa5b1784227f151dab6eac38de2afd0b9c382' + env: + - 'DOCKER_CONFIG=/workspace/.docker' + args: + - 'copy' + - '-r' + - 'gcr.io/${PROJECT_ID}/cosign:${_GIT_TAG}' + - '${_GHCR_PREFIX}/cosign:${_GIT_TAG}' + + - name: 'ghcr.io/oras-project/oras:v1.3.2@sha256:46c55ba0eac848ade573594ef23fa5b1784227f151dab6eac38de2afd0b9c382' + env: + - 'DOCKER_CONFIG=/workspace/.docker' + args: + - 'copy' + - '-r' + - '${_GHCR_PREFIX}/cosign:${_GIT_TAG}' + - '${_GHCR_PREFIX}/cosign:latest' + + - name: 'ghcr.io/oras-project/oras:v1.3.2@sha256:46c55ba0eac848ade573594ef23fa5b1784227f151dab6eac38de2afd0b9c382' + env: + - 'DOCKER_CONFIG=/workspace/.docker' + args: + - 'copy' + - '-r' + - 'gcr.io/${PROJECT_ID}/cosign:${_GIT_TAG}-dev' + - '${_GHCR_PREFIX}/cosign:${_GIT_TAG}-dev' + + - name: 'ghcr.io/oras-project/oras:v1.3.2@sha256:46c55ba0eac848ade573594ef23fa5b1784227f151dab6eac38de2afd0b9c382' + env: + - 'DOCKER_CONFIG=/workspace/.docker' + args: + - 'copy' + - '-r' + - '${_GHCR_PREFIX}/cosign:${_GIT_TAG}-dev' + - '${_GHCR_PREFIX}/cosign:latest-dev' + availableSecrets: secretManager: @@ -124,3 +161,5 @@ substitutions: _KEY_VERSION: '1' _KEY_LOCATION: 'global' _GITHUB_USER: 'placeholder' + _GHCR_PREFIX: 'ghcr.io/sigstore/cosign' + diff --git a/release/release.mk b/release/release.mk index 38402801e6e..7afaa8c5233 100644 --- a/release/release.mk +++ b/release/release.mk @@ -19,14 +19,3 @@ sign-release-images: ko .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 --force=true $(GHCR_PREFIX)/cosign:$(GIT_VERSION) $(GHCR_PREFIX)/cosign:latest - cosign copy $(KO_PREFIX)/cosign:$(GIT_VERSION)-dev $(GHCR_PREFIX)/cosign:$(GIT_VERSION)-dev - cosign copy --force=true $(GHCR_PREFIX)/cosign:$(GIT_VERSION)-dev $(GHCR_PREFIX)/cosign:latest-dev diff --git a/test/config/gettoken/gettoken.yaml b/test/config/gettoken/gettoken.yaml index 16012b072fb..e5608621268 100644 --- a/test/config/gettoken/gettoken.yaml +++ b/test/config/gettoken/gettoken.yaml @@ -21,7 +21,7 @@ spec: spec: containers: - name: gettoken - image: ko://github.com/sigstore/cosign/v2/test/cmd/getoidctoken + image: ko://github.com/sigstore/cosign/v3/test/cmd/getoidctoken env: - name: OIDC_FILE value: "/var/run/sigstore/cosign/oidc-token" diff --git a/test/e2e_attach_test.go b/test/e2e_attach_test.go index 7385da4f4a6..f157d5294ac 100644 --- a/test/e2e_attach_test.go +++ b/test/e2e_attach_test.go @@ -26,31 +26,25 @@ import ( "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/attach" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/download" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + cliverify "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" + cert_test "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" ) func TestAttachSignature(t *testing.T) { @@ -73,11 +67,11 @@ func TestAttachSignature(t *testing.T) { // Scenario 1: attach a single signature with certificate and certificate chain to an artifact // and verify it using the root certificate. - rootCert1, rootKey1, _ := GenerateRootCa() + rootCert1, rootKey1, _ := cert_test.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) + subCert1, subKey1, _ := cert_test.GenerateSubordinateCa(rootCert1, rootKey1) + leafCert1, privKey1, _ := cert_test.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) @@ -122,11 +116,11 @@ func TestAttachSignature(t *testing.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() + rootCert2, rootKey2, _ := cert_test.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) + subCert2, subKey2, _ := cert_test.GenerateSubordinateCa(rootCert2, rootKey2) + leafCert2, privKey2, _ := cert_test.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) @@ -166,82 +160,6 @@ func TestAttachSignature(t *testing.T) { 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() @@ -257,9 +175,9 @@ func TestAttachWithRekorBundle(t *testing.T) { 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) + rootCert, rootKey, _ := cert_test.GenerateRootCa() + subCert, subKey, _ := cert_test.GenerateSubordinateCa(rootCert, rootKey) + leafCert, privKey, _ := cert_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}) diff --git a/test/e2e_insecure_registry_test.go b/test/e2e_insecure_registry_test.go index 0da6ec6380d..d59273797e4 100644 --- a/test/e2e_insecure_registry_test.go +++ b/test/e2e_insecure_registry_test.go @@ -22,16 +22,20 @@ import ( "net/http" "os" "path" + "path/filepath" "testing" + "time" "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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/attest" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/initialize" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" + cliverify "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v3/pkg/cosign" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" ) const ( @@ -56,7 +60,13 @@ func TestInsecureRegistry(t *testing.T) { useOCI11 := os.Getenv("oci11Var") != "" rekorURL := os.Getenv(rekorURLVar) - must(downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td), t) + + ctx := context.Background() + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + rootPath := os.Getenv("TUF_ROOT_JSON") + mirror := os.Getenv("TUF_MIRROR") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) ko := options.KeyOpts{ KeyRef: privKey, @@ -64,36 +74,130 @@ func TestInsecureRegistry(t *testing.T) { RekorURL: rekorURL, SkipConfirmation: true, } + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + + // Sign without bundle format so := options.SignOptions{ Upload: true, TlogUpload: true, } - mustErr(sign.SignCmd(ro, ko, so, []string{imgName}), t) + mustErr(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) so.Registry = options.RegistryOptions{ - AllowInsecure: true, + AllowInsecure: true, + AllowHTTPRegistry: true, } if useOCI11 { so.RegistryExperimental = options.RegistryExperimentalOptions{ RegistryReferrersMode: options.RegistryReferrersModeOCI11, } } - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(t.Context(), 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, + AllowInsecure: true, + AllowHTTPRegistry: true, }, } if useOCI11 { cmd.ExperimentalOCI11 = true } must(cmd.Exec(context.Background(), []string{imgName}), t) + + // Sign new image with new bundle format + // (Must be a new image or the old bundle may be verified instead) + imgName = path.Join(repo, "cosign-registry-e2e-2") + cleanup2 := makeImageIndexWithInsecureRegistry(t, imgName) + defer cleanup2() + + so.NewBundleFormat = true + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) + cmd.NewBundleFormat = true + must(cmd.Exec(context.Background(), []string{imgName}), t) +} + +func TestAttestInsecureRegistry(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) + + rekorURL := os.Getenv(rekorURLVar) + + ctx := context.Background() + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + rootPath := os.Getenv("TUF_ROOT_JSON") + mirror := os.Getenv("TUF_MIRROR") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + ko := options.KeyOpts{ + KeyRef: privKey, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, + } + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + + slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` + slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") + if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0o600); err != nil { + t.Fatal(err) + } + + // Attest without bundle + attestCmd := attest.AttestCommand{ + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + RekorEntryType: "dsse", + TlogUpload: true, + RegistryOptions: options.RegistryOptions{ + AllowInsecure: true, + AllowHTTPRegistry: true, + }, + } + must(attestCmd.Exec(ctx, imgName), t) + verifyAttestation := cliverify.VerifyAttestationCommand{ + KeyRef: pubKey, + PredicateType: "slsaprovenance", + RegistryOptions: options.RegistryOptions{ + AllowInsecure: true, + AllowHTTPRegistry: true, + }, + } + must(verifyAttestation.Exec(ctx, []string{imgName}), t) + + // Attest with new bundle + imgName = path.Join(repo, "cosign-registry-e2e-2") + cleanup2 := makeImageIndexWithInsecureRegistry(t, imgName) + defer cleanup2() + + ko.NewBundleFormat = true + attestCmd.KeyOpts = ko + must(attestCmd.Exec(ctx, imgName), t) + verifyAttestation.CommonVerifyOptions.NewBundleFormat = true + verifyAttestation.IgnoreTlog = false + must(verifyAttestation.Exec(ctx, []string{imgName}), t) } func makeImageIndexWithInsecureRegistry(t *testing.T, n string) func() { - ref, err := name.ParseReference(n, name.WeakValidation) + ref, err := name.ParseReference(n, name.WeakValidation, name.Insecure) if err != nil { t.Fatal(err) } diff --git a/test/e2e_kms_test.go b/test/e2e_kms_test.go index 0050c2a39d4..2d212c85d52 100644 --- a/test/e2e_kms_test.go +++ b/test/e2e_kms_test.go @@ -22,10 +22,10 @@ import ( "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/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" + "github.com/sigstore/cosign/v3/pkg/cosign/env" _ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault" ) @@ -75,7 +75,7 @@ func TestSecretsKMS(t *testing.T) { Upload: true, TlogUpload: true, } - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) must(verify(pubKey, imgName, true, nil, "", false), t) // Sign and verify with annotations @@ -87,12 +87,12 @@ func TestSecretsKMS(t *testing.T) { Annotations: []string{"foo=bar"}, }, } - must(sign.SignCmd(ro, ko, soAnno, []string{imgName}), t) + must(sign.SignCmd(t.Context(), 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(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) must(verify(pubKey, imgName, true, nil, "", false), t) os.Unsetenv("COSIGN_REPOSITORY") } diff --git a/test/e2e_test.go b/test/e2e_test.go index a57d197b99f..99f9a9b5733 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -21,63 +21,75 @@ import ( "bytes" "context" "crypto" + "crypto/ecdsa" "crypto/ed25519" + "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/x509" + "crypto/x509/pkix" "encoding/base64" + "encoding/hex" "encoding/json" "encoding/pem" "fmt" "io" + "math/big" "net/http" "net/http/httptest" "net/url" "os" "path" "path/filepath" + "regexp" "strings" "testing" "time" + "github.com/digitorus/timestamp" "github.com/google/go-cmp/cmp" + "github.com/google/go-containerregistry/pkg/crane" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/stretchr/testify/assert" "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 - "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" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/trustedroot" - 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/cosign/v3/cmd/cosign/cli" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/attach" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/attest" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/dockerfile" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/download" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/initialize" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/manifest" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/publickey" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/signingconfig" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/trustedroot" + cliverify "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/fulcio/fulcioroots" + "github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/client" + cert_test "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/bundle" + "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign/kubernetes" + "github.com/sigstore/cosign/v3/pkg/oci/mutate" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + sigs "github.com/sigstore/cosign/v3/pkg/signature" + protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "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" + tsaclient "github.com/sigstore/timestamp-authority/v2/pkg/client" + "google.golang.org/protobuf/encoding/protojson" _ "k8s.io/client-go/plugin/pkg/client/auth" ) @@ -102,7 +114,7 @@ func TestSignVerify(t *testing.T) { // Verify should fail at first mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) // So should download - mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName), t) + mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) // Now sign the image ko := options.KeyOpts{ @@ -115,11 +127,19 @@ func TestSignVerify(t *testing.T) { Upload: true, TlogUpload: true, } - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(ctx, 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) + must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) + + // Ensure it verifies if you default to the new protobuf bundle format + cmd := cliverify.VerifyCommand{ + KeyRef: pubKeyPath, + RekorURL: rekorURL, + NewBundleFormat: true, + } + must(cmd.Exec(ctx, []string{imgName}), t) // Look for a specific annotation mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) @@ -128,7 +148,7 @@ func TestSignVerify(t *testing.T) { Annotations: []string{"foo=bar"}, } // Sign the image with an annotation - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) // It should match this time. must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) @@ -160,7 +180,7 @@ func TestSignVerifyCertBundle(t *testing.T) { // 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) + mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) // Now sign the image ko := options.KeyOpts{ @@ -173,14 +193,14 @@ func TestSignVerifyCertBundle(t *testing.T) { Upload: true, TlogUpload: true, } - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(ctx, 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) + must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) // Look for a specific annotation mustErr(verifyCertBundle(pubKeyPath, caCertFile, caIntermediateCertFile, imgName, true, map[string]interface{}{"foo": "bar"}, "", ignoreTlog), t) @@ -189,7 +209,7 @@ func TestSignVerifyCertBundle(t *testing.T) { Annotations: []string{"foo=bar"}, } // Sign the image with an annotation - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(ctx, 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) @@ -227,11 +247,11 @@ func TestSignVerifyClean(t *testing.T) { Upload: true, TlogUpload: true, } - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(ctx, 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) + must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) // Now clean signature from the given image must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t) @@ -269,17 +289,39 @@ func TestImportSignVerifyClean(t *testing.T) { Upload: true, TlogUpload: true, } - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(ctx, 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) + must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), 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) + + // Sign with new bundle format + so.NewBundleFormat = true + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) + + // Verify should work again + trustedRootPath := prepareTrustedRoot(t, "") + bundleVerifyCmd := cliverify.VerifyCommand{ + CommonVerifyOptions: options.CommonVerifyOptions{ + TrustedRootPath: trustedRootPath, + }, + KeyRef: pubKeyPath, + NewBundleFormat: true, + UseSignedTimestamps: false, + } + must(bundleVerifyCmd.Exec(ctx, []string{imgName}), t) + + // Clean again + must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t) + + // Verify should fail again + mustErr(bundleVerifyCmd.Exec(ctx, []string{imgName}), t) } type targetInfo struct { @@ -294,7 +336,7 @@ func downloadTargets(td string, targets []targetInfo, targetsMeta *metadata.Meta if err != nil { return err } - err = os.Mkdir(targetsDir, 0700) + err = os.Mkdir(targetsDir, 0o700) if err != nil { return err } @@ -556,8 +598,7 @@ func downloadTSACerts(downloadDirectory string, tsaServer string) (string, strin return leafPath, intermediatePath, rootPath, nil } -func prepareTrustedRoot(t *testing.T, tsaURL string) string { - downloadDirectory := t.TempDir() +func trustedRootCmd(t *testing.T, downloadDirectory, tsaURL string) *trustedroot.CreateCmd { caPath := filepath.Join(downloadDirectory, "fulcio.crt.pem") caFP, err := os.Create(caPath) must(err, t) @@ -569,59 +610,57 @@ func prepareTrustedRoot(t *testing.T, tsaURL string) string { defer rekorFP.Close() must(downloadFile(rekorURL+"/api/v1/log/publicKey", rekorFP), t) ctfePath := filepath.Join(downloadDirectory, "ctfe.pub") - home, err := os.UserHomeDir() - must(err, t) - must(copyFile(filepath.Join(home, "fulcio", "config", "ctfe", "pubkey.pem"), ctfePath), t) - tsaPath := filepath.Join(downloadDirectory, "tsa.crt.pem") - tsaFP, err := os.Create(tsaPath) - must(err, t) - must(downloadFile(tsaURL+"/api/v1/timestamp/certchain", tsaFP), t) + ctLogKey := os.Getenv("CT_LOG_KEY") + must(copyFile(ctLogKey, ctfePath), t) out := filepath.Join(downloadDirectory, "trusted_root.json") cmd := &trustedroot.CreateCmd{ - CertChain: []string{caPath}, - CtfeKeyPath: []string{ctfePath}, - Out: out, - RekorKeyPath: []string{rekorPath}, - TSACertChainPath: []string{tsaPath}, - } + CertChain: []string{caPath}, + CtfeKeyPath: []string{ctfePath}, + Out: out, + RekorKeyPath: []string{rekorPath}, + } + if tsaURL != "" { + tsaPath := filepath.Join(downloadDirectory, "tsa.crt.pem") + tsaFP, err := os.Create(tsaPath) + must(err, t) + must(downloadFile(tsaURL+"/api/v1/timestamp/certchain", tsaFP), t) + cmd.TSACertChainPath = []string{tsaPath} + } + return cmd +} + +func prepareTrustedRoot(t *testing.T, tsaURL string) string { + downloadDirectory := t.TempDir() + cmd := trustedRootCmd(t, downloadDirectory, tsaURL) must(cmd.Exec(context.TODO()), t) - return out + return cmd.Out +} + +func prepareTrustedRootWithSelfSignedCertificate(t *testing.T, certPath, tsaURL string) string { + td := t.TempDir() + cmd := trustedRootCmd(t, td, tsaURL) + cmd.CertChain = append(cmd.CertChain, certPath) + must(cmd.Exec(context.TODO()), t) + return cmd.Out } func TestSignVerifyWithTUFMirror(t *testing.T) { - home, err := os.UserHomeDir() // fulcio repo was downloaded to $HOME in e2e_test.sh - must(err, t) + ctLogKey := os.Getenv("CT_LOG_KEY") 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) + tsaLeaf, tsaInter, tsaRoot, err := downloadTSACerts(t.TempDir(), tsaURL) must(err, t) - trustedRoot := prepareTrustedRoot(t, tsaServer.URL) + trustedRoot := prepareTrustedRoot(t, tsaURL) 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{ @@ -635,7 +674,7 @@ func TestSignVerifyWithTUFMirror(t *testing.T) { }, { name: "ctfe.pub", - source: filepath.Join(home, "fulcio", "config", "ctfe", "pubkey.pem"), + source: ctLogKey, }, { name: "tsa_leaf.crt.pem", @@ -664,7 +703,7 @@ func TestSignVerifyWithTUFMirror(t *testing.T) { }, { name: "ctfe.pub", - source: filepath.Join(home, "fulcio", "config", "ctfe", "pubkey.pem"), + source: ctLogKey, }, { name: "tsaleaf.pem", @@ -702,7 +741,7 @@ func TestSignVerifyWithTUFMirror(t *testing.T) { { name: "cert-transparency.pem", usage: "CTFE", - source: filepath.Join(home, "fulcio", "config", "ctfe", "pubkey.pem"), + source: ctLogKey, }, { name: "tsaleaf.pem", @@ -759,7 +798,7 @@ func TestSignVerifyWithTUFMirror(t *testing.T) { RekorURL: rekorURL, IDToken: identityToken, SkipConfirmation: true, - TSAServerURL: tsaServer.URL + "/api/v1/timestamp", + TSAServerURL: tsaURL + "/api/v1/timestamp", } trustedMaterial, err := cosign.TrustedRoot() if err == nil { @@ -770,15 +809,11 @@ func TestSignVerifyWithTUFMirror(t *testing.T) { TlogUpload: true, SkipConfirmation: true, } - gotErr := sign.SignCmd(ro, ko, so, []string{imgName}) - if test.wantSignErr { - mustErr(gotErr, t) - return - } + gotErr := sign.SignCmd(ctx, ro, ko, so, []string{imgName}) must(gotErr, t) // Verify an image - issuer := os.Getenv("OIDC_URL") + issuer := os.Getenv("ISSUER_URL") verifyCmd := cliverify.VerifyCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuer: issuer, @@ -799,7 +834,7 @@ func TestSignVerifyWithTUFMirror(t *testing.T) { blob := "someblob" blobDir := t.TempDir() bp := filepath.Join(blobDir, blob) - if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { + if err := os.WriteFile(bp, []byte(blob), 0o644); err != nil { t.Fatal(err) } tsPath := filepath.Join(blobDir, "ts.txt") @@ -807,12 +842,8 @@ func TestSignVerifyWithTUFMirror(t *testing.T) { // TODO(cmurphy): make this work with ko.NewBundleFormat = true ko.BundlePath = bundlePath ko.RFC3161TimestampPath = tsPath - _, gotErr = sign.SignBlobCmd(ro, ko, bp, true, "", "", true) - if test.wantSignErr { - mustErr(gotErr, t) - } else { - must(gotErr, t) - } + _, gotErr = sign.SignBlobCmd(ctx, ro, ko, bp, "", "", true, "", "", true) + must(gotErr, t) // Verify a blob verifyBlobCmd := cliverify.VerifyBlobCmd{ @@ -834,1195 +865,3048 @@ func TestSignVerifyWithTUFMirror(t *testing.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 prepareSigningConfig(t *testing.T, fulcioURL, rekorURL, oidcURL, tsaURL string) string { //nolint: unparam + startTime := "2024-01-01T00:00:00Z" + fulcioSpec := fmt.Sprintf("url=%s,api-version=1,operator=fulcio-op,start-time=%s", fulcioURL, startTime) + rekorSpec := fmt.Sprintf("url=%s,api-version=1,operator=rekor-op,start-time=%s", rekorURL, startTime) + oidcSpec := fmt.Sprintf("url=%s,api-version=1,operator=oidc-op,start-time=%s", oidcURL, startTime) + tsaSpec := fmt.Sprintf("url=%s,api-version=1,operator=tsa-op,start-time=%s", tsaURL, startTime) + + downloadDirectory := t.TempDir() + out := filepath.Join(downloadDirectory, "signing_config.v0.2.json") + cmd := &signingconfig.CreateCmd{ + FulcioSpecs: []string{fulcioSpec}, + RekorSpecs: []string{rekorSpec}, + OIDCProviderSpecs: []string{oidcSpec}, + TSASpecs: []string{tsaSpec}, + RekorConfig: "EXACT:1", + TSAConfig: "ANY", + Out: out, } + must(cmd.Exec(context.TODO()), t) + return out } -func TestAttestVerifySPDXJSON(t *testing.T) { - attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.spdx.json") +func TestSignAttestVerifyBlobWithSigningConfig(t *testing.T) { + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + mirror := tufServer.URL + trustedRoot := prepareTrustedRoot(t, tsaURL) + signingConfigStr := prepareSigningConfig(t, fulcioURL, rekorURL, "unused", tsaURL+"/api/v1/timestamp") + sc, err := os.ReadFile(signingConfigStr) + must(err, t) + fmt.Println(string(sc)) + fmt.Println(fulcioURL) + + _, err = newTUF(tufMirror, []targetInfo{ + { + name: "trusted_root.json", + source: trustedRoot, + }, + { + name: "signing_config.v0.2.json", + source: signingConfigStr, + }, + }) + must(err, t) + + ctx := context.Background() + + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + identityToken, err := getOIDCToken() 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"`, - ) + + ko := options.KeyOpts{ + IDToken: identityToken, + SkipConfirmation: true, } -} + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + signingConfig, err := cosign.SigningConfig() + must(err, t) + ko.SigningConfig = signingConfig -func TestAttestVerifyCycloneDXJSON(t *testing.T) { - attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.cyclonedx.json") - if err != nil { + // Sign a blob + blob := "someblob" + blobDir := t.TempDir() + bp := filepath.Join(blobDir, blob) + if err := os.WriteFile(bp, []byte(blob), 0o644); 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"`, - ) + bundlePath := filepath.Join(blobDir, "bundle.json") + ko.NewBundleFormat = true + ko.BundlePath = bundlePath + + _, err = sign.SignBlobCmd(ctx, ro, ko, bp, "", "", false, "", "", true) + must(err, t) + + // Verify a blob + issuer := os.Getenv("ISSUER_URL") + verifyBlobCmd := cliverify.VerifyBlobCmd{ + KeyOpts: ko, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: certID, + }, + UseSignedTimestamps: true, } -} + err = verifyBlobCmd.Exec(ctx, bp) + must(err, t) -func TestAttestVerifyURI(t *testing.T) { - attestationBytes, err := os.ReadFile("./testdata/test-result.json") - if err != nil { + // Sign an attestation + statement := `{"_type":"https://in-toto.io/Statement/v1","subject":[{"name":"someblob","digest":{"alg":"7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3"}}],"predicateType":"something","predicate":{}}` + attestDir := t.TempDir() + statementPath := filepath.Join(attestDir, "statement") + if err := os.WriteFile(statementPath, []byte(statement), 0o644); 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"`, - ) + attBundlePath := filepath.Join(attestDir, "attest.bundle.json") + ko.NewBundleFormat = true + ko.BundlePath = attBundlePath + + attestBlobCmd := attest.AttestBlobCommand{ + KeyOpts: ko, + RekorEntryType: "dsse", + StatementPath: statementPath, + TlogUpload: true, + } + must(attestBlobCmd.Exec(ctx, bp), t) + + // Verify an attestation + verifyBlobAttestationCmd := cliverify.VerifyBlobAttestationCommand{ + KeyOpts: ko, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: certID, + }, + UseSignedTimestamps: true, + Digest: "7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3", + DigestAlg: "alg", + CheckClaims: true, + PredicateType: "something", } + err = verifyBlobAttestationCmd.Exec(ctx, "") + must(err, t) } -func attestVerify(t *testing.T, newBundleFormat bool, predicateType, attestation, goodCue, badCue string) { +func TestSignAttestVerifyContainerWithSigningConfig(t *testing.T) { + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + mirror := tufServer.URL + trustedRoot := prepareTrustedRoot(t, tsaURL) + signingConfigStr := prepareSigningConfig(t, fulcioURL, rekorURL, "unused", tsaURL+"/api/v1/timestamp") + + _, err := newTUF(tufMirror, []targetInfo{ + { + name: "trusted_root.json", + source: trustedRoot, + }, + { + name: "signing_config.v0.2.json", + source: signingConfigStr, + }, + }) + must(err, t) + 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)) - } + 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 - verifyAttestation := cliverify.VerifyAttestationCommand{ - KeyRef: pubKeyPath, - IgnoreTlog: true, - MaxWorkers: 10, + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + identityToken, err := getOIDCToken() + if err != nil { + t.Fatal(err) } - if newBundleFormat { - verifyAttestation.NewBundleFormat = true + ko := options.KeyOpts{ + IDToken: identityToken, + NewBundleFormat: true, + SkipConfirmation: true, } + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + signingConfig, err := cosign.SigningConfig() + must(err, t) + ko.SigningConfig = signingConfig - // Fail case when using without type and policy flag - mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) + // Sign image with identity token in bundle format + so := options.SignOptions{ + Upload: true, + NewBundleFormat: true, + TlogUpload: true, + } + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) - if err := os.WriteFile(attestationPath, []byte(attestation), 0600); err != nil { - t.Fatal(err) + // Verify Fulcio-signed image + cmd := cliverify.VerifyCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentity: certID, + }, + NewBundleFormat: true, + UseSignedTimestamps: true, } + args := []string{imgName} + must(cmd.Exec(ctx, args), t) - // Now attest the image - ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, NewBundleFormat: newBundleFormat} + // Attest image + predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` + predicatePath := filepath.Join(t.TempDir(), "predicate.json") + if err := os.WriteFile(predicatePath, []byte(predicate), 0o644); err != nil { + t.Fatal(err) + } attestCmd := attest.AttestCommand{ KeyOpts: ko, - PredicatePath: attestationPath, - PredicateType: predicateType, + PredicatePath: predicatePath, + PredicateType: "slsaprovenance", Timeout: 30 * time.Second, RekorEntryType: "dsse", + TlogUpload: true, } must(attestCmd.Exec(ctx, imgName), t) - // Use cue to verify attestation - policyPath := filepath.Join(td, "policy.cue") - verifyAttestation.PredicateType = predicateType - verifyAttestation.Policies = []string{policyPath} + // Verify attestation + verifyAttestation := cliverify.VerifyAttestationCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentity: certID, + }, + CommonVerifyOptions: options.CommonVerifyOptions{ + NewBundleFormat: true, + }, + PredicateType: "slsaprovenance", + UseSignedTimestamps: true, + CheckClaims: true, + } + must(verifyAttestation.Exec(ctx, []string{imgName}), t) +} - // Fail case - if err := os.WriteFile(policyPath, []byte(badCue), 0600); err != nil { - t.Fatal(err) +func TestSignVerifyContainerWithSigningConfigWithCertificate(t *testing.T) { + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + mirror := tufServer.URL + + cert, privKey, err := selfSignedCertificate() + must(err, t) + keysDir := t.TempDir() + privKeyPath := filepath.Join(keysDir, "priv.key") + privDer, err := x509.MarshalECPrivateKey(privKey) + must(err, t) + keyWriter, err := os.OpenFile(privKeyPath, os.O_WRONLY|os.O_CREATE, 0o600) + must(err, t) + defer keyWriter.Close() + block := &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: privDer, } - mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) + must(pem.Encode(keyWriter, block), t) - // Success case - if err := os.WriteFile(policyPath, []byte(goodCue), 0600); err != nil { - t.Fatal(err) + certPath := filepath.Join(keysDir, "cert.pem") + certWriter, err := os.OpenFile(certPath, os.O_WRONLY|os.O_CREATE, 0o600) + must(err, t) + defer certWriter.Close() + block = &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, } - must(verifyAttestation.Exec(ctx, []string{imgName}), t) + must(pem.Encode(certWriter, block), t) - // Look for a specific annotation - mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) -} + keys, err := cosign.ImportKeyPair(privKeyPath, passFunc) + must(err, t) + importKeyPath := filepath.Join(keysDir, "import-priv.key") + must(os.WriteFile(importKeyPath, keys.PrivateBytes, 0o600), t) + + trustedRoot := prepareTrustedRootWithSelfSignedCertificate(t, certPath, tsaURL) + signingConfigStr := prepareSigningConfig(t, fulcioURL, rekorURL, "unused", tsaURL+"/api/v1/timestamp") + + _, err = newTUF(tufMirror, []targetInfo{ + { + name: "trusted_root.json", + source: trustedRoot, + }, + { + name: "signing_config.v0.2.json", + source: signingConfigStr, + }, + }) + must(err, t) -func TestAttestationDownload(t *testing.T) { repo, stop := reg(t) defer stop() - td := t.TempDir() - - imgName := path.Join(repo, "cosign-attest-download-e2e") + imgName := path.Join(repo, "cosign-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) + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + ko := options.KeyOpts{ + NewBundleFormat: true, + SkipConfirmation: true, + KeyRef: importKeyPath, + PassFunc: passFunc, } + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + signingConfig, err := cosign.SigningConfig() + must(err, t) + ko.SigningConfig = signingConfig - 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) + // Sign image with cert in bundle format + so := options.SignOptions{ + Upload: true, + NewBundleFormat: true, + Key: importKeyPath, + Cert: certPath, + TlogUpload: false, } + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) - 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) + // Verify image + cmd := cliverify.VerifyCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuerRegexp: ".*", + CertIdentity: "foo@bar.com", + }, + NewBundleFormat: true, + IgnoreSCT: true, } + args := []string{imgName} + must(cmd.Exec(ctx, args), t) +} - // 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) +func TestSignRekorV2NoTSA(t *testing.T) { + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + defer tufServer.Close() + mirror := tufServer.URL - // Attest to create a vuln attestation - attestCommand = attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: vulnAttestationPath, - PredicateType: "vuln", - Timeout: 30 * time.Second, - Replace: true, - RekorEntryType: "dsse", + // Create signing config with Rekor v2 and no TSA + startTime := "2024-01-01T00:00:00Z" + fulcioSpec := fmt.Sprintf("url=%s,api-version=1,operator=fulcio-op,start-time=%s", fulcioURL, startTime) + rekorSpec := fmt.Sprintf("url=%s,api-version=2,operator=rekor-op,start-time=%s", rekorV2URL, startTime) + + downloadDirectory := t.TempDir() + signingConfigPath := filepath.Join(downloadDirectory, "signing_config.v0.2.json") + scCmd := &signingconfig.CreateCmd{ + FulcioSpecs: []string{fulcioSpec}, + RekorSpecs: []string{rekorSpec}, + RekorConfig: "EXACT:1", + Out: signingConfigPath, } - must(attestCommand.Exec(ctx, imgName), t) + must(scCmd.Exec(context.TODO()), t) - // Call download.AttestationCmd() to ensure success - attOpts := options.AttestationDownloadOptions{} - must(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t) + trustedRoot := prepareTrustedRoot(t, "") - attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) + _, err := newTUF(tufMirror, []targetInfo{ + { + name: "trusted_root.json", + source: trustedRoot, + }, + { + name: "signing_config.v0.2.json", + source: signingConfigPath, + }, + }) + must(err, t) + + ctx := context.Background() + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + identityToken, err := getOIDCToken() if err != nil { t.Fatal(err) } - if len(attestations) != 2 { - t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations))) + + ko := options.KeyOpts{ + IDToken: identityToken, + NewBundleFormat: true, + SkipConfirmation: true, } -} + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + signingConfig, err := cosign.SigningConfig() + must(err, t) + ko.SigningConfig = signingConfig -func TestAttestationDownloadWithPredicateType(t *testing.T) { repo, stop := reg(t) defer stop() - td := t.TempDir() - - imgName := path.Join(repo, "cosign-attest-download-predicate-type-e2e") - + imgName := path.Join(repo, "cosign-e2e-rekor-v2-no-tsa") _, _, 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) + so := options.SignOptions{ + Upload: true, + NewBundleFormat: true, + TlogUpload: true, } - ref, err := name.ParseReference(imgName) - if err != nil { - t.Fatal(err) + // This should fail because we are using Rekor v2 (configured) but no TSA, with an ID token. + err = sign.SignCmd(ctx, ro, ko, so, []string{imgName}) + if err == nil { + t.Fatal("expected error signing with Rekor v2 and no TSA") } - regOpts := options.RegistryOptions{} - ociremoteOpts, err := regOpts.ClientOpts(ctx) - if err != nil { - t.Fatal(err) + if !strings.Contains(err.Error(), "timestamp authority must be provided") { + t.Fatalf("expected error about timestamp authority, got: %v", err) } +} - // Attest to create a slsa attestation - attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, - Replace: true, - RekorEntryType: "dsse", +// TestSignAttestVerifyRekorV2 exercises the full sign/attest/verify path +// against rekor-tiles (Rekor v2). It asserts that both signatures and DSSE +// attestations land as hashedrekord/0.0.2 tlog entries. +func TestSignAttestVerifyRekorV2(t *testing.T) { + scaffoldingTR := os.Getenv("TRUSTED_ROOT") + if scaffoldingTR == "" { + t.Skip("TRUSTED_ROOT env var not set; this test requires the scaffolding e2e env") } - 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) + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + defer tufServer.Close() + mirror := tufServer.URL - // Call download.AttestationCmd() to ensure success with --predicate-type - attOpts := options.AttestationDownloadOptions{ - PredicateType: "vuln", - } - must(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t) + // Build a signing config pointing exclusively at Rekor v2 + TSA. + // Rekor v2 mandates a TSA, so this is the minimal viable shape. + startTime := "2024-01-01T00:00:00Z" + fulcioSpec := fmt.Sprintf("url=%s,api-version=1,operator=fulcio-op,start-time=%s", fulcioURL, startTime) + rekorSpec := fmt.Sprintf("url=%s,api-version=2,operator=rekor-op,start-time=%s", rekorV2URL, startTime) + tsaSpec := fmt.Sprintf("url=%s/api/v1/timestamp,api-version=1,operator=tsa-op,start-time=%s", tsaURL, startTime) + + signingConfigPath := filepath.Join(t.TempDir(), "signing_config.v0.2.json") + must((&signingconfig.CreateCmd{ + FulcioSpecs: []string{fulcioSpec}, + RekorSpecs: []string{rekorSpec}, + TSASpecs: []string{tsaSpec}, + RekorConfig: "EXACT:1", + TSAConfig: "EXACT:1", + Out: signingConfigPath, + }).Exec(context.TODO()), t) + + // Use the scaffolding-provided trusted root (it already includes the + // rekor-tiles ed25519 key); pair it with our v2-only signing config. + _, err := newTUF(tufMirror, []targetInfo{ + {name: "trusted_root.json", source: scaffoldingTR}, + {name: "signing_config.v0.2.json", source: signingConfigPath}, + }) + must(err, t) - predicateType, _ := options.ParsePredicateType(attOpts.PredicateType) - attestations, err := cosign.FetchAttestationsForReference(ctx, ref, predicateType, ociremoteOpts...) + ctx := context.Background() + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + identityToken, err := getOIDCToken() 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") - + imgName := path.Join(repo, "cosign-e2e-rekor-v2") _, _, cleanup := mkimage(t, imgName) defer cleanup() - _, privKeyPath, _ := keypair(t, td) - ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} - - ctx := context.Background() + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + signingConfig, err := cosign.SigningConfig() + must(err, t) - 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) + // Sign — capture the bundle and assert the tlog entry kind/version. + signBundlePath := filepath.Join(t.TempDir(), "sign.bundle") + ko := options.KeyOpts{ + IDToken: identityToken, + NewBundleFormat: true, + SkipConfirmation: true, + TrustedMaterial: trustedMaterial, + SigningConfig: signingConfig, } + must(sign.SignCmd(ctx, ro, ko, options.SignOptions{ + Upload: true, + NewBundleFormat: true, + TlogUpload: true, + BundlePath: signBundlePath, + }, []string{imgName}), t) + assertRekorV2HashedrekordEntry(t, signBundlePath) + + // Attest — capture the bundle and assert the same. A DSSE attestation + // landing as hashedrekord (rather than a dedicated dsse entry) is the + // behavior this test most needs to pin down on Rekor v2. + predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` + predicatePath := filepath.Join(t.TempDir(), "predicate.json") + must(os.WriteFile(predicatePath, []byte(predicate), 0o644), t) - regOpts := options.RegistryOptions{} - - // Attest to create a slsa attestation - attestCommand := attest.AttestCommand{ + ko.BundlePath = filepath.Join(t.TempDir(), "att.bundle") + must((&attest.AttestCommand{ KeyOpts: ko, - PredicatePath: slsaAttestationPath, + PredicatePath: predicatePath, PredicateType: "slsaprovenance", Timeout: 30 * time.Second, - Replace: true, RekorEntryType: "dsse", - } - must(attestCommand.Exec(ctx, imgName), t) + TlogUpload: true, + }).Exec(ctx, imgName), t) + assertRekorV2HashedrekordEntry(t, ko.BundlePath) + + // Verify image signature. + must((&cliverify.VerifyCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentity: certID, + }, + NewBundleFormat: true, + UseSignedTimestamps: true, + }).Exec(ctx, []string{imgName}), t) + + // Verify attestation. + must((&cliverify.VerifyAttestationCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentity: certID, + }, + CommonVerifyOptions: options.CommonVerifyOptions{ + NewBundleFormat: true, + }, + PredicateType: "slsaprovenance", + UseSignedTimestamps: true, + CheckClaims: true, + }).Exec(ctx, []string{imgName}), t) +} - // Call download.AttestationCmd() to ensure failure with non-existent --predicate-type - attOpts := options.AttestationDownloadOptions{ - PredicateType: "vuln", +// assertRekorV2HashedrekordEntry asserts that every tlog entry in a written +// bundle file is hashedrekord at version 0.0.2 — what Rekor v2 produces. +func assertRekorV2HashedrekordEntry(t *testing.T, bundlePath string) { + t.Helper() + raw, err := os.ReadFile(bundlePath) + must(err, t) + var pb protobundle.Bundle + must(protojson.Unmarshal(raw, &pb), t) + entries := pb.GetVerificationMaterial().GetTlogEntries() + if len(entries) == 0 { + t.Fatalf("bundle %s has no tlog entries", bundlePath) + } + for i, entry := range entries { + kv := entry.GetKindVersion() + if kv == nil { + t.Fatalf("bundle %s tlog entry %d missing KindVersion", bundlePath, i) + } + if kv.Kind != "hashedrekord" || kv.Version != "0.0.2" { + t.Errorf("bundle %s tlog entry %d: want hashedrekord/0.0.2, got %s/%s", + bundlePath, i, kv.Kind, kv.Version) + } } - mustErr(download.AttestationCmd(ctx, regOpts, attOpts, imgName), t) } -func TestAttestationReplaceCreate(t *testing.T) { - repo, stop := reg(t) - defer stop() - td := t.TempDir() +func TestSignVerifyWithSigningConfigWithKey(t *testing.T) { + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + mirror := tufServer.URL + trustedRoot := prepareTrustedRoot(t, tsaURL) + signingConfigStr := prepareSigningConfig(t, fulcioURL, rekorURL, "unused", tsaURL+"/api/v1/timestamp") - imgName := path.Join(repo, "cosign-attest-replace-e2e") + _, err := newTUF(tufMirror, []targetInfo{ + { + name: "trusted_root.json", + source: trustedRoot, + }, + { + name: "signing_config.v0.2.json", + source: signingConfigStr, + }, + }) + must(err, t) - _, _, cleanup := mkimage(t, imgName) - defer cleanup() + ctx := context.Background() - _, privKeyPath, _ := keypair(t, td) - ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) - ctx := context.Background() + _, privKeyPath, pubKeyPath := keypair(t, t.TempDir()) - 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) + ko := options.KeyOpts{ + PassFunc: passFunc, + SkipConfirmation: true, } + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + signingConfig, err := cosign.SigningConfig() + must(err, t) + ko.SigningConfig = signingConfig - ref, err := name.ParseReference(imgName) - if err != nil { - t.Fatal(err) - } - regOpts := options.RegistryOptions{} - ociremoteOpts, err := regOpts.ClientOpts(ctx) - if err != nil { + // Sign a blob using a provided key + blob := "someblob" + blobDir := t.TempDir() + bp := filepath.Join(blobDir, blob) + if err := os.WriteFile(bp, []byte(blob), 0o644); err != nil { t.Fatal(err) } + bundlePath := filepath.Join(blobDir, "bundle.json") + ko.NewBundleFormat = true + ko.BundlePath = bundlePath + ko.KeyRef = privKeyPath - // 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", + _, err = sign.SignBlobCmd(ctx, ro, ko, bp, "", "", false, "", "", true) + must(err, t) + + // Verify a blob with the key in the trusted root + ko.KeyRef = pubKeyPath + verifyBlobCmd := cliverify.VerifyBlobCmd{ + KeyOpts: ko, } - must(attestCommand.Exec(ctx, imgName), t) + err = verifyBlobCmd.Exec(ctx, bp) + must(err, t) - // Download and count the attestations - attOpts := options.AttestationDownloadOptions{} - attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) - if err != nil { + // Sign an attestation with a provided key + statement := `{"_type":"https://in-toto.io/Statement/v1","subject":[{"name":"someblob","digest":{"alg":"7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3"}}],"predicateType":"something","predicate":{}}` + attestDir := t.TempDir() + statementPath := filepath.Join(attestDir, "statement") + if err := os.WriteFile(statementPath, []byte(statement), 0o644); err != nil { t.Fatal(err) } - if len(attestations) != 1 { - t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) + attBundlePath := filepath.Join(attestDir, "attest.bundle.json") + ko.NewBundleFormat = true + ko.BundlePath = attBundlePath + ko.KeyRef = privKeyPath + + attestBlobCmd := attest.AttestBlobCommand{ + KeyOpts: ko, + RekorEntryType: "dsse", + StatementPath: statementPath, + TlogUpload: true, + } + must(attestBlobCmd.Exec(ctx, bp), t) + + // Verify an attestation with the key in the trusted root + ko.KeyRef = pubKeyPath + verifyBlobAttestationCmd := cliverify.VerifyBlobAttestationCommand{ + KeyOpts: ko, + Digest: "7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3", + DigestAlg: "alg", + CheckClaims: true, + PredicateType: "something", } + err = verifyBlobAttestationCmd.Exec(ctx, "") + must(err, t) } -func TestAttestationReplace(t *testing.T) { +func TestSignVerifyBundle(t *testing.T) { + td := t.TempDir() repo, stop := reg(t) defer stop() - td := t.TempDir() - imgName := path.Join(repo, "cosign-attest-replace-e2e") + imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() - _, privKeyPath, _ := keypair(t, td) - ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} + _, privKeyPath, pubKeyPath := keypair(t, td) 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) + // Sign image with key in bundle format + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, } - regOpts := options.RegistryOptions{} - ociremoteOpts, err := regOpts.ClientOpts(ctx) - if err != nil { - t.Fatal(err) + so := options.SignOptions{ + Upload: true, + NewBundleFormat: true, + TlogUpload: true, } + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) - // Attest once with replace=false creating an attestation - attestCommand := attest.AttestCommand{ - KeyOpts: ko, - PredicatePath: slsaAttestationPath, - PredicateType: "slsaprovenance", - Timeout: 30 * time.Second, - RekorEntryType: "dsse", + // Verify bundle + trustedRootPath := prepareTrustedRoot(t, "") + + cmd := cliverify.VerifyCommand{ + CommonVerifyOptions: options.CommonVerifyOptions{ + TrustedRootPath: trustedRootPath, + }, + KeyRef: pubKeyPath, + NewBundleFormat: true, + UseSignedTimestamps: false, } - must(attestCommand.Exec(ctx, imgName), t) + args := []string{imgName} + must(cmd.Exec(ctx, args), t) - // Download and count the attestations - attOpts := options.AttestationDownloadOptions{} - attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) - if err != nil { - t.Fatal(err) + // Sign image with key in bundle format without Rekor + _, privKeyPath, pubKeyPath = keypair(t, td) + ko = options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + SkipConfirmation: true, } - if len(attestations) != 1 { - t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) + so = options.SignOptions{ + Upload: true, + NewBundleFormat: true, + TlogUpload: false, } - - // 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(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) + // Verify bundle without Rekor + cmd = cliverify.VerifyCommand{ + CommonVerifyOptions: options.CommonVerifyOptions{ + TrustedRootPath: trustedRootPath, + }, + KeyRef: pubKeyPath, + NewBundleFormat: true, + IgnoreTlog: true, + UseSignedTimestamps: false, } - must(attestCommand.Exec(ctx, imgName), t) - attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) + must(cmd.Exec(ctx, args), t) - // Download and count the attestations + // Sign image with Fulcio + identityToken, err := getOIDCToken() if err != nil { t.Fatal(err) } - if len(attestations) != 1 { - t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) + + ko = options.KeyOpts{ + IDToken: identityToken, + FulcioURL: fulcioURL, + RekorURL: rekorURL, + SkipConfirmation: true, + } + so = options.SignOptions{ + Upload: true, + NewBundleFormat: true, + TlogUpload: true, } + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) - // 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", + // Verify Fulcio-signed image + cmd = cliverify.VerifyCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentityRegexp: ".+", + }, + CommonVerifyOptions: options.CommonVerifyOptions{ + TrustedRootPath: trustedRootPath, + }, + NewBundleFormat: true, + UseSignedTimestamps: false, } - must(attestCommand.Exec(ctx, imgName), t) + must(cmd.Exec(ctx, args), t) - // Download and count the attestations - attestations, err = cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) - if err != nil { - t.Fatal(err) + // Add annotations and verify claims + _, privKeyPath, pubKeyPath = keypair(t, td) + ko = options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, } - if len(attestations) != 2 { - t.Fatal(fmt.Errorf("expected len(attestations) == 2, got %d", len(attestations))) + so = options.SignOptions{ + Upload: true, + NewBundleFormat: true, + TlogUpload: true, + AnnotationOptions: options.AnnotationOptions{ + Annotations: []string{"foo=bar"}, + }, } -} + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) + cmd = cliverify.VerifyCommand{ + CommonVerifyOptions: options.CommonVerifyOptions{ + TrustedRootPath: trustedRootPath, + }, + KeyRef: pubKeyPath, + NewBundleFormat: true, + UseSignedTimestamps: false, + Annotations: sigs.AnnotationsMap{Annotations: map[string]any{"foo": "bar"}}, + CheckClaims: true, + } + must(cmd.Exec(ctx, args), t) -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) + // Verfying other annotations should not work + cmd.Annotations.Annotations["baz"] = "bat" + mustErr(cmd.Exec(ctx, args), t) +} +// TestSignVerifyBundleOffline tests that signing +// with a key and not verifying with Rekor or the TSA +// is entirely offline and doesn't try to request the TUF repo. +func TestSignVerifyBundleOffline(t *testing.T) { + td := t.TempDir() repo, stop := reg(t) defer stop() - td := t.TempDir() - imgName := path.Join(repo, "cosign-attest-timestamp-e2e") + imgName := path.Join(repo, "cosign-e2e") _, _, cleanup := mkimage(t, imgName) defer cleanup() + // To simulate offline verification, we'll set the TUF repo + // env vars to invalid values. If signing were online, verification + // would err out when trying to request the TUF repo contents. + t.Setenv("TUF_ROOT", td) + t.Setenv("TUF_MIRROR", td) + _, 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) + // Sign image with key in bundle format + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + SkipConfirmation: true, } - - ref, err := name.ParseReference(imgName) - if err != nil { - t.Fatal(err) + so := options.SignOptions{ + Upload: true, + NewBundleFormat: true, + TlogUpload: false, } - regOpts := options.RegistryOptions{} - ociremoteOpts, err := regOpts.ClientOpts(ctx) - if err != nil { - t.Fatal(err) + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) + + // Verify bundle offline + cmd := cliverify.VerifyCommand{ + KeyRef: pubKeyPath, + NewBundleFormat: true, + IgnoreTlog: true, + UseSignedTimestamps: false, } + args := []string{imgName} + must(cmd.Exec(ctx, args), t) +} - // 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", +func TestTrustedRootCreateFromDefaults(t *testing.T) { + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + t.Cleanup(tufServer.Close) + mirror := tufServer.URL + trustedRoot := prepareTrustedRoot(t, tsaURL) + + _, err := newTUF(tufMirror, []targetInfo{ + { + name: "trusted_root.json", + source: trustedRoot, + }, + }) + must(err, t) + + ctx := context.Background() + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + // Create trusted root + td := t.TempDir() + outPath := filepath.Join(td, "trustedroot.json") + trustedrootCreate := trustedroot.CreateCmd{ + WithDefaultServices: true, + Out: outPath, } - must(attestCommand.Exec(ctx, imgName), t) + must(trustedrootCreate.Exec(context.Background()), t) - // Download and count the attestations - attOpts := options.AttestationDownloadOptions{} - attestations, err := cosign.FetchAttestationsForReference(ctx, ref, attOpts.PredicateType, ociremoteOpts...) - if err != nil { - t.Fatal(err) + // Verify trusted root was populated from TUF repo + tr, err := root.NewTrustedRootFromPath(outPath) + must(err, t) + if len(tr.FulcioCertificateAuthorities()) != 1 { + t.Fatal("expected default Fulcio certificate authority") } - if len(attestations) != 1 { - t.Fatal(fmt.Errorf("expected len(attestations) == 1, got %d", len(attestations))) + if len(tr.RekorLogs()) != 1 { + t.Fatal("expected default Rekor log") } - - client, err := tsaclient.GetTimestampClient(server.URL) - if err != nil { - t.Error(err) + if len(tr.CTLogs()) != 1 { + t.Fatal("expected default CT log") } - - chain, err := client.Timestamp.GetTimestampCertChain(nil) - if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + if len(tr.TimestampingAuthorities()) != 1 { + t.Fatal("expected default timestamp authority") } - file, err := os.CreateTemp(os.TempDir(), "tempfile") - if err != nil { - t.Fatalf("error creating temp file: %v", err) + // Skip Fulcio + trustedrootCreate.NoDefaultFulcio = true + err = trustedrootCreate.Exec(ctx) + must(err, t) + tr, err = root.NewTrustedRootFromPath(outPath) + must(err, t) + if len(tr.FulcioCertificateAuthorities()) != 0 { + t.Fatal("expected no Fulcio certificate authorities") } - defer os.Remove(file.Name()) - _, err = file.WriteString(chain.Payload) - if err != nil { - t.Fatalf("error writing chain payload to temp file: %v", err) + + // Skip Rekor + trustedrootCreate.NoDefaultRekor = true + err = trustedrootCreate.Exec(ctx) + must(err, t) + tr, err = root.NewTrustedRootFromPath(outPath) + must(err, t) + if len(tr.RekorLogs()) != 0 { + t.Fatal("expected no Rekor logs") } - verifyAttestation := cliverify.VerifyAttestationCommand{ - KeyRef: pubKeyPath, - TSACertChainPath: file.Name(), - IgnoreTlog: true, - PredicateType: "slsaprovenance", - MaxWorkers: 10, + // Skip CT log + trustedrootCreate.NoDefaultCTFE = true + err = trustedrootCreate.Exec(ctx) + must(err, t) + tr, err = root.NewTrustedRootFromPath(outPath) + must(err, t) + if len(tr.CTLogs()) != 0 { + t.Fatal("expected no CT logs") } - must(verifyAttestation.Exec(ctx, []string{imgName}), t) + // Skip TSA + trustedrootCreate.NoDefaultTSA = true + err = trustedrootCreate.Exec(ctx) + must(err, t) + tr, err = root.NewTrustedRootFromPath(outPath) + must(err, t) + if len(tr.TimestampingAuthorities()) != 0 { + t.Fatal("expected no timestamp authorities") + } } -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" +func TestSigningConfigCreateFromDefaults(t *testing.T) { + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + t.Cleanup(tufServer.Close) + mirror := tufServer.URL + oidcURL := "https://oidc.example" + signingConfigStr := prepareSigningConfig(t, fulcioURL, rekorURL, oidcURL, tsaURL+"/api/v1/timestamp") + // Trusted root is needed as well for initialization + trustedRoot := prepareTrustedRoot(t, tsaURL) - td := t.TempDir() - t.Cleanup(func() { - os.RemoveAll(td) + _, err := newTUF(tufMirror, []targetInfo{ + { + name: "trusted_root.json", + source: trustedRoot, + }, + { + name: "signing_config.v0.2.json", + source: signingConfigStr, + }, }) + must(err, t) - bp := filepath.Join(td, blob) - if err := os.WriteFile(bp, []byte(blob), 0600); err != nil { - t.Fatal(err) + ctx := context.Background() + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + // Create signing config + td := t.TempDir() + outPath := filepath.Join(td, "signingconfig.json") + signingConfigCreate := signingconfig.CreateCmd{ + WithDefaultServices: true, + Out: outPath, } + must(signingConfigCreate.Exec(context.Background()), t) - predicatePath := filepath.Join(td, "predicate") - if err := os.WriteFile(predicatePath, []byte(predicate), 0600); err != nil { - t.Fatal(err) + // Verify signing root was populated from TUF repo + sc, err := root.NewSigningConfigFromPath(outPath) + must(err, t) + if len(sc.FulcioCertificateAuthorityURLs()) != 1 || sc.FulcioCertificateAuthorityURLs()[0].URL != fulcioURL { + t.Fatal("expected default Fulcio certificate authority service") + } + if len(sc.RekorLogURLs()) != 1 || sc.RekorLogURLs()[0].URL != rekorURL { + t.Fatal("expected default Rekor log service") + } + if len(sc.OIDCProviderURLs()) != 1 || sc.OIDCProviderURLs()[0].URL != oidcURL { + t.Fatal("expected default OIDC provider service") + } + if len(sc.TimestampAuthorityURLs()) != 1 || sc.TimestampAuthorityURLs()[0].URL != tsaURL+"/api/v1/timestamp" { + t.Fatal("expected default timestamp authority service") } - 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", + // Skip Fulcio + signingConfigCreate.NoDefaultFulcio = true + err = signingConfigCreate.Exec(ctx) + must(err, t) + sc, err = root.NewSigningConfigFromPath(outPath) + must(err, t) + if len(sc.FulcioCertificateAuthorityURLs()) != 0 { + t.Fatal("expected no Fulcio certificate authority services") } - must(attestBlobCmd.Exec(ctx, bp), t) - client, err := tsaclient.GetTimestampClient(server.URL) - if err != nil { - t.Error(err) + // Skip Rekor + signingConfigCreate.NoDefaultRekor = true + err = signingConfigCreate.Exec(ctx) + must(err, t) + sc, err = root.NewSigningConfigFromPath(outPath) + must(err, t) + if len(sc.RekorLogURLs()) != 0 { + t.Fatal("expected no Rekor log services") } - chain, err := client.Timestamp.GetTimestampCertChain(nil) - if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + // Skip OIDC + signingConfigCreate.NoDefaultOIDC = true + err = signingConfigCreate.Exec(ctx) + must(err, t) + sc, err = root.NewSigningConfigFromPath(outPath) + must(err, t) + if len(sc.OIDCProviderURLs()) != 0 { + t.Fatal("expected no OIDC provider services") } - 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 - } + // Skip TSA + signingConfigCreate.NoDefaultTSA = true + err = signingConfigCreate.Exec(ctx) + must(err, t) + sc, err = root.NewSigningConfigFromPath(outPath) + must(err, t) + if len(sc.TimestampAuthorityURLs()) != 0 { + t.Fatal("expected no timestamp authority services") } +} - tsaCA := &root.SigstoreTimestampingAuthority{ - Root: certs[len(certs)-1], - Intermediates: certs[:len(certs)-1], +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"`, + ) } +} - trustedRoot, err := root.NewTrustedRoot(root.TrustedRootMediaType01, nil, nil, []root.TimestampingAuthority{tsaCA}, nil) +func TestAttestVerifySPDXJSON(t *testing.T) { + attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.spdx.json") if err != nil { - t.Error(err) + 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"`, + ) } +} - trustedRootPath := filepath.Join(td, "trustedroot.json") - trustedRootBytes, err := trustedRoot.MarshalJSON() +func TestAttestVerifyCycloneDXJSON(t *testing.T) { + attestationBytes, err := os.ReadFile("./testdata/bom-go-mod.cyclonedx.json") 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, + for _, newBundleFormat := range []bool{false, true} { + attestVerify(t, + newBundleFormat, + "cyclonedx", + string(attestationBytes), + `predicate: specVersion: "1.4"`, + `predicate: specVersion: "7.7"`, + ) } +} - verifyBlobAttestation := cliverify.VerifyBlobAttestationCommand{ - KeyOpts: ko, - PredicateType: predicateType, - IgnoreTlog: true, - CheckClaims: true, - TrustedRootPath: trustedRootPath, +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"`, + ) } - - 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) - +func attestVerify(t *testing.T, newBundleFormat bool, predicateType, attestation, goodCue, badCue string) { repo, stop := reg(t) defer stop() td := t.TempDir() - imgName := path.Join(repo, "cosign-verify-caroots-e2e") + 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() - blob := "someblob2sign" - b := bytes.Buffer{} - blobRef := filepath.Join(td, blob) - if err := os.WriteFile(blobRef, []byte(blob), 0644); err != nil { + _, 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), 0o600); 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}) + // 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) - 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) + // Use cue to verify attestation + policyPath := filepath.Join(td, "policy.cue") + verifyAttestation.PredicateType = predicateType + verifyAttestation.Policies = []string{policyPath} - rootPool := x509.NewCertPool() - rootPool.AddCert(rootCert) + // Fail case + if err := os.WriteFile(policyPath, []byte(badCue), 0o600); err != nil { + t.Fatal(err) + } + mustErr(verifyAttestation.Exec(ctx, []string{imgName}), t) - payloadref := mkfile(b.String(), td, t) + // Success case + if err := os.WriteFile(policyPath, []byte(goodCue), 0o600); err != nil { + t.Fatal(err) + } + must(verifyAttestation.Exec(ctx, []string{imgName}), 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) + // Look for a specific annotation + mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) +} - pemrootBundleRef := mkfile(string(append(pemRoot, pemRoot02...)), td, t) - pemsubBundleRef := mkfile(string(append(pemSub, pemSub02...)), td, t) +func TestAttestationDownload(t *testing.T) { + repo, stop := reg(t) + defer stop() + td := t.TempDir() - tsclient, err := tsaclient.GetTimestampClient(server.URL) - if err != nil { - t.Error(err) + 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), 0o600); err != nil { + t.Fatal(err) } - chain, err := tsclient.Timestamp.GetTimestampCertChain(nil) - if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", 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), 0o600); err != nil { + t.Fatal(err) } - tsaChainRef, err := os.CreateTemp(os.TempDir(), "tempfile") + ref, err := name.ParseReference(imgName) if err != nil { - t.Fatalf("error creating temp file: %v", err) + t.Fatal(err) } - defer os.Remove(tsaChainRef.Name()) - _, err = tsaChainRef.WriteString(chain.Payload) + regOpts := options.RegistryOptions{} + ociremoteOpts, err := regOpts.ClientOpts(ctx) if err != nil { - t.Fatalf("error writing chain payload to temp file: %v", err) + t.Fatal(err) } - tsBytes, err := tsa.GetTimestampedSignature(signature, client.NewTSAClient(server.URL+"/api/v1/timestamp")) - if err != nil { - t.Fatalf("unexpected error creating timestamp: %v", 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, os.Stdout), 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), 0o600); 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), 0o600); 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, os.Stdout), 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), 0o600); 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, os.Stdout), 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), 0o600); 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), 0o600); 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) { + 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), 0o600); 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: tsaURL + "/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(tsaURL) + 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) + + // Ensure it verifies if you default to the new protobuf bundle format + verifyAttestation.NewBundleFormat = true + must(verifyAttestation.Exec(ctx, []string{imgName}), t) +} + +func TestAttestationBlobRFC3161Timestamp(t *testing.T) { + 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), 0o600); err != nil { + t.Fatal(err) + } + + predicatePath := filepath.Join(td, "predicate") + if err := os.WriteFile(predicatePath, []byte(predicate), 0o600); 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: tsaURL + "/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(tsaURL) + 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, 0o600); 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() + + 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), 0o644); err != nil { + t.Fatal(err) + } + must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t) + + rootCert, rootKey, _ := cert_test.GenerateRootCa() + subCert, subKey, _ := cert_test.GenerateSubordinateCa(rootCert, rootKey) + leafCert, privKey, _ := cert_test.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, _ := cert_test.GenerateRootCa() + subCert02, subKey02, _ := cert_test.GenerateSubordinateCa(rootCert02, rootKey02) + leafCert02, _, _ := cert_test.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(tsaURL) + 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 := getTimestampedSignature(signature, client.NewTSAClient(tsaURL+"/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(ctx, 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(t.Context(), 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(t.Context(), 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(t.Context(), 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) { + client, err := tsaclient.GetTimestampClient(tsaURL) + 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: tsaURL + "/api/v1/timestamp", + } + so := options.SignOptions{ + Upload: true, + TlogUpload: false, + } + + // Sign the image + must(sign.SignCmd(t.Context(), 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) + } + + client, err := tsaclient.GetTimestampClient(tsaURL) + 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: tsaURL + "/api/v1/timestamp", + RekorURL: rekorURL, + SkipConfirmation: true, + } + so := options.SignOptions{ + Upload: true, + TlogUpload: true, + } + + // Sign the image + must(sign.SignCmd(t.Context(), 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 TestAttachWithRFC3161Timestamp(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, _ := cert_test.GenerateRootCa() + subCert, subKey, _ := cert_test.GenerateSubordinateCa(rootCert, rootKey) + leafCert, privKey, _ := cert_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}) + + 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(tsaURL) + 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 := getTimestampedSignature(signature, client.NewTSAClient(tsaURL+"/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(), pemrootRef, true, true), 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, os.Stdout), t) + + // Now sign the image + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + } + so := options.SignOptions{ + Upload: true, + } + must(sign.SignCmd(ctx, 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, os.Stdout), t) + + // Signing again should work just fine... + must(sign.SignCmd(ctx, 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"), nil); 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, } - rfc3161TSRef := mkfile(string(tsBytes), td, t) + so := options.SignOptions{ + Upload: true, + TlogUpload: true, + } + must(sign.SignCmd(t.Context(), 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) - // Upload it! - err = attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadref, pemleafRef, certchainRef, rfc3161TSRef, "", imgName) + // Now sign with the other key too + ko.KeyRef = priv2 + must(sign.SignCmd(t.Context(), 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), 0o644); 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: privKeyRef, + KeyRef: privKeyPath1, PassFunc: passFunc, } - blobSig, err := sign.SignBlobCmd(ro, ko, blobRef, true, "", "", false) + sig, err := sign.SignBlobCmd(ctx, ro, ko, bp, "", "", 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, - }, + // 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), 0o644); 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(ctx, 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(ctx, 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), 0o644); 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(ctx, 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 TestSignBlobNewBundleNonSHA256(t *testing.T) { + td1 := t.TempDir() + + blob := "someblob" + blobPath := filepath.Join(td1, blob) + if err := os.WriteFile(blobPath, []byte(blob), 0o644); err != nil { + t.Fatal(err) } - 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) - } - } - } - }) + + bundlePath := filepath.Join(td1, "bundle.sigstore.json") + + ctx := context.Background() + + // Generate ecdsa-p521 key + _, privKeyPath, pubKeyPath := keypairWithAlgorithm(t, td1, v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512) + + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + BundlePath: bundlePath, + NewBundleFormat: true, + } + if _, err := sign.SignBlobCmd(ctx, ro, ko, blobPath, "", "", true, "", "", false); err != nil { + t.Fatal(err) + } + + ko1 := options.KeyOpts{ + KeyRef: pubKeyPath, + BundlePath: bundlePath, + NewBundleFormat: true, + } + verifyBlobCmd := cliverify.VerifyBlobCmd{ + KeyOpts: ko1, + IgnoreTlog: true, + HashAlgorithm: crypto.SHA512, } + must(verifyBlobCmd.Exec(ctx, blobPath), t) } -func TestRekorBundle(t *testing.T) { +func TestSignBlobNewBundleNonDefaultAlgorithm(t *testing.T) { + tts := []struct { + algo v1.PublicKeyDetails + }{ + {v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384}, + {v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256}, + // ed25519 and ed25519ph aren't supported for the default flow. + // By default, we sign using the prehash variant for a ed25519 key. + // Rekor supports ed25519ph for a hashedrekord, but Fulcio doesn't. + } + td := t.TempDir() - err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) + + // set up SIGSTORE_ variables to point to keys for the local instances + err := setLocalEnv(t, td) if err != nil { t.Fatal(err) } - repo, stop := reg(t) - defer stop() - - imgName := path.Join(repo, "cosign-e2e") + err = fulcioroots.ReInit() + if err != nil { + t.Fatal(err) + } - _, _, cleanup := mkimage(t, imgName) - defer cleanup() + identityToken, err := getOIDCToken() + if err != nil { + t.Fatal(err) + } - _, privKeyPath, pubKeyPath := keypair(t, td) + // Use the CreateCmd approach to create a trusted root + rootFile := os.Getenv("SIGSTORE_ROOT_FILE") + ctfePubKey := os.Getenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE") + rekorPubKey := os.Getenv("SIGSTORE_REKOR_PUBLIC_KEY") + // Create a temporary file for the trusted root JSON + trustedRootPath := filepath.Join(td, "trustedroot.json") - ko := options.KeyOpts{ - KeyRef: privKeyPath, - PassFunc: passFunc, - RekorURL: rekorURL, - SkipConfirmation: true, + // Create a CreateCmd instance + createCmd := trustedroot.CreateCmd{ + CertChain: []string{rootFile}, + Out: trustedRootPath, + RekorKeyPath: []string{rekorPubKey}, + CtfeKeyPath: []string{ctfePubKey}, } - so := options.SignOptions{ - Upload: true, - TlogUpload: true, + + // Execute the command to create the trusted root + if err := createCmd.Exec(context.Background()); err != nil { + t.Fatal(err) } - // Sign the image - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) - // Make sure verify works - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + for _, tt := range tts { + t.Run(tt.algo.String(), func(t *testing.T) { + td1 := t.TempDir() - // Make sure offline verification works with bundling - must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t) + blob := "someblob" + blobPath := filepath.Join(td1, blob) + if err := os.WriteFile(blobPath, []byte(blob), 0o644); err != nil { + t.Fatal(err) + } + + bundlePath := filepath.Join(td1, "bundle.sigstore.json") + + ctx := context.Background() + _, privKeyPath, _ := keypairWithAlgorithm(t, td1, tt.algo) + + verifyBlobCmd := cliverify.VerifyBlobCmd{ + TrustedRootPath: trustedRootPath, + KeyOpts: options.KeyOpts{ + FulcioURL: fulcioURL, + RekorURL: rekorURL, + PassFunc: passFunc, + BundlePath: bundlePath, + NewBundleFormat: true, + SkipConfirmation: true, + }, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuerRegexp: ".*", + CertIdentityRegexp: ".*", + }, + } + + // Verify should fail before bundle is written + mustErr(verifyBlobCmd.Exec(ctx, blobPath), t) + + // Produce signed bundle + ko := options.KeyOpts{ + FulcioURL: fulcioURL, + RekorURL: rekorURL, + IDToken: identityToken, + KeyRef: privKeyPath, + PassFunc: passFunc, + BundlePath: bundlePath, + NewBundleFormat: true, + IssueCertificateForExistingKey: true, + SkipConfirmation: true, + } + + if _, err := sign.SignBlobCmd(ctx, ro, ko, blobPath, "", "", true, "", "", true); err != nil { + t.Fatal(err) + } + + // Copy bundle to /tmp with test name + bundleBytes, err := os.ReadFile(bundlePath) + if err != nil { + t.Fatal(err) + } + tmpBundlePath := filepath.Join("/tmp", fmt.Sprintf("bundle-%s", tt.algo)) + if err := os.WriteFile(tmpBundlePath, bundleBytes, 0o644); err != nil { + t.Fatal(err) + } + + // Verify should succeed now that bundle is written + must(verifyBlobCmd.Exec(ctx, blobPath), t) + }) + } } -func TestRekorOutput(t *testing.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) } - repo, stop := reg(t) - defer stop() - - imgName := path.Join(repo, "cosign-e2e") + blob := "someblob" + bp := filepath.Join(td, blob) bundlePath := filepath.Join(td, "bundle.sig") + tsPath := filepath.Join(td, "rfc3161Timestamp.json") - _, _, cleanup := mkimage(t, imgName) - defer cleanup() + if err := os.WriteFile(bp, []byte(blob), 0o644); err != nil { + t.Fatal(err) + } - _, privKeyPath, pubKeyPath := keypair(t, td) + client, err := tsaclient.GetTimestampClient(tsaURL) + if err != nil { + t.Error(err) + } - ko := options.KeyOpts{ - KeyRef: privKeyPath, - PassFunc: passFunc, - RekorURL: rekorURL, - BundlePath: bundlePath, + chain, err := client.Timestamp.GetTimestampCertChain(nil) + if err != nil { + t.Fatalf("unexpected error getting timestamp chain: %v", err) } - so := options.SignOptions{ - Upload: true, - TlogUpload: true, + + 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) } - // Sign the image - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) - // Make sure verify works - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + _, privKeyPath1, pubKeyPath1 := keypair(t, td) - if file, err := os.ReadFile(bundlePath); err != nil { + 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: tsaURL + "/api/v1/timestamp", + RekorURL: rekorURL, + SkipConfirmation: true, + } + if _, err := sign.SignBlobCmd(ctx, ro, ko, bp, "", "", true, "", "", false); 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) -} + // Now verify should work + must(verifyBlobCmd.Exec(ctx, bp), t) -func TestFulcioBundle(t *testing.T) { - td := t.TempDir() - err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) - if err != nil { + // Now we turn on the tlog and sign again + if _, err := sign.SignBlobCmd(ctx, 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") - - _, _, cleanup := mkimage(t, imgName) + _, desc, 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, - } + // 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) - // Sign the image - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) - // Make sure verify works - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, 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) -} + // 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) -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) + equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) + equals(ss.Optional["foo"], "bar", t) +} - client, err := tsaclient.GetTimestampClient(server.URL) +func TestSaveLoad(t *testing.T) { + td := t.TempDir() + err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { - t.Error(err) + t.Fatal(err) } - - chain, err := client.Timestamp.GetTimestampCertChain(nil) - if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) + tests := []struct { + description string + getSignedEntity func(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) + newBundle bool + }{ + { + description: "save and load an image", + getSignedEntity: mkimage, + newBundle: false, + }, + { + description: "save and load an image bundle", + getSignedEntity: mkimage, + newBundle: true, + }, + { + description: "save and load an image index", + getSignedEntity: mkimageindex, + newBundle: false, + }, } + for i, test := range tests { + t.Run(test.description, func(t *testing.T) { + repo, stop := reg(t) + defer stop() + keysDir := t.TempDir() - 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) - } + imgName := path.Join(repo, fmt.Sprintf("save-load-%d", i)) - repo, stop := reg(t) - defer stop() - td := t.TempDir() + _, _, cleanup := test.getSignedEntity(t, imgName) + defer cleanup() - imgName := path.Join(repo, "cosign-e2e") + _, privKeyPath, pubKeyPath := keypair(t, keysDir) - _, _, cleanup := mkimage(t, imgName) - defer cleanup() + 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, + NewBundleFormat: test.newBundle, + } + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) + trustedRootPath := prepareTrustedRoot(t, "") + bundleVerifyCmd := cliverify.VerifyCommand{ + CommonVerifyOptions: options.CommonVerifyOptions{ + TrustedRootPath: trustedRootPath, + }, + KeyRef: pubKeyPath, + NewBundleFormat: true, + UseSignedTimestamps: false, + } - _, privKeyPath, pubKeyPath := keypair(t, td) + if test.newBundle { + must(bundleVerifyCmd.Exec(ctx, []string{imgName}), t) + } else { + must(verify(pubKeyPath, imgName, true, nil, "", false), t) + } - ko := options.KeyOpts{ - KeyRef: privKeyPath, - PassFunc: passFunc, - TSAServerURL: server.URL + "/api/v1/timestamp", - } - so := options.SignOptions{ - Upload: true, - TlogUpload: false, - } + // save the image to a temp dir + imageDir := t.TempDir() + must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) - // 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) + // verify the local image using a local key + // if we are not using protobuf bundle format + if !test.newBundle { + 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) + if test.newBundle { + must(bundleVerifyCmd.Exec(ctx, []string{imgName2}), t) + } else { + must(verify(pubKeyPath, imgName2, true, nil, "", false), t) + } + }) + } } -func TestRekorBundleAndRFC3161Timestamp(t *testing.T) { +// TestSaveLoadAutoDetectFormat verifies that local image verification auto-detects +// the signature format (v2 attached signatures vs v3 bundles) without requiring +// explicit --new-bundle-format flag. This tests the fix for sigstore/cosign#4621. +func TestSaveLoadAutoDetectFormat(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) + // Test v2 attached signatures - this is the main use case for #4621 + // where users have v2 signatures but cosign v3 defaults to --new-bundle-format=true + t.Run("auto-detect v2 attached signatures", func(t *testing.T) { + repo, stop := reg(t) + defer stop() + keysDir := t.TempDir() - client, err := tsaclient.GetTimestampClient(server.URL) - if err != nil { - t.Error(err) - } + imgName := path.Join(repo, "auto-detect-v2") - chain, err := client.Timestamp.GetTimestampCertChain(nil) - if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) - } + _, _, cleanup := mkimage(t, imgName) + defer cleanup() - 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) - } + _, privKeyPath, pubKeyPath := keypair(t, keysDir) - repo, stop := reg(t) - defer stop() + ctx := context.Background() + // Sign the image with v2 format (no bundle) + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, + } + so := options.SignOptions{ + Upload: true, + TlogUpload: true, + NewBundleFormat: false, // v2 format + } + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), 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 WITHOUT specifying --new-bundle-format + // The format should be auto-detected as v2, allowing verification to succeed + verifyCmd := cliverify.VerifyCommand{ + KeyRef: pubKeyPath, + LocalImage: true, + MaxWorkers: 10, + // Explicitly NOT setting NewBundleFormat - should auto-detect as v2 + } + must(verifyCmd.Exec(ctx, []string{imageDir}), t) + }) - imgName := path.Join(repo, "cosign-e2e") + // For v3 bundles, we now support full local verification. The bundles are stored as + // OCI referrers and can be verified directly from the local layout without loading + // back to a registry. + t.Run("auto-detect v3 bundle format and verify locally", func(t *testing.T) { + repo, stop := reg(t) + defer stop() + keysDir := t.TempDir() - _, _, cleanup := mkimage(t, imgName) - defer cleanup() + imgName := path.Join(repo, "auto-detect-v3") - _, privKeyPath, pubKeyPath := keypair(t, td) + _, _, cleanup := mkimage(t, imgName) + defer cleanup() - ko := options.KeyOpts{ - KeyRef: privKeyPath, - PassFunc: passFunc, - TSAServerURL: server.URL + "/api/v1/timestamp", - RekorURL: rekorURL, - SkipConfirmation: true, - } - so := options.SignOptions{ - Upload: true, - TlogUpload: true, - } + _, privKeyPath, pubKeyPath := keypair(t, keysDir) - // 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) + ctx := context.Background() + // Sign the image with v3 format (bundle) + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, + } + so := options.SignOptions{ + Upload: true, + TlogUpload: true, + NewBundleFormat: true, // v3 format + } + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) + + // Save the image to a temp dir + imageDir := t.TempDir() + must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) + + // Verify locally with NewBundleFormat enabled + trustedRootPath := prepareTrustedRoot(t, "") + verifyCmd := cliverify.VerifyCommand{ + CommonVerifyOptions: options.CommonVerifyOptions{ + TrustedRootPath: trustedRootPath, + }, + KeyRef: pubKeyPath, + LocalImage: true, + NewBundleFormat: true, + UseSignedTimestamps: false, + MaxWorkers: 10, + } + must(verifyCmd.Exec(ctx, []string{imageDir}), t) + }) } -func TestDuplicateSign(t *testing.T) { +func TestSaveLoadAttestation(t *testing.T) { td := t.TempDir() err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) if err != nil { @@ -2032,942 +3916,1035 @@ func TestDuplicateSign(t *testing.T) { repo, stop := reg(t) defer stop() - imgName := path.Join(repo, "cosign-e2e") + imgName := path.Join(repo, "save-load") - ref, _, cleanup := mkimage(t, imgName) + _, _, 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 + // Now sign the image and verify it ko := options.KeyOpts{ - KeyRef: privKeyPath, - PassFunc: passFunc, + KeyRef: privKeyPath, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, } 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) + Upload: true, + TlogUpload: true, } -} - -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) -} + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) + must(verify(pubKeyPath, imgName, 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 { + // 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), 0o600); 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) + // 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", } - // make sure the secret actually exists + must(attestCommand.Exec(ctx, imgName), t) - 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") + // 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, } - // Clean up the secret (so tests can be re-run locally) - err = client.CoreV1().Secrets(namespace).Delete(ctx, name, metav1.DeleteOptions{}) - if err != nil { + verifyAttestation.PredicateType = "slsaprovenance" + verifyAttestation.Policies = []string{policyPath} + // Success case (remote) + cuePolicy := `predicate: builder: id: "2"` + if err := os.WriteFile(policyPath, []byte(cuePolicy), 0o600); 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 TestMultipleSignatures(t *testing.T) { +func TestAttestDownloadAttachNewBundle(t *testing.T) { + repo, stop := reg(t) + defer stop() + + imgName := path.Join(repo, "attest-new-bundle") + _, _, cleanup := mkimage(t, imgName) + defer cleanup() + + // Download should fail before attesting + ctx := context.Background() + regOpts := options.RegistryOptions{} + attOpts := options.AttestationDownloadOptions{} + mustErr(download.AttestationCmd(ctx, regOpts, attOpts, imgName, os.Stdout), t) + + // Attest first image td := t.TempDir() - err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) - if err != nil { + _, privKeyPath, _ := keypair(t, td) + ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, NewBundleFormat: true} + + slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` + slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") + if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0o600); err != nil { t.Fatal(err) } - repo, stop := reg(t) - defer stop() + attestCommand := attest.AttestCommand{ + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + RekorEntryType: "dsse", + } - td1 := t.TempDir() - td2 := t.TempDir() + must(attestCommand.Exec(ctx, imgName), t) - imgName := path.Join(repo, "cosign-e2e") + // Download should now succeed - redirect stdout to use with attach + out := bytes.Buffer{} + must(download.AttestationCmd(ctx, regOpts, attOpts, imgName, &out), t) - _, _, cleanup := mkimage(t, imgName) + // Create a new image to attach to + img2Name := path.Join(repo, "attest-new-bundle-2") + _, _, cleanup = mkimage(t, img2Name) defer cleanup() - _, priv1, pub1 := keypair(t, td1) - _, priv2, pub2 := keypair(t, td2) + bundlePath := filepath.Join(td, "downloaded-bundle.sigstore.json") + if err := os.WriteFile(bundlePath, out.Bytes(), 0o600); err != nil { + t.Fatal(err) + } - // Verify should fail at first for both keys - mustErr(verify(pub1, imgName, true, nil, "", false), t) - mustErr(verify(pub2, imgName, true, nil, "", false), t) + must(attach.AttestationCmd(ctx, regOpts, []string{bundlePath}, img2Name), t) - // Now sign the image with one key - ko := options.KeyOpts{ - KeyRef: priv1, - PassFunc: passFunc, - RekorURL: rekorURL, - SkipConfirmation: true, - } + // Download should succeed on second image + must(download.AttestationCmd(ctx, regOpts, attOpts, img2Name, os.Stdout), t) +} + +func TestSignDownloadAttachNewBundle(t *testing.T) { + repo, stop := reg(t) + defer stop() + + imgName := path.Join(repo, "sign-new-bundle") + _, _, cleanup := mkimage(t, imgName) + defer cleanup() + + // Download should fail before attesting + ctx := context.Background() + regOpts := options.RegistryOptions{} + mustErr(download.SignatureCmd(ctx, regOpts, imgName, os.Stdout), t) + + // Sign first image + td := t.TempDir() + _, privKeyPath, _ := keypair(t, td) + ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} so := options.SignOptions{ - Upload: true, - TlogUpload: true, + NewBundleFormat: true, + Upload: 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) + must(sign.SignCmd(ctx, 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) + // Download should now succeed - redirect stdout to use with attach + out := bytes.Buffer{} + must(download.SignatureCmd(ctx, regOpts, imgName, &out), t) + + // Create a new image to attach to + img2Name := path.Join(repo, "sign-new-bundle-2") + _, _, cleanup = mkimage(t, img2Name) + defer cleanup() + + bundlePath := filepath.Join(td, "downloaded-bundle.sigstore.json") + if err := os.WriteFile(bundlePath, out.Bytes(), 0o600); err != nil { + t.Fatal(err) + } + + must(attach.SignatureCmd(ctx, regOpts, "", bundlePath, "", "", "", "", img2Name), t) + + // Download should succeed on second image + must(download.SignatureCmd(ctx, regOpts, img2Name, os.Stdout), t) } -func TestSignBlob(t *testing.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) } - 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) - } + repo, stop := reg(t) + defer stop() + ctx := context.Background() - _, privKeyPath1, pubKeyPath1 := keypair(t, td1) - _, _, pubKeyPath2 := keypair(t, td2) + imgName := path.Join(repo, "sbom-image") + img, _, cleanup := mkimage(t, imgName) + defer cleanup() - ctx := context.Background() + out := bytes.Buffer{} - 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, + _, 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") } - cmd2 := cliverify.VerifyBlobCmd{ - KeyOpts: ko2, - SigRef: "badsig", - IgnoreTlog: true, + _, err = download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, img.Name(), &out) + if err == nil { + t.Fatal("Expected error") } - mustErr(cmd1.Exec(ctx, blob), t) - mustErr(cmd2.Exec(ctx, blob), t) + t.Log(out.String()) + out.Reset() - // Now sign the blob with one key - ko := options.KeyOpts{ - KeyRef: privKeyPath1, - PassFunc: passFunc, - } - sig, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false) + // 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) } - // 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) + t.Log(out.String()) + if len(sboms) != 1 { + t.Fatalf("Expected one sbom, got %d", len(sboms)) } - - err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td1) + 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) - 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) + mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t) + mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), 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 sign the sbom with one key + ko1 := options.KeyOpts{ + KeyRef: privKeyPath1, + PassFunc: passFunc, + RekorURL: rekorURL, } - // 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) + so := options.SignOptions{ + Upload: true, + TlogUpload: true, + Attachment: "sbom", } + must(sign.SignCmd(ctx, ro, ko1, so, []string{imgName}), t) - // 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) + // 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 TestSignBlobNewBundle(t *testing.T) { - td1 := t.TempDir() +func TestNoTlog(t *testing.T) { + repo, stop := reg(t) + defer stop() + td := t.TempDir() - blob := "someblob" - blobPath := filepath.Join(td1, blob) - if err := os.WriteFile(blobPath, []byte(blob), 0644); err != nil { - t.Fatal(err) - } + imgName := path.Join(repo, "cosign-e2e") - bundlePath := filepath.Join(td1, "bundle.sigstore.json") + _, _, cleanup := mkimage(t, imgName) + defer cleanup() - ctx := context.Background() - _, privKeyPath, pubKeyPath := keypair(t, td1) + _, privKeyPath, pubKeyPath := keypair(t, td) - ko1 := options.KeyOpts{ - KeyRef: pubKeyPath, - BundlePath: bundlePath, - NewBundleFormat: true, - } + // Verify should fail at first + mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t) - verifyBlobCmd := cliverify.VerifyBlobCmd{ - KeyOpts: ko1, - IgnoreTlog: true, + // Now sign the image without the tlog + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + RekorURL: rekorURL, + } + so := options.SignOptions{ + Upload: true, } + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) - // Verify should fail before bundle is written - mustErr(verifyBlobCmd.Exec(ctx, blobPath), 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() - // Produce signed bundle - ko := options.KeyOpts{ - KeyRef: privKeyPath, - PassFunc: passFunc, - BundlePath: bundlePath, - NewBundleFormat: true, - } + outFile := "output.pub" + outPath := filepath.Join(td, outFile) + outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0o600) + must(err, t) - if _, err := sign.SignBlobCmd(ro, ko, blobPath, true, "", "", false); err != nil { - t.Fatal(err) + pk := publickey.Pkopts{ + KeyRef: privKeyPath, } + must(publickey.GetPublicKey(ctx, pk, publickey.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t) - // Verify should succeed now that bundle is written - must(verifyBlobCmd.Exec(ctx, blobPath), t) + output, err := os.ReadFile(outPath) + must(err, t) + equals(keys.PublicBytes, output, t) } -func TestSignBlobRFC3161TimestampBundle(t *testing.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) } - // 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) - } + regName, stop := reg(t) + defer stop() - chain, err := client.Timestamp.GetTimestampCertChain(nil) - if err != nil { - t.Fatalf("unexpected error getting timestamp chain: %v", err) - } + img1 := path.Join(regName, "cosign-e2e") - 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) - } + imgRef, _, cleanup := mkimage(t, img1) + defer cleanup() - _, privKeyPath1, pubKeyPath1 := keypair(t, td) + _, privKeyPath, pubKeyPath := keypair(t, td) ctx := context.Background() - ko1 := options.KeyOpts{ - KeyRef: pubKeyPath1, - BundlePath: bundlePath, - RFC3161TimestampPath: tsPath, - TSACertChainPath: file.Name(), + // 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, } - // Verify should fail on a bad input - verifyBlobCmd := cliverify.VerifyBlobCmd{ - KeyOpts: ko1, - IgnoreTlog: true, + must(sign.SignCmd(ctx, 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() } - 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, + // 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(ctx, 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") } - if _, err := sign.SignBlobCmd(ro, ko, bp, true, "", "", false); err != nil { + sigsTag, err := ociremote.SignatureTag(imgRef2) + if 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 { + // 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) } - // 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) -} + mustErr(verify(pubKeyPath, img2, true, nil, "", false), t) -func TestGenerate(t *testing.T) { - repo, stop := reg(t) - defer stop() + 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) + } - imgName := path.Join(repo, "cosign-e2e") - _, desc, cleanup := mkimage(t, imgName) - defer cleanup() + // 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) +} - // 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) +func TestAttestBlobSignVerify(t *testing.T) { + blob := "someblob" + predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` + predicateType := "slsaprovenance" + statement := `{"_type":"https://in-toto.io/Statement/v1","subject":[{"name":"someblob","digest":{"alg":"7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3"}}],"predicateType":"something","predicate":{}}` - equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) + td1 := t.TempDir() + t.Cleanup(func() { + os.RemoveAll(td1) + }) - // 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) + bp := filepath.Join(td1, blob) + if err := os.WriteFile(bp, []byte(blob), 0o644); err != nil { + t.Fatal(err) + } - equals(desc.Digest.String(), ss.Critical.Image.DockerManifestDigest, t) - equals(ss.Optional["foo"], "bar", t) -} + anotherBlob := filepath.Join(td1, "another-blob") + if err := os.WriteFile(anotherBlob, []byte("another-blob"), 0o644); err != nil { + t.Fatal(err) + } -func TestSaveLoad(t *testing.T) { - td := t.TempDir() - err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) - if err != nil { + predicatePath := filepath.Join(td1, "predicate") + if err := os.WriteFile(predicatePath, []byte(predicate), 0o644); 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, - }, + + statementPath := filepath.Join(td1, "statement") + if err := os.WriteFile(statementPath, []byte(statement), 0o644); err != nil { + t.Fatal(err) } - 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)) + outputSignature := filepath.Join(td1, "signature") - _, _, cleanup := test.getSignedEntity(t, imgName) - defer cleanup() + _, privKeyPath1, pubKeyPath1 := keypair(t, td1) - _, privKeyPath, pubKeyPath := keypair(t, keysDir) + 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) - 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 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) - // save the image to a temp dir - imageDir := t.TempDir() - must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) + // Make sure we fail with the wrong predicate type + blobVerifyAttestationCmd.PredicateType = "custom" + mustErr(blobVerifyAttestationCmd.Exec(ctx, bp), t) - // verify the local image using a local key - must(verifyLocal(pubKeyPath, imageDir, true, nil, ""), t) + // Make sure we fail with the wrong blob (set the predicate type back) + blobVerifyAttestationCmd.PredicateType = predicateType + mustErr(blobVerifyAttestationCmd.Exec(ctx, anotherBlob), 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) - }) + // Test statement signing + attestBlobCmd = attest.AttestBlobCommand{ + KeyOpts: ko, + StatementPath: statementPath, + OutputSignature: outputSignature, + RekorEntryType: "dsse", + } + must(attestBlobCmd.Exec(ctx, bp), t) + + // Test statement verification + ko = options.KeyOpts{ + KeyRef: pubKeyPath1, + } + blobVerifyAttestationCmd = cliverify.VerifyBlobAttestationCommand{ + KeyOpts: ko, + Digest: "7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3", + DigestAlg: "alg", + SignaturePath: outputSignature, + IgnoreTlog: true, + PredicateType: "something", } + must(blobVerifyAttestationCmd.Exec(ctx, bp), t) } -func TestSaveLoadAttestation(t *testing.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) } - repo, stop := reg(t) + regName, stop := reg(t) defer stop() - imgName := path.Join(repo, "save-load") + img1 := path.Join(regName, "cosign-e2e") - _, _, cleanup := mkimage(t, imgName) + imgRef, _, cleanup := mkimage(t, img1) 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, + + // 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, } - so := options.SignOptions{ - Upload: true, - TlogUpload: true, + must(sign.SignCmd(ctx, 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(sign.SignCmd(ro, ko, so, []string{imgName}), t) - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verifyCmd.Exec(ctx, []string{img1}), 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) - } + // Get signatures + si, err := ociremote.SignedEntity(imgRef) + must(err, t) + sigs, err := si.Signatures() + must(err, t) + gottenSigs, err := sigs.Get() + must(err, t) - // 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", + fakeBundle := &bundle.RekorBundle{ + SignedEntryTimestamp: []byte(""), + Payload: bundle.RekorPayload{ + Body: "", + }, } - must(attestCommand.Exec(ctx, imgName), t) + newSig, err := mutate.Signature(gottenSigs[0], mutate.WithBundle(fakeBundle)) + must(err, t) - // save the image to a temp dir - imageDir := t.TempDir() - must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) + sigsTag, err := ociremote.SignatureTag(imgRef) + must(err, 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, + if err := remote.Delete(sigsTag); err != nil { + t.Fatal(err) } - 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 { + + 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) } - must(verifyAttestation.Exec(ctx, []string{imgName2}), t) - // Success case (local) - verifyAttestation.LocalImage = true - must(verifyAttestation.Exec(ctx, []string{imageDir}), t) + + // Confirm offline verification fails + mustErr(verifyCmd.Exec(ctx, []string{img1}), t) } -func TestAttachSBOM(t *testing.T) { +func TestDockerfileVerify(t *testing.T) { td := t.TempDir() - err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) + + // 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(ctx, ro, ko, so, []string{signedImg1}), t) + must(sign.SignCmd(ctx, ro, ko, so, []string{signedImg2}), t) - imgName := path.Join(repo, "sbom-image") - img, _, cleanup := mkimage(t, imgName) - defer cleanup() + // create the dockerfiles + singleStageDockerfileContents := fmt.Sprintf(` +FROM %s +`, signedImg1) + singleStageDockerfile := mkfile(singleStageDockerfileContents, td, t) - out := bytes.Buffer{} + unsignedBuildStageDockerfileContents := fmt.Sprintf(` +FROM %s - _, 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") +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("ISSUER_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, + }, } - _, err = download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, img.Name(), &out) - if err == nil { - t.Fatal("Expected error") + 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) + } + }) } - t.Log(out.String()) - out.Reset() +} - // Upload it! - must(attach.SBOMCmd(ctx, options.RegistryOptions{}, options.RegistryExperimentalOptions{}, "./testdata/bom-go-mod.spdx", "spdx", imgName), t) +func TestManifestVerify(t *testing.T) { + td := t.TempDir() - sboms, err := download.SBOMCmd(ctx, options.RegistryOptions{}, options.SBOMDownloadOptions{}, imgName, &out) + // set up SIGSTORE_ variables to point to keys for the local instances + err := setLocalEnv(t, td) 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") + + // unset the roots that were generated for timestamp signing, they won't work here + err = fulcioroots.ReInit() 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", + identityToken, err := getOIDCToken() + if err != nil { + t.Fatal(err) } - 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) { + // create some images repo, stop := reg(t) defer stop() - td := t.TempDir() - - imgName := path.Join(repo, "cosign-e2e") - - _, _, cleanup := mkimage(t, imgName) + 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() - _, 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 + // sign the images using --identity-token ko := options.KeyOpts{ - KeyRef: privKeyPath, - PassFunc: passFunc, - RekorURL: rekorURL, + FulcioURL: fulcioURL, + RekorURL: rekorURL, + IDToken: identityToken, + SkipConfirmation: true, } so := options.SignOptions{ - Upload: true, + Upload: true, + TlogUpload: true, + SkipConfirmation: 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() + must(sign.SignCmd(ctx, ro, ko, so, []string{signedImg}), t) - outFile := "output.pub" - outPath := filepath.Join(td, outFile) - outWriter, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0600) - must(err, 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) - pk := publickey.Pkopts{ - KeyRef: privKeyPath, + issuer := os.Getenv("ISSUER_URL") + + tests := []struct { + name string + manifest string + wantErr bool + }{ + { + name: "signed manifest", + manifest: signedManifest, + }, + { + name: "unsigned manifest", + manifest: unsignedManifest, + wantErr: true, + }, } - must(publickey.GetPublicKey(ctx, pk, publickey.NamedWriter{Name: outPath, Writer: outWriter}, passFunc), t) + 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) + } + }) + } +} - output, err := os.ReadFile(outPath) - must(err, t) - equals(keys.PublicBytes, output, 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 } -// 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) { +func TestSignVerifyWithRepoOverride(t *testing.T) { + cosignRepo := env.Getenv(env.VariableRepository) + if cosignRepo == "" { + t.Skip("Skipping COSIGN_REPOSITORY test because a second repository and COSIGN_REPOSITORY must be set up") + } 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) + repo, stop := reg(t) defer stop() - img1 := path.Join(regName, "cosign-e2e") + imgName := path.Join(repo, "cosign-e2e") - imgRef, _, cleanup := mkimage(t, img1) + name, _, cleanup := mkimage(t, imgName) defer cleanup() + digest, err := crane.Digest(name.String()) + must(err, t) + _, privKeyPath, pubKeyPath := keypair(t, td) - ctx := context.Background() + // Verify should fail at first + mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) - // 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() + // No artifacts yet in the second registry + _, err = crane.ListTags(cosignRepo) + mustErr(err, t) + + // Only one tag in the first registry + tags, err := crane.ListTags(name.String()) must(err, t) - if bund == nil { - t.Fail() - } + assert.Len(t, tags, 1, "expected 1 tag in the first repo") + assert.Equal(t, tags[0], "latest", "expected tag name to be 'latest'") - // 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) + // Now sign the image - 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) + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, } - // 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) + so := options.SignOptions{ + Upload: true, + TlogUpload: true, } - mustErr(verify(pubKeyPath, img2, true, nil, "", false), t) - newSig, err := mutate.Signature(gottenSigs2[0], mutate.WithBundle(bund)) + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) + + // Bundle should appear in the second repo + tags, err = crane.ListTags(cosignRepo) must(err, t) - si2, err = ociremote.SignedEntity(imgRef2, remoteOpts) + assert.Len(t, tags, 1, "expected 1 signature tag in the second repo") + expectedTagName := fmt.Sprintf("%s.sig", strings.ReplaceAll(digest, ":", "-")) + assert.Equal(t, tags[0], expectedTagName, "expected signature tag to match sha256-.sig") + // but not in the first repo + tags, err = crane.ListTags(name.String()) must(err, t) - newImage, err := mutate.AttachSignatureToEntity(si2, newSig) + assert.Len(t, tags, 1, "expected no extra tags in the first repo") + assert.Equal(t, tags[0], "latest", "expected tag name to be 'latest'") + + // Now verify and download should work! + must(verify(pubKeyPath, imgName, true, nil, "", false), t) + + // Sign another image with the new protobuf bundle format + so.NewBundleFormat = true + must(sign.SignCmd(t.Context(), ro, ko, so, []string{name.String()}), t) + + // The new bundle should appear under a new tag for the second repo + tags, err = crane.ListTags(cosignRepo) must(err, t) - if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil { - t.Fatal(err) - } + assert.Len(t, tags, 2, "expected new tag in the second repo") + expectedTagName = strings.ReplaceAll(digest, ":", "-") + assert.Equal(t, tags[0], expectedTagName, "expected new tag to match referrers format") + // but not in the first repo + tags, err = crane.ListTags(name.String()) + must(err, t) + assert.Len(t, tags, 1, "expected no extra tags in the first repo") + assert.Equal(t, tags[0], "latest", "expected tag name to be 'latest'") - // veriyfing image2 now should fail + // Verify should work with new bundle format cmd := cliverify.VerifyCommand{ - KeyRef: pubKeyPath, - RekorURL: rekorURL, - CheckClaims: true, - HashAlgorithm: crypto.SHA256, - MaxWorkers: 10, + KeyRef: pubKeyPath, + RekorURL: rekorURL, + NewBundleFormat: true, + IgnoreTlog: true, } - 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" - statement := `{"_type":"https://in-toto.io/Statement/v1","subject":[{"name":"someblob","digest":{"alg":"123"}}],"predicateType":"something","predicate":{}}` - td1 := t.TempDir() - t.Cleanup(func() { - os.RemoveAll(td1) - }) + ctx := context.Background() + must(cmd.Exec(ctx, []string{imgName}), t) +} - bp := filepath.Join(td1, blob) - if err := os.WriteFile(bp, []byte(blob), 0644); err != nil { +func TestSignVerifyMultipleIdentities(t *testing.T) { + td := t.TempDir() + err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) + if 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) - } + repo, stop := reg(t) + defer stop() - predicatePath := filepath.Join(td1, "predicate") - if err := os.WriteFile(predicatePath, []byte(predicate), 0644); err != nil { - t.Fatal(err) - } + imgName := path.Join(repo, "cosign-e2e") - statementPath := filepath.Join(td1, "statement") - if err := os.WriteFile(statementPath, []byte(statement), 0644); err != nil { - t.Fatal(err) - } + _, _, cleanup := mkimage(t, imgName) + defer cleanup() - outputSignature := filepath.Join(td1, "signature") + _, privKeyPath, pubKeyPath := keypair(t, td) - _, privKeyPath1, pubKeyPath1 := keypair(t, td1) + // Verify should fail at first + mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) - ctx := context.Background() + // Now sign the image with multiple container identities 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, + KeyRef: privKeyPath, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, } - attestBlobCmd := attest.AttestBlobCommand{ - KeyOpts: ko, - PredicatePath: predicatePath, - PredicateType: predicateType, - OutputSignature: outputSignature, - RekorEntryType: "dsse", + so := options.SignOptions{ + Upload: true, + TlogUpload: true, + SignContainerIdentities: []string{"registry/cosign-e2e:tag1", "registry/cosign-e2e:tag2"}, } - must(attestBlobCmd.Exec(ctx, bp), t) + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), 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) + must(verify(pubKeyPath, imgName, true, nil, "", false), t) +} - // Test statement signing - attestBlobCmd = attest.AttestBlobCommand{ - KeyOpts: ko, - StatementPath: statementPath, - OutputSignature: outputSignature, - RekorEntryType: "dsse", - } - must(attestBlobCmd.Exec(ctx, bp), t) +func TestSignVerifyMultipleIdentitiesKeyless(t *testing.T) { + td := t.TempDir() - // Test statement verification - ko = options.KeyOpts{ - KeyRef: pubKeyPath1, - } - blobVerifyAttestationCmd = cliverify.VerifyBlobAttestationCommand{ - KeyOpts: ko, - Digest: "123", - DigestAlg: "alg", - SignaturePath: outputSignature, - IgnoreTlog: true, - PredicateType: "something", + // set up SIGSTORE_ variables to point to keys for the local instances + err := setLocalEnv(t, td) + if err != nil { + t.Fatal(err) } - must(blobVerifyAttestationCmd.Exec(ctx, bp), t) -} -func TestOffline(t *testing.T) { - td := t.TempDir() - err := downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td) + // unset the roots that were generated for timestamp signing, they won't work here + err = fulcioroots.ReInit() if err != nil { t.Fatal(err) } - regName, stop := reg(t) + repo, stop := reg(t) defer stop() - img1 := path.Join(regName, "cosign-e2e") + imgName := path.Join(repo, "cosign-e2e") - imgRef, _, cleanup := mkimage(t, img1) + imgRef, _, cleanup := mkimage(t, imgName) defer cleanup() - _, privKeyPath, pubKeyPath := keypair(t, td) + identityToken, err := getOIDCToken() + if err != nil { + t.Fatal(err) + } - ctx := context.Background() + // Verify should fail at first + issuer := os.Getenv("ISSUER_URL") + verifyCmd := cliverify.VerifyCommand{ + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: issuer, + CertIdentity: certID, + }, + RekorURL: rekorURL, + CheckClaims: true, + } + mustErr(verifyCmd.Exec(t.Context(), []string{imgName}), t) - // Sign image1 and store the entry in rekor - ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc, RekorURL: rekorURL} - so := options.SignOptions{ - Upload: true, - TlogUpload: true, + // Now sign the image with multiple container identities + ko := options.KeyOpts{ + FulcioURL: fulcioURL, + RekorURL: rekorURL, + IDToken: identityToken, 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, + so := options.SignOptions{ + Upload: true, + TlogUpload: true, + SignContainerIdentities: []string{"registry/cosign-e2e:tag1", "registry/cosign-e2e:tag2"}, } - must(verifyCmd.Exec(ctx, []string{img1}), t) + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) - // Get signatures + // Now verify should work + must(verifyCmd.Exec(t.Context(), []string{imgName}), t) + + // Fetch signatures and check if certificates are identical si, err := ociremote.SignedEntity(imgRef) must(err, t) sigs, err := si.Signatures() @@ -2975,216 +4952,290 @@ func TestOffline(t *testing.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) + assert.Len(t, gottenSigs, 2, "expected 2 signatures") - sigsTag, err := ociremote.SignatureTag(imgRef) + cert1, err := gottenSigs[0].Cert() + must(err, t) + cert2, err := gottenSigs[1].Cert() must(err, t) - if err := remote.Delete(sigsTag); err != nil { - t.Fatal(err) - } + // Compare raw bytes of certificates to ensure they are the same + assert.Equal(t, cert1.Raw, cert2.Raw, "expected certificates to be identical") +} - si, err = ociremote.SignedEntity(imgRef) - must(err, t) - newImage, err := mutate.AttachSignatureToEntity(si, newSig) - must(err, t) +func TestTree(t *testing.T) { + repo, stop := reg(t) + defer stop() - mustErr(verify(pubKeyPath, img1, true, nil, "", false), t) - if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil { - t.Fatal(err) + imgName := path.Join(repo, "tree") + _, _, cleanup := mkimage(t, imgName) + defer cleanup() + + // Test out tree command before + ctx := context.Background() + regOpts := options.RegistryOptions{} + regExpOpts := options.RegistryExperimentalOptions{} + out := bytes.Buffer{} + + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.False(t, strings.Contains(out.String(), "https://sigstore.dev/cosign/sign/v1")) + + // Sign the image + td := t.TempDir() + _, privKeyPath, _ := keypair(t, td) + ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} + so := options.SignOptions{ + NewBundleFormat: true, + Upload: true, } - // Confirm offline verification fails - mustErr(verifyCmd.Exec(ctx, []string{img1}), t) + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) + + // Test out tree command after sign + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.True(t, strings.Contains(out.String(), "https://sigstore.dev/cosign/sign/v1")) } -func TestDockerfileVerify(t *testing.T) { +func TestSignVerifyUploadFalse(t *testing.T) { td := t.TempDir() + ctx := context.Background() - // set up SIGSTORE_ variables to point to keys for the local instances - err := setLocalEnv(t, td) - if err != nil { - t.Fatal(err) - } + repo, stop := reg(t) + defer stop() - // unset the roots that were generated for timestamp signing, they won't work here - err = fulcioroots.ReInit() - if err != nil { - t.Fatal(err) - } + imgName := path.Join(repo, "cosign-e2e-no-upload") + name, desc, cleanup := mkimage(t, imgName) + defer cleanup() - identityToken, err := getOIDCToken() - if err != nil { - t.Fatal(err) + _, privKeyPath, _ := keypair(t, td) + + regOpts := options.RegistryOptions{} + regExpOpts := options.RegistryExperimentalOptions{} + out := bytes.Buffer{} + + // There should be no signatures yet + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image") + + // Now sign the image with Upload: false + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + SkipConfirmation: true, + } + so := options.SignOptions{ + Upload: false, } + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) + + // There should still be no signatures + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image") + + // Now with Upload: true + so.Upload = true + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) + + // Now there should be signatures + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), fmt.Sprintf("Signatures for an image tag: %s:%s-%s.sig", name, desc.Digest.Algorithm, desc.Digest.Hex)) + + // Try on a new image with new bundle format + imgName = path.Join(repo, "cosign-e2e-no-upload-bundle") + name2, _, cleanup2 := mkimage(t, imgName) + defer cleanup2() + + // There should be no signatures yet + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image") + + // Now sign the image with Upload: false + so.Upload = false + so.NewBundleFormat = true + so.BundlePath = path.Join(td, "output.bundle") + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) + assert.FileExists(t, so.BundlePath) + + // There should still be no signatures + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image") + + // Now with Upload: true + so.Upload = true + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) + + // Now there should be signatures + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Regexp(t, regexp.MustCompile(fmt.Sprintf("https://sigstore.dev/cosign/sign/v1 artifacts via OCI referrer: %s@sha256:[a-z0-9]*\n", name2)), out.String()) + assert.FileExists(t, so.BundlePath) + f, err := os.Open(so.BundlePath) + must(err, t) + defer f.Close() + h := sha256.New() + _, err = io.Copy(h, f) + must(err, t) + assert.Contains(t, out.String(), fmt.Sprintf("sha256:%s", hex.EncodeToString(h.Sum(nil)))) +} + +func TestAttestVerifyUploadFalse(t *testing.T) { + td := t.TempDir() + ctx := context.Background() - // 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 + imgName := path.Join(repo, "cosign-e2e-no-upload") + name, desc, cleanup := mkimage(t, imgName) + defer cleanup() + + _, privKeyPath, _ := keypair(t, td) + + regOpts := options.RegistryOptions{} + regExpOpts := options.RegistryExperimentalOptions{} + out := bytes.Buffer{} + + // There should be no attestations yet + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image") + + // Now attest the image with NoUpload: true ko := options.KeyOpts{ - FulcioURL: fulcioURL, - RekorURL: rekorURL, - IDToken: identityToken, + KeyRef: privKeyPath, + PassFunc: passFunc, SkipConfirmation: true, } - so := options.SignOptions{ - Upload: true, - TlogUpload: true, - SkipConfirmation: true, + predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` + predicatePath := filepath.Join(t.TempDir(), "predicate.json") + if err := os.WriteFile(predicatePath, []byte(predicate), 0o644); err != nil { + t.Fatal(err) } - 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) + attestCmd := attest.AttestCommand{ + KeyOpts: ko, + PredicatePath: predicatePath, + PredicateType: "slsaprovenance", + RekorEntryType: "dsse", + NoUpload: true, + } + must(attestCmd.Exec(ctx, imgName), t) - unsignedBuildStageDockerfileContents := fmt.Sprintf(` -FROM %s + // There should still be no attestations + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image") -FROM %s + // Now with NoUpload: false + attestCmd.NoUpload = false + must(attestCmd.Exec(ctx, imgName), t) -FROM %s -`, signedImg1, unsignedImg, signedImg2) - unsignedBuildStageDockerfile := mkfile(unsignedBuildStageDockerfileContents, td, t) + // Now there should be attestations + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), fmt.Sprintf("Attestations for an image tag: %s:%s-%s.att", name, desc.Digest.Algorithm, desc.Digest.Hex)) - fromAsDockerfileContents := fmt.Sprintf(` -FROM --platform=linux/amd64 %s AS base -`, signedImg1) - fromAsDockerfile := mkfile(fromAsDockerfileContents, td, t) + // Try on a new image with new bundle format + imgName = path.Join(repo, "cosign-e2e-no-upload-bundle") + name2, _, cleanup2 := mkimage(t, imgName) + defer cleanup2() - withArgDockerfileContents := ` -ARG test_image + // There should be no attestations yet + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image") -FROM ${test_image} -` - withArgDockerfile := mkfile(withArgDockerfileContents, td, t) + // Now attest the image with NoUpload: true + attestCmd.NoUpload = true + attestCmd.NewBundleFormat = true + attestCmd.BundlePath = path.Join(td, "output.bundle") + must(attestCmd.Exec(ctx, imgName), t) + assert.FileExists(t, attestCmd.BundlePath) - withLowercaseDockerfileContents := fmt.Sprintf(` -from %s -`, signedImg1) - withLowercaseDockerfile := mkfile(withLowercaseDockerfileContents, td, t) + // There should still be no attestations + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Contains(t, out.String(), "No Supply Chain Security Related Artifacts found for image") - issuer := os.Getenv("OIDC_URL") + // Now with NoUpload: true + attestCmd.NoUpload = false + must(attestCmd.Exec(ctx, imgName), t) - 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) - } - }) - } + // Now there should be attestations + out.Reset() + must(cli.TreeCmd(ctx, regOpts, regExpOpts, true, imgName, &out), t) + assert.Regexp(t, regexp.MustCompile(fmt.Sprintf("https://slsa.dev/provenance/v0.2 artifacts via OCI referrer: %s@sha256:[a-z0-9]*\n", name2)), out.String()) + assert.FileExists(t, attestCmd.BundlePath) + f, err := os.Open(attestCmd.BundlePath) + must(err, t) + defer f.Close() + h := sha256.New() + _, err = io.Copy(h, f) + must(err, t) + assert.Contains(t, out.String(), fmt.Sprintf("sha256:%s", hex.EncodeToString(h.Sum(nil)))) } -func TestManifestVerify(t *testing.T) { - td := t.TempDir() - - // set up SIGSTORE_ variables to point to keys for the local instances - err := setLocalEnv(t, td) +func selfSignedCertificate() (*x509.Certificate, *ecdsa.PrivateKey, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - t.Fatal(err) + return nil, nil, err } - - // unset the roots that were generated for timestamp signing, they won't work here - err = fulcioroots.ReInit() + ct := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "self.signed.cert", + Organization: []string{"dev"}, + }, + EmailAddresses: []string{"foo@bar.com"}, + NotBefore: time.Now().Add(-1 * time.Minute), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + } + certBytes, err := x509.CreateCertificate(rand.Reader, ct, ct, &priv.PublicKey, priv) if err != nil { - t.Fatal(err) + return nil, nil, err } - - identityToken, err := getOIDCToken() + cert, err := x509.ParseCertificate(certBytes) if err != nil { - t.Fatal(err) + return nil, nil, err } + return cert, priv, nil +} + +func TestSignVerifyDetachedKeyless(t *testing.T) { + td := t.TempDir() + err := setLocalEnv(t, td) + must(err, t) + must(fulcioroots.ReInit(), t) - // create some images repo, stop := reg(t) defer stop() - signedImg := path.Join(repo, "cosign-e2e-manifest-signed") - _, _, cleanup := mkimage(t, signedImg) + imgName := path.Join(repo, "cosign-e2e-detached-keyless") + + _, _, cleanup := mkimage(t, imgName) defer cleanup() - unsignedImg := path.Join(repo, "cosign-e2e-manifest-unsigned") - _, _, cleanupUnsigned := mkimage(t, unsignedImg) - defer cleanupUnsigned() - // sign the images using --identity-token + identityToken, err := getOIDCToken() + must(err, t) + + ctx := context.Background() + sigFile := filepath.Join(td, "sig.out") + certFile := filepath.Join(td, "cert.out") + + // Verify should fail before signing + failCmd1 := cliverify.VerifyCommand{ + RekorURL: rekorURL, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentity: certID, + }, + } + mustErr(failCmd1.Exec(ctx, []string{imgName}), t) + ko := options.KeyOpts{ FulcioURL: fulcioURL, RekorURL: rekorURL, @@ -3192,78 +5243,73 @@ func TestManifestVerify(t *testing.T) { SkipConfirmation: true, } so := options.SignOptions{ - Upload: true, - TlogUpload: true, - SkipConfirmation: true, + Upload: true, + TlogUpload: true, + OutputSignature: sigFile, + OutputCertificate: certFile, + UseSigningConfig: false, + } + must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) + + // Verify should fail with a detached signature but no certificate + failCmd2 := cliverify.VerifyCommand{ + RekorURL: rekorURL, + SignatureRef: sigFile, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentity: certID, + }, } - ctx := context.Background() - must(sign.SignCmd(ro, ko, so, []string{signedImg}), t) + mustErr(failCmd2.Exec(ctx, []string{imgName}), 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) + // Now verify should work using the certificate + cmd := cliverify.VerifyCommand{ + RekorURL: rekorURL, + SignatureRef: sigFile, + CertRef: certFile, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentity: certID, + }, + } + must(cmd.Exec(ctx, []string{imgName}), t) - issuer := os.Getenv("OIDC_URL") + // Save the original root file path and ensure it's restored for subsequent tests + origRootFile := os.Getenv("SIGSTORE_ROOT_FILE") + defer func() { + t.Setenv("SIGSTORE_ROOT_FILE", origRootFile) + _ = fulcioroots.ReInit() + }() - tests := []struct { - name string - manifest string - wantErr bool - }{ - { - name: "signed manifest", - manifest: signedManifest, - }, - { - name: "unsigned manifest", - manifest: unsignedManifest, - wantErr: true, + // Invalidate the default root cert env var and re-initialize to simulate a missing/unconfigured default trust root + t.Setenv("SIGSTORE_ROOT_FILE", "/nonexistent/path") + _ = fulcioroots.ReInit() + + // Verify should now fail without explicitly passing the certificate chain + mustErr(cmd.Exec(ctx, []string{imgName}), t) + + // Now verify should work when explicitly providing the certificate chain + cmdWithChain := cliverify.VerifyCommand{ + RekorURL: rekorURL, + SignatureRef: sigFile, + CertRef: certFile, + CertChain: origRootFile, + CertVerifyOptions: options.CertVerifyOptions{ + CertOidcIssuer: os.Getenv("ISSUER_URL"), + CertIdentity: certID, }, } - 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) - } - }) - } + must(cmdWithChain.Exec(ctx, []string{imgName}), 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) +func getTimestampedSignature(sigBytes []byte, tsaClient client.TimestampAuthorityClient) ([]byte, error) { + requestBytes, err := timestamp.CreateRequest(bytes.NewReader(sigBytes), ×tamp.RequestOptions{ + Hash: crypto.SHA256, + Certificates: true, + }) if err != nil { - return "", err + return nil, fmt.Errorf("error creating timestamp request: %w", err) } - return string(body), nil + + return tsaClient.GetTimestampResponse(requestBytes) } diff --git a/test/e2e_test.ps1 b/test/e2e_test.ps1 index 66e4c30edd7..4228d769a29 100644 --- a/test/e2e_test.ps1 +++ b/test/e2e_test.ps1 @@ -34,9 +34,9 @@ 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 +Write-Output "hello world" | Out-File -FilePath "hello_world.txt" +Write-Output $pass | .\cosign.exe sign-blob --key $signing_key --bundle test.sigstore.json --use-signing-config=false --tlog-upload=false hello_world.txt +.\cosign.exe verify-blob --key $verification_key --bundle test.sigstore.json --insecure-ignore-tlog=true hello_world.txt Pop-Location diff --git a/test/e2e_test.sh b/test/e2e_test.sh index 32c45566456..99560c1a1d9 100755 --- a/test/e2e_test.sh +++ b/test/e2e_test.sh @@ -16,110 +16,40 @@ 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 +echo "downloading sigstore/scaffolding repository" +SCAFFOLDING_DIR=$(mktemp -d) +git clone https://github.com/sigstore/scaffolding.git "$SCAFFOLDING_DIR" +SCAFFOLDING_SETUP_DIR="$SCAFFOLDING_DIR/actions/setup-sigstore-env" + +echo "setting up sigstore test environment" +pushd "$SCAFFOLDING_SETUP_DIR" +source ./run-containers.sh 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" - if [ "$repo" == "fulcio" ]; then - healthytotal=3 - elif [ "$repo" == "rekor" ]; then - healthytotal=5 - else - # handle no match in case another service is added - healthytotal=0 - fi - count=0 - until [ $(${docker_compose} ps | grep -c "(healthy)") == $healthytotal ]; - do - if [ $count -eq 18 ]; then - echo "! timeout reached" - exit 1 - else - echo -n "." - sleep 10 - let 'count+=1' - fi - done +cleanup() { + echo "cleaning up sigstore test environment" + pushd "$SCAFFOLDING_SETUP_DIR" + stop_services popd -done -cleanup_services() { - echo "cleaning up" - cleanup_oidc - for repo in rekor fulcio; do - pushd $HOME/$repo - ${docker_compose} down - popd - done + docker rm -f registry registry-2 || true } -trap cleanup_services EXIT +trap cleanup 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 +# Test with signature in separate registry +docker run -d -p 5001:5000 --restart always -e REGISTRY_STORAGE_DELETE_ENABLED=true --name registry-2 registry:latest +export COSIGN_REPOSITORY=localhost:5001/hello +go test -tags=e2e -v ./test/... -run TestSignVerifyWithRepoOverride + # Run the built container to make sure it doesn't crash make ko-local img="ko.local/cosign:$(git rev-parse HEAD)" diff --git a/test/e2e_test_pkcs11.sh b/test/e2e_test_pkcs11.sh index 703ce7c7beb..03f980f9327 100755 --- a/test/e2e_test_pkcs11.sh +++ b/test/e2e_test_pkcs11.sh @@ -36,7 +36,7 @@ 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 +go test -v -cover -coverprofile=./cover.out -tags=softhsm,pkcs11key -coverpkg github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key test/pkcs11_test.go EOF diff --git a/test/e2e_tsa_test.go b/test/e2e_tsa_test.go index acd873a601c..b415ce8c008 100644 --- a/test/e2e_tsa_test.go +++ b/test/e2e_tsa_test.go @@ -21,19 +21,24 @@ import ( "crypto/x509" "encoding/pem" "net/http/httptest" + "os" "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/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" + cliverify "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" + cert_test "github.com/sigstore/cosign/v3/internal/test" + "github.com/sigstore/cosign/v3/pkg/cosign" + tsaclient "github.com/sigstore/timestamp-authority/v2/pkg/client" + tsaserver "github.com/sigstore/timestamp-authority/v2/pkg/server" "github.com/spf13/viper" + + prototrustroot "github.com/sigstore/protobuf-specs/gen/pb-go/trustroot/v1" + "github.com/sigstore/sigstore-go/pkg/root" ) func TestTSAMTLS(t *testing.T) { @@ -68,7 +73,7 @@ func TestTSAMTLS(t *testing.T) { Cert: pemLeafRef, CertChain: pemRootRef, } - must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) verifyCmd := cliverify.VerifyCommand{ IgnoreTlog: true, @@ -109,7 +114,7 @@ func TestSignBlobTSAMTLS(t *testing.T) { RFC3161TimestampPath: timestampPath, BundlePath: bundlePath, } - sig, err := sign.SignBlobCmd(ro, signingKO, blobPath, true, "", "", false) + sig, err := sign.SignBlobCmd(t.Context(), ro, signingKO, blobPath, "", "", true, "", "", false) must(err, t) verifyKO := options.KeyOpts{ @@ -120,23 +125,241 @@ func TestSignBlobTSAMTLS(t *testing.T) { } verifyCmd := cliverify.VerifyBlobCmd{ - KeyOpts: verifyKO, - SigRef: string(sig), + KeyOpts: verifyKO, + SigRef: string(sig), + IgnoreTlog: true, + } + must(verifyCmd.Exec(context.Background(), blobPath), t) +} + +func TestSignBlobTSAMTLSWithSigningConfig(t *testing.T) { + td := t.TempDir() + blob := time.Now().Format("Mon Jan 2 15:04:05 MST 2006") + blobPath := mkfile(blob, td, t) + 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) + + // Create SigningConfig + tsaService := root.Service{ + URL: timestampServerURL, + MajorAPIVersion: 1, + Operator: "test-operator", + ValidityPeriodStart: time.Now().Add(-1 * time.Hour), + } + + signingConfig, err := root.NewSigningConfig( + root.SigningConfigMediaType02, + nil, nil, nil, root.ServiceConfiguration{}, + []root.Service{tsaService}, + root.ServiceConfiguration{Selector: prototrustroot.ServiceSelector_ANY}, + ) + must(err, t) + + // Create TrustedRoot with TSA CA + chainBytes, err := os.ReadFile(timestampChainFile) + must(err, t) + + var certs []*x509.Certificate + for block, rest := pem.Decode(chainBytes); block != nil; block, rest = pem.Decode(rest) { + cert, err := x509.ParseCertificate(block.Bytes) + must(err, t) + certs = append(certs, cert) + } + + if len(certs) == 0 { + t.Fatal("no certs found in timestamp chain file") + } + + var intermediates []*x509.Certificate + if len(certs) > 1 { + intermediates = certs[1 : len(certs)-1] + } + + tsaAuth := &root.SigstoreTimestampingAuthority{ + Root: certs[len(certs)-1], + Leaf: certs[0], + Intermediates: intermediates, + ValidityPeriodStart: certs[0].NotBefore, + URI: timestampServerURL, + } + + trustedRoot, err := root.NewTrustedRoot( + root.TrustedRootMediaType01, + nil, + nil, + []root.TimestampingAuthority{tsaAuth}, + nil, + ) + must(err, t) + + signingKO := options.KeyOpts{ + KeyRef: privKey, + PassFunc: passFunc, + TSAClientCACert: timestampCACert, + TSAClientCert: timestampClientCert, + TSAClientKey: timestampClientKey, + TSAServerName: "server.example.com", + BundlePath: bundlePath, + SigningConfig: signingConfig, + TrustedMaterial: trustedRoot, + NewBundleFormat: true, + } + _, err = sign.SignBlobCmd(t.Context(), ro, signingKO, blobPath, "", "", true, "", "", false) + must(err, t) + + verifyKO := options.KeyOpts{ + KeyRef: pubKey, + BundlePath: bundlePath, + TrustedMaterial: trustedRoot, + NewBundleFormat: true, + } + + verifyCmd := cliverify.VerifyBlobCmd{ + KeyOpts: verifyKO, + IgnoreTlog: true, + } + must(verifyCmd.Exec(context.Background(), blobPath), t) +} + +func TestTSAMTLSWithSigningConfig(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) + + // Create SigningConfig + tsaService := root.Service{ + URL: timestampServerURL, + MajorAPIVersion: 1, + Operator: "test-operator", + ValidityPeriodStart: time.Now().Add(-1 * time.Hour), + } + + signingConfig, err := root.NewSigningConfig( + root.SigningConfigMediaType02, + nil, nil, nil, root.ServiceConfiguration{}, + []root.Service{tsaService}, + root.ServiceConfiguration{Selector: prototrustroot.ServiceSelector_ANY}, + ) + must(err, t) + + // Create TrustedRoot with TSA CA + chainBytes, err := os.ReadFile(timestampChainFile) + must(err, t) + + var certs []*x509.Certificate + for block, rest := pem.Decode(chainBytes); block != nil; block, rest = pem.Decode(rest) { + cert, err := x509.ParseCertificate(block.Bytes) + must(err, t) + certs = append(certs, cert) + } + + if len(certs) == 0 { + t.Fatal("no certs found in timestamp chain file") + } + + var intermediates []*x509.Certificate + if len(certs) > 1 { + intermediates = certs[1 : len(certs)-1] + } + + tsaAuth := &root.SigstoreTimestampingAuthority{ + Root: certs[len(certs)-1], + Leaf: certs[0], + Intermediates: intermediates, + ValidityPeriodStart: certs[0].NotBefore, + URI: timestampServerURL, + } + + // Add CA from pemRootRef + caBytes, err := os.ReadFile(pemRootRef) + must(err, t) + var caCerts []*x509.Certificate + for block, rest := pem.Decode(caBytes); block != nil; block, rest = pem.Decode(rest) { + cert, err := x509.ParseCertificate(block.Bytes) + must(err, t) + caCerts = append(caCerts, cert) + } + if len(caCerts) == 0 { + t.Fatal("no certs in pemRootRef") + } + caAuth := &root.FulcioCertificateAuthority{ + Root: caCerts[0], + ValidityPeriodStart: caCerts[0].NotBefore, + } + + trustedRoot, err := root.NewTrustedRoot( + root.TrustedRootMediaType01, + []root.CertificateAuthority{caAuth}, + nil, + []root.TimestampingAuthority{tsaAuth}, + nil, + ) + must(err, t) + + ko := options.KeyOpts{ + KeyRef: pemKeyRef, + PassFunc: passFunc, + TSAClientCACert: timestampCACert, + TSAClientCert: timestampClientCert, + TSAClientKey: timestampClientKey, + TSAServerName: "server.example.com", + SigningConfig: signingConfig, + TrustedMaterial: trustedRoot, + NewBundleFormat: true, + } + so := options.SignOptions{ + Upload: true, + TlogUpload: false, + Cert: pemLeafRef, + CertChain: pemRootRef, + NewBundleFormat: true, + } + must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) + + trBytes, err := trustedRoot.MarshalJSON() + must(err, t) + trustedRootFile := mkfile(string(trBytes), td, t) + + verifyCmd := cliverify.VerifyCommand{ + IgnoreTlog: true, + IgnoreSCT: true, + CheckClaims: true, + NewBundleFormat: true, + CommonVerifyOptions: options.CommonVerifyOptions{ + TrustedRootPath: trustedRootFile, + }, CertVerifyOptions: options.CertVerifyOptions{ CertIdentityRegexp: ".*", CertOidcIssuerRegexp: ".*", }, - IgnoreTlog: true, } - must(verifyCmd.Exec(context.Background(), blobPath), t) + must(verifyCmd.Exec(context.Background(), []string{imgName}), t) } func generateSigningKeys(t *testing.T, td string) (string, string, string) { - rootCert, rootKey, _ := GenerateRootCa() + rootCert, rootKey, _ := cert_test.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) + leafCert, privKey, _ := cert_test.GenerateLeafCert("xyz@nosuchprovider.com", "oidc-issuer", rootCert, rootKey) pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw}) pemLeafRef := mkfile(string(pemLeaf), td, t) @@ -144,33 +367,36 @@ func generateSigningKeys(t *testing.T, td string) (string, string, string) { encBytes, _ := encrypted.Encrypt(x509Encoded, keyPass) keyPem := pem.EncodeToMemory(&pem.Block{ Type: cosign.CosignPrivateKeyPemType, - Bytes: encBytes}) + 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() + rootCert, rootKey, _ := cert_test.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) + serverLeafCert, serverPrivKey, _ := cert_test.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}) + Bytes: serverX509Encoded, + }) serverPemKeyRef := mkfile(string(serverKeyPem), td, t) - clientLeafCert, clientPrivKey, _ := GenerateLeafCert("tsa-mtls-client", "oidc-issuer", rootCert, rootKey) + clientLeafCert, clientPrivKey, _ := cert_test.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}) + Bytes: clientX509Encoded, + }) clientPemKeyRef := mkfile(string(clientKeyPem), td, t) return pemRootRef, serverPemLeafRef, serverPemKeyRef, clientPemLeafRef, clientPemKeyRef } @@ -179,6 +405,7 @@ func setUpTSAServerWithTLS(t *testing.T, td, timestampCACert, timestampServerKey viper.Set("timestamp-signer", "memory") viper.Set("timestamp-signer-hash", "sha256") viper.Set("disable-ntp-monitoring", true) + viper.Set("default-policy-oid", "1.3.6.1.4.1.57264.2") viper.Set("tls-host", "0.0.0.0") viper.Set("tls-port", 3000) viper.Set("tls-ca", timestampCACert) diff --git a/test/fakeoidc/go.mod b/test/fakeoidc/go.mod deleted file mode 100644 index 0b7bc6d3239..00000000000 --- a/test/fakeoidc/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module 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.35.0 // indirect diff --git a/test/fakeoidc/go.sum b/test/fakeoidc/go.sum deleted file mode 100644 index 3bb8cc15339..00000000000 --- a/test/fakeoidc/go.sum +++ /dev/null @@ -1,14 +0,0 @@ -github.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.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/fakeoidc/main.go b/test/fakeoidc/main.go deleted file mode 100644 index 95dc357e7f8..00000000000 --- a/test/fakeoidc/main.go +++ /dev/null @@ -1,118 +0,0 @@ -// -// 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) - } -} diff --git a/test/fuzz/oss_fuzz_build.sh b/test/fuzz/oss_fuzz_build.sh index 0c0c949fd5c..d74b4aafc96 100755 --- a/test/fuzz/oss_fuzz_build.sh +++ b/test/fuzz/oss_fuzz_build.sh @@ -14,15 +14,14 @@ # 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 +rm ./pkg/cosign/verify_bundle_test.go +compile_native_go_fuzzer_v2 github.com/sigstore/cosign/v3/pkg/cosign/attestation FuzzGenerateStatement FuzzGenerateStatement +compile_native_go_fuzzer_v2 github.com/sigstore/cosign/v3/pkg/cosign/cue FuzzValidateJSON FuzzValidateJSON_cue +compile_native_go_fuzzer_v2 github.com/sigstore/cosign/v3/pkg/cosign/rego FuzzValidateJSON FuzzValidateJSON_rego +compile_native_go_fuzzer_v2 github.com/sigstore/cosign/v3/pkg/cosign FuzzImportKeyPairLoadPrivateKey FuzzImportKeyPairLoadPrivateKey +compile_native_go_fuzzer_v2 github.com/sigstore/cosign/v3/pkg/cosign FuzzSigVerify FuzzSigVerify +compile_native_go_fuzzer_v2 github.com/sigstore/cosign/v3/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/* diff --git a/test/helpers.go b/test/helpers.go index 393366eedc2..112e3569bd8 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -36,7 +36,6 @@ import ( "net/http/httptest" "net/url" "os" - "path" "path/filepath" "testing" "time" @@ -52,18 +51,22 @@ import ( // 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" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + cliverify "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" + "github.com/sigstore/cosign/v3/pkg/cosign" + "github.com/sigstore/cosign/v3/pkg/cosign/env" + ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" + sigs "github.com/sigstore/cosign/v3/pkg/signature" + v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" ) const ( - rekorURL = "http://127.0.0.1:3000" - fulcioURL = "http://127.0.0.1:5555" - certID = "foo@bar.com" + rekorURL = "http://127.0.0.1:3000" + rekorV2URL = "http://127.0.0.1:3003" + fulcioURL = "http://127.0.0.1:5555" + tsaURL = "http://127.0.0.1:3004" + certID = "foo@bar.com" ) var keyPass = []byte("hello") @@ -121,10 +124,8 @@ var verifyCertBundle = func(keyRef, caCertFile, caIntermediateCertFile, imageRef MaxWorkers: 10, IgnoreTlog: skipTlogVerify, CertVerifyOptions: options.CertVerifyOptions{ - CAIntermediates: caIntermediateCertFile, - CARoots: caCertFile, - CertOidcIssuerRegexp: ".*", - CertIdentityRegexp: ".*", + CAIntermediates: caIntermediateCertFile, + CARoots: caCertFile, }, } @@ -151,12 +152,13 @@ var verifyTSA = func(keyRef, imageRef string, checkClaims bool, annotations map[ return cmd.Exec(context.Background(), args) } -var verifyKeylessTSA = func(imageRef string, tsaCertChain string, skipSCT bool, skipTlogVerify bool) error { //nolint: unused +var verifyKeylessTSA = func(imageRef, tsaCertChain, certChain string, skipSCT, skipTlogVerify bool) error { //nolint: unused cmd := cliverify.VerifyCommand{ CertVerifyOptions: options.CertVerifyOptions{ CertOidcIssuerRegexp: ".*", CertIdentityRegexp: ".*", }, + CertChain: certChain, RekorURL: rekorURL, HashAlgorithm: crypto.SHA256, TSACertChainPath: tsaCertChain, @@ -256,7 +258,11 @@ var verifyOffline = func(keyRef, imageRef string, checkClaims bool, annotations var ro = &options.RootOptions{Timeout: options.DefaultTimeout} -func keypair(t *testing.T, td string) (*cosign.KeysBytes, string, string) { +func keypairWithAlgorithm(t *testing.T, td string, publicKeyDetails v1.PublicKeyDetails) (*cosign.KeysBytes, string, string) { + algo, err := signature.GetAlgorithmDetails(publicKeyDetails) + if err != nil { + t.Fatal(err) + } wd, err := os.Getwd() if err != nil { t.Fatal(err) @@ -267,7 +273,7 @@ func keypair(t *testing.T, td string) (*cosign.KeysBytes, string, string) { defer func() { _ = os.Chdir(wd) }() - keys, err := cosign.GenerateKeyPair(passFunc) + keys, err := cosign.GenerateKeyPairWithAlgorithm(&algo, passFunc) if err != nil { t.Fatal(err) } @@ -284,6 +290,10 @@ func keypair(t *testing.T, td string) (*cosign.KeysBytes, string, string) { return keys, privKeyPath, pubKeyPath } +func keypair(t *testing.T, td string) (*cosign.KeysBytes, string, string) { + return keypairWithAlgorithm(t, td, v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256) +} + // 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 { @@ -502,13 +512,9 @@ func registryClientOpts(ctx context.Context) []remote.Option { // 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) + ctLogKey := os.Getenv("CT_LOG_KEY") //nolint: forbidigo + t.Setenv(env.VariableSigstoreCTLogPublicKeyFile.String(), ctLogKey) + 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) } diff --git a/test/piv_test.go b/test/piv_test.go index eb482e4318c..80d25b37dec 100644 --- a/test/piv_test.go +++ b/test/piv_test.go @@ -29,7 +29,7 @@ import ( "testing" // Import the functions directly for testing. - . "github.com/sigstore/cosign/v2/cmd/cosign/cli/pivcli" + . "github.com/sigstore/cosign/v3/cmd/cosign/cli/pivcli" ) func TestSetManagementKeyCmd(t *testing.T) { diff --git a/test/pkcs11_test.go b/test/pkcs11_test.go index bfe031b9d1c..4b232b6a99b 100644 --- a/test/pkcs11_test.go +++ b/test/pkcs11_test.go @@ -54,8 +54,8 @@ import ( // 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/sigstore/cosign/v3/cmd/cosign/cli/pkcs11cli" + "github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key" "github.com/stretchr/testify/require" )