pax_global_header00006660000000000000000000000064147725355230014527gustar00rootroot0000000000000052 comment=b17948db1402b33caa005637f40e8df9eab56daf gitsign-0.13.0/000077500000000000000000000000001477253552300132545ustar00rootroot00000000000000gitsign-0.13.0/.github/000077500000000000000000000000001477253552300146145ustar00rootroot00000000000000gitsign-0.13.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001477253552300167775ustar00rootroot00000000000000gitsign-0.13.0/.github/ISSUE_TEMPLATE/bug.md000066400000000000000000000004671477253552300201050ustar00rootroot00000000000000--- name: Bug about: Something wrong with the project? Report it here! labels: 'bug' assignees: '' --- **Description** **Version** gitsign-0.13.0/.github/dependabot.yml000066400000000000000000000006551477253552300174520ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: weekly open-pull-requests-limit: 10 groups: gomod: update-types: - "patch" - package-ecosystem: "github-actions" directory: "/" schedule: interval: weekly open-pull-requests-limit: 10 groups: actions: update-types: - "minor" - "patch" gitsign-0.13.0/.github/workflows/000077500000000000000000000000001477253552300166515ustar00rootroot00000000000000gitsign-0.13.0/.github/workflows/ci.yaml000066400000000000000000000011011477253552300201210ustar00rootroot00000000000000name: CI on: push: branches: ["main"] pull_request: branches: ["main"] workflow_dispatch: jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Set up Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Build run: make build-all - name: Unit Tests run: make unit-test gitsign-0.13.0/.github/workflows/e2e.yaml000066400000000000000000000077321477253552300202210ustar00rootroot00000000000000name: E2E on: push: branches: ["main"] pull_request: branches: ["main"] workflow_dispatch: jobs: e2e: runs-on: ubuntu-latest permissions: # The rest of these are sanity-check settings, since I'm not sure if the # org default is permissive or restricted. # See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token # for more details. actions: none checks: none contents: read deployments: none id-token: none issues: none packages: none pages: none pull-requests: none repository-projects: none security-events: none statuses: none steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Set up Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Get test OIDC token uses: sigstore-conformance/extremely-dangerous-public-oidc-beacon@main - name: export OIDC token run: | echo "SIGSTORE_ID_TOKEN=$(cat ./oidc-token.txt)" >> $GITHUB_ENV - name: e2e unit tests run: | set -e make e2e-test - name: Install Gitsign run: | set -e # Setup repo + tool make install-gitsign export PATH="$PATH:$GOPATH/bin" echo "PATH=${PATH}" whereis gitsign mkdir /tmp/git cd /tmp/git git init -b main . git config --global user.email "test@example.com" git config --global user.name "gitsign" git config --global gpg.x509.program gitsign git config --global gpg.format x509 git config --global commit.gpgsign true # Verify tool is on our path gitsign -h - name: Test Sign and Verify commit run: | set -e # Sign commit git commit --allow-empty -S --message="Signed commit" # Verify commit echo "========== git verify-commit ==========" git verify-commit HEAD echo "========== gitsign verify ==========" gitsign verify \ --certificate-github-workflow-repository="sigstore-conformance/extremely-dangerous-public-oidc-beacon" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ --certificate-identity="https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" # Extra debug info git cat-file commit HEAD | sed -n '/-BEGIN/, /-END/p' | sed 's/^ //g' | sed 's/gpgsig //g' | sed 's/SIGNED MESSAGE/PKCS7/g' | openssl pkcs7 -print -print_certs -text - name: Test Sign and Verify commit - offline verification env: GITSIGN_REKOR_MODE: "offline" run: | set -e # Sign commit git commit --allow-empty -S --message="Signed commit" # Verify commit echo "========== git verify-commit ==========" git verify-commit HEAD echo "========== gitsign verify ==========" gitsign verify \ --certificate-github-workflow-repository="sigstore-conformance/extremely-dangerous-public-oidc-beacon" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ --certificate-identity="https://github.com/sigstore-conformance/extremely-dangerous-public-oidc-beacon/.github/workflows/extremely-dangerous-oidc-beacon.yml@refs/heads/main" # Extra debug info git cat-file commit HEAD | sed -n '/-BEGIN/, /-END/p' | sed 's/^ //g' | sed 's/gpgsig //g' | sed 's/SIGNED MESSAGE/PKCS7/g' | openssl pkcs7 -print -print_certs -text - name: Debug log if: failure() run: cat "${GITSIGN_LOG}" gitsign-0.13.0/.github/workflows/release.yml000066400000000000000000000044601477253552300210200ustar00rootroot00000000000000name: release # run only on tags on: push: tags: - 'v*' jobs: release: runs-on: ubuntu-latest permissions: contents: write # needed to write releases id-token: write # needed for keyless signing packages: write # needed for push images attestations: write steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # this is important, otherwise it won't checkout the full tree (i.e. no previous tags) persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - uses: imjasonh/setup-crane@31b88efe9de28ae0ffa220711af4b60be9435f6e # v0.4 - uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1 - uses: anchore/sbom-action/download-syft@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0 - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> "$GITHUB_ENV" - name: Login to GitHub Containers uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 with: version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} KO_DOCKER_REPO: ghcr.io/sigstore/gitsign - name: get the digest id: digest run: | digest=$(crane digest ghcr.io/sigstore/gitsign:${RELEASE_VERSION}) echo "digest=${digest}" >> "$GITHUB_OUTPUT" - name: sign image run: | cosign sign "ghcr.io/sigstore/gitsign@${DIGEST_TO_SIGN}" env: DIGEST_TO_SIGN: ${{ steps.digest.outputs.digest }} COSIGN_YES: true - name: Generate build provenance attestation uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 with: subject-name: ghcr.io/sigstore/gitsign subject-digest: ${{ steps.digest.outputs.digest }} push-to-registry: true gitsign-0.13.0/.github/workflows/validate-release.yml000066400000000000000000000022161477253552300226040ustar00rootroot00000000000000name: validate-release on: push: branches: ['main'] pull_request: branches: ['main'] workflow_dispatch: jobs: validate-release: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1 - uses: anchore/sbom-action/download-syft@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0 - uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 with: version: latest args: release --clean --snapshot --skip=sign env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} gitsign-0.13.0/.github/workflows/verify.yml000066400000000000000000000035251477253552300207050ustar00rootroot00000000000000name: Verify on: push: branches: ["main"] pull_request: branches: ["main"] permissions: contents: read jobs: license-check: name: license boilerplate check runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Install addlicense run: go install github.com/google/addlicense@v1.0.0 - name: Check license headers run: | set -e addlicense -l apache -c 'The Sigstore Authors' -v \ -ignore "*.yml" \ -ignore "*.yaml" \ -ignore "internal/fork/**" \ * git diff --exit-code golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: golangci-lint uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 with: version: v1.61 args: --timeout 10m generate-docs: name: generate-docs runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version-file: 'go.mod' check-latest: true - name: Check CLI docs are up to date run: ./hack/presubmit.sh gitsign-0.13.0/.gitignore000066400000000000000000000001611477253552300152420ustar00rootroot00000000000000# Vim swap files *.swp dist/* .vscode/* /gitsign /gitsign-credential-cache !/cmd/gitsign-credential-cache vendor gitsign-0.13.0/.golangci.yml000066400000000000000000000010671477253552300156440ustar00rootroot00000000000000linters: enable: - asciicheck - errcheck - errorlint - gofmt - goimports - gosec - gocritic - importas - prealloc - revive - misspell - stylecheck - tparallel - unconvert - unparam - unused - whitespace output: uniq-by-line: false issues: exclude-rules: - path: _test\.go linters: - errcheck - gosec - text: "SA1019: package golang.org/x/crypto/openpgp" linters: - staticcheck exclude-dirs: - internal/fork max-issues-per-linter: 0 max-same-issues: 0 run: issues-exit-code: 1 timeout: 10m gitsign-0.13.0/.goreleaser.yaml000066400000000000000000000047611477253552300163560ustar00rootroot00000000000000project_name: gitsign version: 2 gomod: proxy: true builds: - id: gitsign mod_timestamp: '{{ .CommitTimestamp }}' env: - CGO_ENABLED=0 flags: - -trimpath goos: - linux - darwin - freebsd - windows goarch: - amd64 - arm64 ldflags: - "-s -w" - "-extldflags=-zrelro" - "-extldflags=-znow" - "-buildid= -X github.com/sigstore/gitsign/pkg/version.gitVersion={{ .Version }}" - id: gitsign-credential-cache mod_timestamp: '{{ .CommitTimestamp }}' main: ./cmd/gitsign-credential-cache binary: gitsign-credential-cache env: - CGO_ENABLED=0 flags: - -trimpath goos: - linux - darwin - freebsd - windows goarch: - amd64 - arm64 ldflags: - "-s -w" - "-extldflags=-zrelro" - "-extldflags=-znow" - "-buildid= -X github.com/sigstore/gitsign/pkg/version.gitVersion={{ .Version }}" nfpms: - id: default package_name: gitsign vendor: Sigstore homepage: https://github.com/sigstore/gitsign maintainer: Billy Lynch description: Keyless git commit signing using OIDC identity builds: - gitsign - gitsign-credential-cache formats: - apk - deb - rpm archives: - id: binary formats: - binary allow_different_binary_count: true kos: - id: gitsign repositories: - github.com/sigstore/gitsign tags: - 'v{{ .Version }}' ldflags: - "-s -w -extldflags=-zrelro -extldflags=-znow -buildid= -X github.com/sigstore/gitsign/pkg/version.gitVersion={{ .Version }}" main: . bare: true preserve_import_paths: false base_import_paths: false sbom: spdx # then it have a shell base_image: cgr.dev/chainguard/git:latest-dev platforms: - linux/amd64 - linux/arm64 - linux/arm checksum: name_template: 'checksums.txt' source: enabled: true sboms: - id: binaries artifacts: binary - id: packages artifacts: package signs: - cmd: cosign env: - COSIGN_YES=true certificate: '${artifact}.pem' signature: '${artifact}.sig' args: - sign-blob - '--output-certificate=${certificate}' - '--output-signature=${signature}' - '${artifact}' artifacts: binary output: true release: prerelease: allow draft: true # allow for manual edits github: owner: sigstore name: gitsign footer: | ### Thanks to all contributors! gitsign-0.13.0/CODEOWNERS000066400000000000000000000001571477253552300146520ustar00rootroot00000000000000# The CODEOWNERS are managed via a GitHub team, but the current list is (in alphabetical order): # # lukehinds gitsign-0.13.0/CODE_OF_CONDUCT.md000066400000000000000000000062161477253552300160600ustar00rootroot00000000000000# Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/gitsign-0.13.0/CONTRIBUTORS.md000066400000000000000000000104651477253552300155410ustar00rootroot00000000000000# Contributing When contributing to this repository, please first discuss the change you wish to make via an [issue](https://github.com/sigstore/{project-name}/issues). ## Pull Request Process 1. Create an [issue](https://github.com/sigstore/{project-name}/issues) outlining the fix or feature. 2. Fork the {project-name} repository to your own github account and clone it locally. 3. Hack on your changes. 4. Update the README.md with details of changes to any interface, this includes new environment variables, exposed ports, useful file locations, CLI parameters and new or changed configuration values. 5. Correctly format your commit message see [Commit Messages](#Commit Message Guidelines) below. 6. Ensure that CI passes, if it fails, fix the failures. 7. Every pull request requires a review from the [core {project-name} team](https://github.com/orgs/github.com/sigstore/teams/{project-name}-codeowners) before merging. 8. If your pull request consists of more than one commit, please squash your commits as described in [Squash Commits](#Squash Commits) ## Commit Message Guidelines We follow the commit formatting recommendations found on [Chris Beams' How to Write a Git Commit Message article]((https://chris.beams.io/posts/git-commit/). Well formed commit messages not only help reviewers understand the nature of the Pull Request, but also assists the release process where commit messages are used to generate release notes. A good example of a commit message would be as follows: ``` Summarize changes in around 50 characters or less More detailed explanatory text, if necessary. Wrap it to about 72 characters or so. In some contexts, the first line is treated as the subject of the commit and the rest of the text as the body. The blank line separating the summary from the body is critical (unless you omit the body entirely); various tools like `log`, `shortlog` and `rebase` can get confused if you run the two together. Explain the problem that this commit is solving. Focus on why you are making this change as opposed to how (the code explains that). Are there side effects or other unintuitive consequences of this change? Here's the place to explain them. Further paragraphs come after blank lines. - Bullet points are okay, too - Typically a hyphen or asterisk is used for the bullet, preceded by a single space, with blank lines in between, but conventions vary here If you use an issue tracker, put references to them at the bottom, like this: Resolves: #123 See also: #456, #789 ``` Note the `Resolves #123` tag, this references the issue raised and allows us to ensure issues are associated and closed when a pull request is merged. Please refer to [the github help page on message types](https://help.github.com/articles/closing-issues-using-keywords/) for a complete list of issue references. ## Squash Commits Should your pull request consist of more than one commit (perhaps due to a change being requested during the review cycle), please perform a git squash once a reviewer has approved your pull request. A squash can be performed as follows. Let's say you have the following commits: initial commit second commit final commit Run the command below with the number set to the total commits you wish to squash (in our case 3 commits): git rebase -i HEAD~3 You default text editor will then open up and you will see the following:: pick eb36612 initial commit pick 9ac8968 second commit pick a760569 final commit # Rebase eb1429f..a760569 onto eb1429f (3 commands) We want to rebase on top of our first commit, so we change the other two commits to `squash`: pick eb36612 initial commit squash 9ac8968 second commit squash a760569 final commit After this, should you wish to update your commit message to better summarise all of your pull request, run: git commit --amend You will then need to force push (assuming your initial commit(s) were posted to github): git push origin your-branch --force Alternatively, a core member can squash your commits within Github. ## Code of Conduct {project-name} adheres to and enforces the [Contributor Covenant](http://contributor-covenant.org/version/1/4/) Code of Conduct. Please take a moment to read the [CODE_OF_CONDUCT.md](https://github.com/sigstore/{project-name}/blob/master/CODE_OF_CONDUCT.md) document. gitsign-0.13.0/COPYRIGHT.txt000066400000000000000000000010631477253552300153650ustar00rootroot00000000000000 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. gitsign-0.13.0/LICENSE000066400000000000000000000261351477253552300142700ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gitsign-0.13.0/Makefile000066400000000000000000000034041477253552300147150ustar00rootroot00000000000000# # 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. GIT_VERSION ?= $(shell git describe --tags --always --dirty) ARCH ?= $(shell uname -m) CGO_FLAG=0 # Fix `-buildmode=pie requires external (cgo) linking, but cgo is not enabled` ifeq ($(ARCH),riscv64) CGO_FLAG=1 endif LDFLAGS=-buildid= -X github.com/sigstore/gitsign/pkg/version.gitVersion=$(GIT_VERSION) .PHONY: build-gitsign build-gitsign: CGO_ENABLED=$(CGO_FLAG) go build -trimpath -ldflags "$(LDFLAGS)" . .PHONY: build-credential-cache build-credential-cache: CGO_ENABLED=$(CGO_FLAG) go build -trimpath -ldflags "$(LDFLAGS)" ./cmd/gitsign-credential-cache .PHONY: build-all build-all: build-gitsign build-credential-cache .PHONY: install-gitsign install-gitsign: CGO_ENABLED=$(CGO_FLAG) go install -trimpath -ldflags "$(LDFLAGS)" github.com/sigstore/gitsign .PHONY: install-credential-cache install-credential-cache: CGO_ENABLED=$(CGO_FLAG) go install -trimpath -ldflags "$(LDFLAGS)" github.com/sigstore/gitsign/cmd/gitsign-credential-cache .PHONY: install-all install-all: install-gitsign install-credential-cache .PHONY: unit-test unit-test: go test -v ./... # These tests use live dependencies, and may otherwise modify state. .PHONY: e2e-test e2e-test: go test -tags e2e -v ./... gitsign-0.13.0/README.md000066400000000000000000001077161477253552300145470ustar00rootroot00000000000000# Gitsign [![CI](https://github.com/sigstore/gitsign/actions/workflows/ci.yaml/badge.svg)](https://github.com/sigstore/gitsign/actions/workflows/ci.yaml) [![E2E](https://github.com/sigstore/gitsign/actions/workflows/e2e.yaml/badge.svg)](https://github.com/sigstore/gitsign/actions/workflows/e2e.yaml)

Gitsign logo

Keyless Git signing with Sigstore! This is heavily inspired by , but uses keyless Sigstore to sign Git commits with your own GitHub / OIDC identity. ## Installation Using Homebrew: ```sh brew install gitsign ``` Using Go: ```sh go install github.com/sigstore/gitsign@latest ``` ## Configuration Single Repository: ```sh cd /path/to/my/repository git config --local gpg.x509.program gitsign # Use gitsign for signing git config --local gpg.format x509 # gitsign expects x509 args # Warning: Setting this will make git commit/tag reliant on internet. # Alternatively, don't use these settings and add the -S flag instead. git config --local commit.gpgsign true # Sign all commits git config --local tag.gpgsign true # Sign all tags ``` All respositories: ```sh git config --global gpg.x509.program gitsign # Use gitsign for signing git config --global gpg.format x509 # gitsign expects x509 args # Warning: Setting this will make git commit/tag reliant on internet. # Alternatively, don't use these settings and add the -S flag instead. git config --global commit.gpgsign true # Sign all commits git config --global tag.gpgsign true # Sign all tags ``` To learn more about these options, see [`git-config`](https://git-scm.com/docs/git-config#Documentation/git-config.txt). ### File config Gitsign can be configured with a standard [git-config](https://git-scm.com/docs/git-config) file. For example, to set the Fulcio option for a single repo: ```sh $ git config --local gitsign.fulcio https://fulcio.example.com ``` The following config options are supported: | Option | Default | Description | | ------------------ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | fulcio | https://fulcio.sigstore.dev | Address of Fulcio server | | logPath | | Path to log status output. Helpful for debugging when no TTY is available in the environment. | | clientID | sigstore | OIDC client ID for application | | issuer | https://oauth2.sigstore.dev/auth | OIDC provider to be used to issue ID token | | matchCommitter | false | If true, verify that the committer matches certificate user identity. See [docs/committer-verification.md](./docs/committer-verification.md) for more details. | | redirectURL | | OIDC Redirect URL | | rekor | https://rekor.sigstore.dev | Address of Rekor server | | connectorID | | Optional Connector ID to auto-select to pre-select auth flow to use. For the public sigstore instance, valid values are:
- `https://github.com/login/oauth`
- `https://accounts.google.com`
- `https://login.microsoftonline.com` | | tokenProvider | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` | | timestampServerURL | | Address of timestamping authority. If set, a trusted timestamp will be included in the signature. | | timestampCertChain | | Path to PEM encoded certificate chain for RFC3161 Timestamp Authority verification. | | autoclose | true | If true, autoclose the browser window after `autocloseTimeout`. In order for autoclose to work you must also set `connectorID`. | | autocloseTimeout | 6 | If `autoclose` is true, this is how long to wait until the window is closed. | ### Environment Variables | Environment Variable | Sigstore
Prefix | Default | Description | | ---------------------------- | ------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GITSIGN_CREDENTIAL_CACHE | | | Optional path to [gitsign-credential-cache](cmd/gitsign-credential-cache/README.md) socket. | | GITSIGN_CONNECTOR_ID | ✅ | | Optional Connector ID to auto-select to pre-select auth flow to use. For the public sigstore instance, valid values are:
- `https://github.com/login/oauth`
- `https://accounts.google.com`
- `https://login.microsoftonline.com` | | GITSIGN_TOKEN_PROVIDER | ✅ | | Optional OIDC token provider to use to fetch tokens. If not set, any available providers are used. valid values are:
- `interactive`
- `spiffe`
- `google-workload-identity`
- `google-impersonation`
- `github-actions`
- `filesystem`
- `buildkite-agent` | | GITSIGN_FULCIO_URL | ✅ | https://fulcio.sigstore.dev | Address of Fulcio server | | GITSIGN_LOG | ❌ | | Path to log status output. Helpful for debugging when no TTY is available in the environment. | | GITSIGN_OIDC_CLIENT_ID | ✅ | sigstore | OIDC client ID for application | GITSIGN_OIDC_CLIENT_SECRET_FILE | ✅ | | Path to the file containing the OIDC client secret for the application. | | | GITSIGN_OIDC_ISSUER | ✅ | https://oauth2.sigstore.dev/auth | OIDC provider to be used to issue ID token | | GITSIGN_OIDC_REDIRECT_URL | ✅ | | OIDC Redirect URL | | GITSIGN_REKOR_URL | ✅ | https://rekor.sigstore.dev | Address of Rekor server | | GITSIGN_TIMESTAMP_SERVER_URL | ✅ | | Address of timestamping authority. If set, a trusted timestamp will be included in the signature. | | GITSIGN_TIMESTAMP_CERT_CHAIN | ✅ | | Path to PEM encoded certificate chain for RFC3161 Timestamp Authority verification. | | GITSIGN_FULCIO_ROOT | ✅ | | Path to PEM encoded certificate for Fulcio CA (additional alias: SIGSTORE_ROOT_FILE) | | GITSIGN_REKOR_MODE | ❌ | online | Rekor storage mode to operate in. One of [online, offline] (default: online)
online - Commit SHAs are stored in Rekor, requiring online verification for all commit objects.
offline - Hashed commit content is stored in Rekor, with Rekor attributes necessary for offline verification being stored in the commit itself.
Note: online verification will be deprecated in favor of offline in the future. | | GITSIGN_AUTOCLOSE | ❌ | true | If true, autoclose the browser window after `GITSIGN_AUTOCLOSE_TIME`. | | GITSIGN_AUTOCLOSE_TIMEOUT | ❌ | 6 | If `GITSIGN_AUTOCLOSE` is true, this is how long to wait until the window is closed. | For environment variables that support `Sigstore Prefix`, the values may be provided with either a `GITSIGN_` or `SIGSTORE_` prefix - e.g. `GITSIGN_CONNECTOR_ID` or `SIGSTORE_CONNECTOR_ID`. If both environment variables are set, `GITSIGN_` prefix takes priority. #### Other environment variables | Environment Variable | Description | | ------------------------- | ------------------------------------------------------------------------------- | | SIGSTORE_REKOR_PUBLIC_KEY | This specifies an out of band PEM-encoded public key to use for a custom Rekor. | ## Usage ### Signing Commits Once configured, you can sign commits as usual with `git commit -S` (or `git config --global commit.gpgsign true` to enable signing for all commits). ```sh $ git commit --allow-empty --message="Signed commit" Your browser will now be opened to: https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&... [main 040b9af] Signed commit ``` This will redirect you through the Sigstore Keyless flow to authenticate and sign the commit. ### Signing Tags Once configured, you can sign commits as usual with `git tag -s` (or `git config --global tag.gpgsign true` to enable signing for all tags). ```sh $ git tag v0.0.1 Your browser will now be opened to: https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&... ``` This will redirect you through the Sigstore Keyless flow to authenticate and sign the tag. ### Verifying commits Commits can be verified using `gitsign verify`: ```sh $ gitsign verify --certificate-identity=billy@chainguard.dev --certificate-oidc-issuer=https://accounts.google.com HEAD tlog index: 16072348 gitsign: Signature made using certificate ID 0xa6c178d9292f70eb5c4ad9e274ead0158e75e484 | CN=sigstore-intermediate,O=sigstore.dev gitsign: Good signature from [billy@chainguard.dev](https://accounts.google.com) Validated Git signature: true Validated Rekor entry: true Validated Certificate claims: true ``` `HEAD` may be replaced with any [Git revision](https://git-scm.com/docs/gitrevisions) (e.g. branch, tag, etc.). **NOTE**: `gitsign verify` is preferred over [`git verify-commit`](https://git-scm.com/docs/git-verify-commit) and [`git verify-tag`](https://git-scm.com/docs/git-verify-tag). The git commands do not pass through any expected identity information to the signing tools, so they only verify cryptographic integrity and that the data exists on Rekor, but not **who** put the data there. Using these commands will still work, but a warning being displayed. ```sh $ git verify-commit HEAD tlog index: 16072349 gitsign: Signature made using certificate ID 0xa6c178d9292f70eb5c4ad9e274ead0158e75e484 | CN=sigstore-intermediate,O=sigstore.dev gitsign: Good signature from [billy@chainguard.dev](https://accounts.google.com) Validated Git signature: true Validated Rekor entry: true Validated Certificate claims: false WARNING: git verify-commit does not verify cert claims. Prefer using `gitsign verify` instead. ``` ### Private Sigstore Gitsign is compatible with other Sigstore tools cosign for running against other Sigstore instances besides the default public instance. See [cosign documentation](https://docs.sigstore.dev/cosign/custom_components/) for how to configure and use another instance. ## FAQ ### Is there any way to bypass the browser flow? A browser window is needed to get an OAuth token, since gitsign aims to not store refresh tokens or other cryptographic material on disk, but there are some things you can do to make this process a bit easier! 1. Set the `connectorID` config option - This preselects the identity provider to use. Assuming you're already signed in, in most cases you'll bounce directly to the auth success screen! (and you can clean up the browser tabs later) 2. Use the [Credential Cache](cmd/gitsign-credential-cache/README.md). This uses an in-memory credential cache over a file socket that allows you to persist keys and certificates for their full lifetime (meaning you only need to auth once every 10 minutes). ### Why doesn't GitHub show commits as [verified](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)? GitHub doesn't recognize Gitsign signatures as verified at the moment: 1. The sigstore CA root is not a part of [GitHub's trust root](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#smime-commit-signature-verification). 2. Because Gitsign's ephemeral keys are only valid for a short time, using standard x509 verification would consider the certificate invalid after expiration. Verification needs to include validation via Rekor to verify the cert was valid at the time it was used. We hope to work with GitHub to get these types of signatures recognized as verified in the future! ## Debugging ### Configuration If `gitsign` is running with unexpected configs, you can validate the config values that are being ran by running `gitsign --version`: ```sh $ gitsign --version gitsign version v0.5.2 parsed config: { "Fulcio": "https://fulcio.sigstore.dev", "FulcioRoot": "", "Rekor": "https://rekor.sigstore.dev", "ClientID": "sigstore", "RedirectURL": "", "Issuer": "https://oauth2.sigstore.dev/auth", "ConnectorID": "", "TimestampURL": "", "TimestampCert": "", "LogPath": "" } ``` ### Signing If there is an error during signing, you may see an error like: ``` error: gpg failed to sign the data fatal: failed to write commit object ``` When Git invokes signing tools, both stdout and stderr are captured which means `gitsign` cannot push back messages to shells interactively. If a TTY is available, `gitsign` will output information to the TTY output directly. If a TTY is not available (e.g. in CI runners, etc.), you can use the `GITSIGN_LOG` environment variable to tee logs into a readable location for debugging. ### Verification - `failed to verify detached signature: x509: certificate signed by unknown authority` This usually means the TUF root used to verify the commit is not the same as the root that was used to sign it. This can happen if you use multiple sigstore instances frequently (e.g. if you're a sigstore developer - sigstore staging). You can double check relevant environment variables by running `gitsign --version`. ## Privacy ### What data does Gitsign store? Gitsign stores data in 2 places: 1. Within the Git commit The commit itself contains a signed digest of the user commit content (e.g. author, committer, message, parents, etc.) along with the code signing certificate. This data is stored within the commit itself as part of your repository. See [Inspecting the Git commit signature](#inspecting-the-git-commit-signature) for more details. 2. Within the Rekor transparency log To be able to verify signatures for ephemeral certs past their `Not After` time, Gitsign records commits and the code signing certificates to [Rekor](https://docs.sigstore.dev/rekor/overview/). - If `rekorMode = online` (default) This data is a [HashedRekord](https://github.com/sigstore/rekor/blob/e375eb461cae524270889b57a249ff086bea6c05/types.md#hashed-rekord) containing a SHA256 hash of the commit SHA, as well as the code signing certificate. See [Verifying the Transparency Log](#verifying-the-transparency-log) for more details. - If `rekorMode = offline` Note: offline verification is new, and should be considered experimental for now. By default, data is written to the [public Rekor instance](https://docs.sigstore.dev/rekor/public-instance). In particular, users and organizations may be sensitive to the data contained within code signing certificates returned by Fulcio, which may include user emails or repo identifiers. See [OIDC usage in Fulcio](https://github.com/sigstore/fulcio/blob/6ac6b8c94c3ec6106d68c0f92225016a3a6eef79/docs/oidc.md) for more details for what data is contained in the code signing certs, and [Deploy a Rekor Server Manually](https://docs.sigstore.dev/rekor/installation/#deploy-a-rekor-server-manually) for how to run your own Rekor instance. ## Security Should you discover any security issues, please refer to the [security process](https://github.com/sigstore/gitsign/security/policy) ## Advanced ### Inspecting the Git commit signature Git commit signatures use [CMS/PKCS7 signatures](https://datatracker.ietf.org/doc/html/rfc5652). We can inspect the underlying data / certificate used by running: ```sh $ git cat-file commit HEAD | sed -n '/-BEGIN/, /-END/p' | sed 's/^ //g' | sed 's/gpgsig //g' | sed 's/SIGNED MESSAGE/PKCS7/g' | openssl pkcs7 -print -print_certs -text PKCS7: type: pkcs7-signedData (1.2.840.113549.1.7.2) d.sign: version: 1 md_algs: algorithm: sha256 (2.16.840.1.101.3.4.2.1) parameter: contents: type: pkcs7-data (1.2.840.113549.1.7.1) d.data: cert: cert_info: version: 2 serialNumber: 0x2ECFB7E0D25F9A741FC3B19B56C4B74D25864788 signature: algorithm: ecdsa-with-SHA384 (1.2.840.10045.4.3.3) parameter: issuer: O=sigstore.dev, CN=sigstore-intermediate validity: notBefore: Jan 13 21:00:13 2023 GMT notAfter: Jan 13 21:10:13 2023 GMT subject: key: algor: algorithm: id-ecPublicKey (1.2.840.10045.2.1) parameter: OBJECT:prime256v1 (1.2.840.10045.3.1.7) public_key: (0 unused bits) 0000 - 04 0d 3e f5 05 98 53 d2-68 21 9d e7 88 07 ..>...S.h!.... 000e - 0a d9 bc 8e 9f e3 00 e0-5d 28 b2 41 24 a7 ........](.A$. 001c - a5 93 28 cc 45 d9 1e ee-a3 1c 8d 42 64 ab ..(.E......Bd. 002a - 14 e6 ec 41 29 77 3a 0e-95 94 33 f7 40 62 ...A)w:...3.@b 0038 - cd 25 fd 17 35 be 4d d4-f9 .%..5.M.. issuerUID: subjectUID: extensions: object: X509v3 Key Usage (2.5.29.15) critical: TRUE value: 0000 - 03 02 07 80 .... object: X509v3 Extended Key Usage (2.5.29.37) critical: BOOL ABSENT value: 0000 - 30 0a 06 08 2b 06 01 05-05 07 03 03 0...+....... object: X509v3 Subject Key Identifier (2.5.29.14) critical: BOOL ABSENT value: 0000 - 04 14 46 eb 25 b9 3b 3d-87 71 6a eb ba ..F.%.;=.qj.. 000d - e4 a4 4b b0 f1 17 4b 46-58 ..K...KFX object: X509v3 Authority Key Identifier (2.5.29.35) critical: BOOL ABSENT value: 0000 - 30 16 80 14 df d3 e9 cf-56 24 11 96 f9 0.......V$... 000d - a8 d8 e9 28 55 a2 c6 2e-18 64 3f ...(U....d? object: X509v3 Subject Alternative Name (2.5.29.17) critical: TRUE value: 0000 - 30 16 81 14 62 69 6c 6c-79 40 63 68 61 0...billy@cha 000d - 69 6e 67 75 61 72 64 2e-64 65 76 inguard.dev object: undefined (1.3.6.1.4.1.57264.1.1) critical: BOOL ABSENT value: 0000 - 68 74 74 70 73 3a 2f 2f-61 63 63 6f 75 https://accou 000d - 6e 74 73 2e 67 6f 6f 67-6c 65 2e 63 6f nts.google.co 001a - 6d m object: undefined (1.3.6.1.4.1.11129.2.4.2) critical: BOOL ABSENT value: 0000 - 04 7b 00 79 00 77 00 dd-3d 30 6a c6 c7 .{.y.w..=0j.. 000d - 11 32 63 19 1e 1c 99 67-37 02 a2 4a 5e .2c....g7..J^ 001a - b8 de 3c ad ff 87 8a 72-80 2f 29 ee 8e ..<....r./).. 0027 - 00 00 01 85 ac ee dc fa-00 00 04 03 00 ............. 0034 - 48 30 46 02 21 00 a1 e2-05 30 53 6f fb H0F.!....0So. 0041 - 05 28 b6 bb 41 77 a9 7c-21 f4 a9 49 8b .(..Aw.|!..I. 004e - f8 a6 1f 35 85 a7 40 b3-07 5c cb 04 02 ...5..@..\... 005b - 21 00 f4 39 7b 17 5a 59-fa 10 1c f8 bf !..9{.ZY..... 0068 - 46 cd bc de cc e8 39 7a-03 d4 1c 78 e5 F.....9z...x. 0075 - b1 e7 7a ba 66 79 f2 c8- ..z.fy.. sig_alg: algorithm: ecdsa-with-SHA384 (1.2.840.10045.4.3.3) parameter: signature: (0 unused bits) 0000 - 30 65 02 30 5b 7c d7 ea-7c 5f 68 76 0b da 50 0e.0[|..|_hv..P 000f - 14 cc bf 4c 65 07 70 68-52 33 9a 85 57 ce f5 ...Le.phR3..W.. 001e - ff 18 5b 8b 08 76 2a dd-7d 1a 19 7f b6 90 be ..[..v*.}...... 002d - ad 24 96 9a 2a 0a d6 02-31 00 ac 15 2b 1d 00 .$..*...1...+.. 003c - 6e 26 95 66 c9 6d cd 7e-e0 cd 12 0e 60 8b f9 n&.f.m.~....`.. 004b - 38 a9 0a dc 01 28 9a 39-e3 cd c9 eb a5 0c 08 8....(.9....... 005a - 71 47 39 c8 dc 9d db c3-cf 8e f5 cd e9 qG9.......... crl: signer_info: version: 1 issuer_and_serial: issuer: O=sigstore.dev, CN=sigstore-intermediate serial: 0x2ECFB7E0D25F9A741FC3B19B56C4B74D25864788 digest_alg: algorithm: sha256 (2.16.840.1.101.3.4.2.1) parameter: auth_attr: object: contentType (1.2.840.113549.1.9.3) value.set: OBJECT:pkcs7-data (1.2.840.113549.1.7.1) object: signingTime (1.2.840.113549.1.9.5) value.set: UTCTIME:Jan 13 21:00:13 2023 GMT object: messageDigest (1.2.840.113549.1.9.4) value.set: OCTET STRING: 0000 - 21 e9 ce 7a 69 ff 22 57-43 a2 fc c9 12 !..zi."WC.... 000d - 8a 67 c6 45 e7 31 88 4c-08 3f 26 9a 13 .g.E.1.L.?&.. 001a - ac 85 d6 6d f5 8e ...m.. digest_enc_alg: algorithm: ecdsa-with-SHA256 (1.2.840.10045.4.3.2) parameter: enc_digest: 0000 - 30 46 02 21 00 cc 5a 1e-9a 27 70 ba 1f 70 7d 0F.!..Z..'p..p} 000f - d6 f0 1c 56 f2 32 b3 d2-8f c4 63 dd 9c 82 cc ...V.2....c.... 001e - 69 30 2c cd 9e 90 f9 02-21 00 82 43 0a f7 79 i0,.....!..C..y 002d - 64 41 14 6b 28 03 ac 38-2b a3 82 bd a8 a1 ea dA.k(..8+...... 003c - 52 db cf f2 5f d4 84 4f-85 b4 53 53 R..._..O..SS unauth_attr: object: undefined (1.3.6.1.4.1.57264.3.8) value.set: INTEGER:6954358 object: undefined (1.3.6.1.4.1.57264.3.9) value.set: INTEGER:6954357 object: undefined (1.3.6.1.4.1.57264.3.1) value.set: INTEGER:1673643613 object: undefined (1.3.6.1.4.1.57264.3.3) value.set: INTEGER:11117788 object: undefined (1.3.6.1.4.1.57264.3.2) value.set: PRINTABLESTRING:c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d object: undefined (1.3.6.1.4.1.57264.3.7) value.set: PRINTABLESTRING:373443ac6ee5e01d4bfa00666f79d5c7cee0380684ebe571fc98bdffea82f972 object: undefined (1.3.6.1.4.1.57264.3.4) value.set: OCTET STRING: 0000 - 30 45 02 20 00 d0 88 ff-91 18 75 1c 90 0E. ......u.. 000d - 4c aa f3 37 94 45 a8 ca-1e a4 de 60 10 L..7.E.....`. 001a - 0a 22 69 03 c9 2d d2 0e-1a 9f 02 21 00 ."i..-.....!. 0027 - af cd 78 85 f2 66 5f 22-c5 d3 a2 5c fc ..x..f_"...\. 0034 - e2 c1 fe 0c f2 27 aa f0-fa fd 73 ca 5d .....'....s.] 0041 - 58 98 9c 00 df 5c X....\ object: undefined (1.3.6.1.4.1.57264.3.5) value.set: UTF8STRING:rekor.sigstore.dev - 2605736670972794746 6954358 NzRDrG7l4B1L+gBmb3nVx87gOAaE6+Vx/Ji9/+qC+XI= Timestamp: 1673643613823629328 \U2014 rekor.sigstore.dev wNI9ajBFAiB1IrUY3QV0nXQF0NFuo+1WtTRRYIKhaBI4rUj0Ry3WkwIhAI6D+kvZh+NhJ7Xi4HT0kPVB0nxGjR+cOHFOU1HJbUKF object: undefined (1.3.6.1.4.1.57264.3.6) value.set: SEQUENCE: 0:d=0 hl=4 l= 858 cons: SEQUENCE 4:d=1 hl=2 l= 64 prim: PRINTABLESTRING :be961775858a32f96c8d12fb8db3c3101bb4d8296f37f53f74dc2cb51c22a9ad 70:d=1 hl=2 l= 64 prim: PRINTABLESTRING :92bd4aedddebab9be5678442a28bcfbada3300e04c0726368796a6d8b32fd909 136:d=1 hl=2 l= 64 prim: PRINTABLESTRING :6e5a335c4b2f89e25d5be75ed0a724b154e0f53367bd4888c625d96f4a1e6b79 202:d=1 hl=2 l= 64 prim: PRINTABLESTRING :67bce8699de01f6fc9ac8865ee5b08ee3a6617b57328b59cc342c55a4067652b 268:d=1 hl=2 l= 64 prim: PRINTABLESTRING :f06fad8a06e8b60133ec7847be1586d517728f2da95f6e81ec9d1e4b1bbfc9d1 334:d=1 hl=2 l= 64 prim: PRINTABLESTRING :32f164dcc4d2ff3b095c4f2d2b4beb25223cffd028a53fae3cac98f70e4bbd83 400:d=1 hl=2 l= 64 prim: PRINTABLESTRING :1c3b03f4eff02f6405ef856350ffd03650d5de5271a65f0cee51ffe4fc6a99af 466:d=1 hl=2 l= 64 prim: PRINTABLESTRING :c73ab44c0792697f44a5e237a47fff42f9c4dbf869071ee08e95dec222917f09 532:d=1 hl=2 l= 64 prim: PRINTABLESTRING :e1e7772b7c20874ea1b3bebb2fd4ec5b496bcf45c338495ddbe93ae1fbcabe2c 598:d=1 hl=2 l= 64 prim: PRINTABLESTRING :5da6951fe16688f8a256fc9adf3ccda1806b811e2bc50caab99ee61ded6ef6a3 664:d=1 hl=2 l= 64 prim: PRINTABLESTRING :e7d67f5102ddeda58eda651dcba76876d01955a4eca9fce4caaf9e0ba7521cdd 730:d=1 hl=2 l= 64 prim: PRINTABLESTRING :616429db6c7d20c5b0eff1a6e512ea57a0734b94ae0bc7c914679463e01a7fba 796:d=1 hl=2 l= 64 prim: PRINTABLESTRING :5a4ad1534b1e770f02bfde0de15008a6971cf1ffbfa963fc9c2a644973a8d2d1 -----BEGIN PKCS7----- MIIJ3gYJKoZIhvcNAQcCoIIJzzCCCcsCAQExDTALBglghkgBZQMEAgEwCwYJKoZI hvcNAQcBoIICpTCCAqEwggInoAMCAQICFC7Pt+DSX5p0H8Oxm1bEt00lhkeIMAoG CCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln c3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIzMDExMzIxMDAxM1oXDTIzMDExMzIxMTAx M1owADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABA0+9QWYU9JoIZ3niAcK2byO n+MA4F0oskEkp6WTKMxF2R7uoxyNQmSrFObsQSl3Og6VlDP3QGLNJf0XNb5N1Pmj ggFGMIIBQjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD VR0OBBYEFEbrJbk7PYdxauu65KRLsPEXS0ZYMB8GA1UdIwQYMBaAFN/T6c9WJBGW +ajY6ShVosYuGGQ/MCIGA1UdEQEB/wQYMBaBFGJpbGx5QGNoYWluZ3VhcmQuZGV2 MCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiwYK KwYBBAHWeQIEAgR9BHsAeQB3AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAv Ke6OAAABhazu3PoAAAQDAEgwRgIhAKHiBTBTb/sFKLa7QXepfCH0qUmL+KYfNYWn QLMHXMsEAiEA9Dl7F1pZ+hAc+L9GzbzezOg5egPUHHjlsed6umZ58sgwCgYIKoZI zj0EAwMDaAAwZQIwW3zX6nxfaHYL2lAUzL9MZQdwaFIzmoVXzvX/GFuLCHYq3X0a GX+2kL6tJJaaKgrWAjEArBUrHQBuJpVmyW3NfuDNEg5gi/k4qQrcASiaOePNyeul DAhxRznI3J3bw8+O9c3pMYIG/zCCBvsCAQEwTzA3MRUwEwYDVQQKEwxzaWdzdG9y ZS5kZXYxHjAcBgNVBAMTFXNpZ3N0b3JlLWludGVybWVkaWF0ZQIULs+34NJfmnQf w7GbVsS3TSWGR4gwCwYJYIZIAWUDBAIBoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3 DQEHATAcBgkqhkiG9w0BCQUxDxcNMjMwMTEzMjEwMDEzWjAvBgkqhkiG9w0BCQQx IgQgIenOemn/IldDovzJEopnxkXnMYhMCD8mmhOshdZt9Y4wCgYIKoZIzj0EAwIE SDBGAiEAzFoemidwuh9wfdbwHFbyMrPSj8Rj3ZyCzGkwLM2ekPkCIQCCQwr3eWRB FGsoA6w4K6OCvaih6lLbz/Jf1IRPhbRTU6GCBdUwEwYKKwYBBAGDvzADCDEFAgNq HXYwEwYKKwYBBAGDvzADCTEFAgNqHXUwFAYKKwYBBAGDvzADATEGAgRjwcZdMBQG CisGAQQBg78wAwMxBgIEAKmk3DBQBgorBgEEAYO/MAMCMUITQGMwZDIzZDZhZDQw Njk3M2Y5NTU5ZjNiYTJkMWNhMDFmODQxNDdkOGZmYzViODQ0NWMyMjRmOThiOTU5 MTgwMWQwUAYKKwYBBAGDvzADBzFCE0AzNzM0NDNhYzZlZTVlMDFkNGJmYTAwNjY2 Zjc5ZDVjN2NlZTAzODA2ODRlYmU1NzFmYzk4YmRmZmVhODJmOTcyMFcGCisGAQQB g78wAwQxSQRHMEUCIADQiP+RGHUckEyq8zeURajKHqTeYBAKImkDyS3SDhqfAiEA r814hfJmXyLF06Jc/OLB/gzyJ6rw+v1zyl1YmJwA31wwggEMBgorBgEEAYO/MAMF MYH9DIH6cmVrb3Iuc2lnc3RvcmUuZGV2IC0gMjYwNTczNjY3MDk3Mjc5NDc0Ngo2 OTU0MzU4Ck56UkRyRzdsNEIxTCtnQm1iM25WeDg3Z09BYUU2K1Z4L0ppOS8rcUMr WEk9ClRpbWVzdGFtcDogMTY3MzY0MzYxMzgyMzYyOTMyOAoK4oCUIHJla29yLnNp Z3N0b3JlLmRldiB3Tkk5YWpCRkFpQjFJclVZM1FWMG5YUUYwTkZ1bysxV3RUUlJZ SUtoYUJJNHJVajBSeTNXa3dJaEFJNkQra3ZaaCtOaEo3WGk0SFQwa1BWQjBueEdq UitjT0hGT1UxSEpiVUtGCjCCA24GCisGAQQBg78wAwYxggNeMIIDWhNAYmU5NjE3 NzU4NThhMzJmOTZjOGQxMmZiOGRiM2MzMTAxYmI0ZDgyOTZmMzdmNTNmNzRkYzJj YjUxYzIyYTlhZBNAOTJiZDRhZWRkZGViYWI5YmU1Njc4NDQyYTI4YmNmYmFkYTMz MDBlMDRjMDcyNjM2ODc5NmE2ZDhiMzJmZDkwORNANmU1YTMzNWM0YjJmODllMjVk NWJlNzVlZDBhNzI0YjE1NGUwZjUzMzY3YmQ0ODg4YzYyNWQ5NmY0YTFlNmI3ORNA NjdiY2U4Njk5ZGUwMWY2ZmM5YWM4ODY1ZWU1YjA4ZWUzYTY2MTdiNTczMjhiNTlj YzM0MmM1NWE0MDY3NjUyYhNAZjA2ZmFkOGEwNmU4YjYwMTMzZWM3ODQ3YmUxNTg2 ZDUxNzcyOGYyZGE5NWY2ZTgxZWM5ZDFlNGIxYmJmYzlkMRNAMzJmMTY0ZGNjNGQy ZmYzYjA5NWM0ZjJkMmI0YmViMjUyMjNjZmZkMDI4YTUzZmFlM2NhYzk4ZjcwZTRi YmQ4MxNAMWMzYjAzZjRlZmYwMmY2NDA1ZWY4NTYzNTBmZmQwMzY1MGQ1ZGU1Mjcx YTY1ZjBjZWU1MWZmZTRmYzZhOTlhZhNAYzczYWI0NGMwNzkyNjk3ZjQ0YTVlMjM3 YTQ3ZmZmNDJmOWM0ZGJmODY5MDcxZWUwOGU5NWRlYzIyMjkxN2YwORNAZTFlNzc3 MmI3YzIwODc0ZWExYjNiZWJiMmZkNGVjNWI0OTZiY2Y0NWMzMzg0OTVkZGJlOTNh ZTFmYmNhYmUyYxNANWRhNjk1MWZlMTY2ODhmOGEyNTZmYzlhZGYzY2NkYTE4MDZi ODExZTJiYzUwY2FhYjk5ZWU2MWRlZDZlZjZhMxNAZTdkNjdmNTEwMmRkZWRhNThl ZGE2NTFkY2JhNzY4NzZkMDE5NTVhNGVjYTlmY2U0Y2FhZjllMGJhNzUyMWNkZBNA NjE2NDI5ZGI2YzdkMjBjNWIwZWZmMWE2ZTUxMmVhNTdhMDczNGI5NGFlMGJjN2M5 MTQ2Nzk0NjNlMDFhN2ZiYRNANWE0YWQxNTM0YjFlNzcwZjAyYmZkZTBkZTE1MDA4 YTY5NzFjZjFmZmJmYTk2M2ZjOWMyYTY0NDk3M2E4ZDJkMQ== -----END PKCS7----- ``` gitsign-0.13.0/cmd/000077500000000000000000000000001477253552300140175ustar00rootroot00000000000000gitsign-0.13.0/cmd/gitsign-credential-cache/000077500000000000000000000000001477253552300206345ustar00rootroot00000000000000gitsign-0.13.0/cmd/gitsign-credential-cache/README.md000066400000000000000000000163221477253552300221170ustar00rootroot00000000000000# gitsign-credential-cache `gitsign-credential-cache` is a **optional** helper binary that allows users to cache signing credentials. This can be helpful in situations where you need to perform multiple signing operations back to back. Credentials are stored in memory, and the cache is exposed via a Unix socket. Credentials stored in this cache are only as secure as the unix socket implementation on your OS - any user that can access the socket can access the data. ⚠️ When in doubt, we recommend **not** using the cache. In particular: - If you're running on a shared system - if other admins have access to the cache socket they can access your keys. - If you're running in an environment that has ambient OIDC credentials (e.g. GCE/GKE, AWS, GitHub Actions, etc.), Gitsign will automatically use the environment's OIDC credentials. You don't need caching. If you understand the risks, read on! ## What's stored in the cache - Ephemeral Private Key - Fulcio Code Signing certificate + chain All data is stored in memory, keyed to your Git working directory (i.e. different repo paths will cache different keys) The data that is cached would allow any user with access to sign artifacts as you, until the signing certificate expires, typically in ten minutes. ## Usage ```sh $ gitsign-credential-cache & $ export GITSIGN_CREDENTIAL_CACHE="$HOME/.cache/sigstore/gitsign/cache.sock" $ git commit ... ``` Note: The cache directory will change depending on your OS - the socket file that is used is output by `gitsign-credential-cache` when it is spawned. See [os.UserCacheDir](https://pkg.go.dev/os#UserCacheDir) for details on how the cache directory is selected. ### Systemd service There are systemd user units in contrib Change path to gitsign-credential-cache in service unit ```sh ${EDITOR:-vi} ./contrib/gitsign-credential-cache.service ``` Install units in home directory for specific user ```sh install -m 0660 -D -t $HOME/.config/systemd/user/ ./contrib/gitsign-credential-cache.{socket,service} systemctl --user daemon-reload ``` OR install them for all users ```sh sudo install -m 0660 -D -t /etc/systemd/user/ ./contrib/gitsign-credential-cache.{socket,service} sudo systemctl daemon-reload ``` After that you can enable and start socket service ```sh systemctl --user enable --now gitsign-credential-cache.socket ``` ### Forwarding cache over SSH (Requires gitsign >= v0.5) The credential cache socket can be forwarded over SSH using `RemoteForward`: ```sh [local] $ ssh -R /home/wlynch/.sigstore/cache.sock:${GITSIGN_CREDENTIAL_CACHE} [remote] $ export GITSIGN_CREDENTIAL_CACHE="/home/wlynch/.sigstore/cache.sock" [remote] $ git commit ... ``` (format is `-R :`) or in `~/.ssh/config`: ``` Host amazon RemoteForward /home/wlynch/.cache/sigstore/cache.sock /Users/wlynch/Library/Caches/sigstore/gitsign/cache.sock ``` where `/home/wlynch/.cache/sigstore/cache.sock` is the location of the socket path on the remote host (this can be changed, so long as the environment variable is also updated to match). #### Common issues > Warning: remote port forwarding failed for listen path - The socket directory must exist on the remote, else the socket will fail to mount. - We recommend setting `StreamLocalBindUnlink yes` on the remote `/etc/ssh/sshd_config` to allow for sockets to be overwritten on the same path for new connections - SSH does not cleanup sockets automatically on exit and the socket forwarding will fail if a file already exists on the remote path (see [thread](https://marc.info/?l=openssh-unix-dev&m=151998074424424&w=2) for more discussion). ## Running it as a launchctl service on macOS If you are a macOS user, you can run `gitsign-credential-cache` as a launchctl service by running the following commands in your terminal: ```sh cat < /tmp/gitsign-credential-cache.sh #!/bin/bash set -euo pipefail if ! command -v gitsign &> /dev/null; then echo "gitsign command not found. Please install it before running this script: https://docs.sigstore.dev/signing/gitsign/" exit 1 fi if ! command -v gitsign-credential-cache &> /dev/null; then echo "gitsign-credential-cache command not found. Please install it before running this script: 'go install github.com/sigstore/gitsign/cmd/gitsign-credential-cache@latest'" exit 1 fi launch_agents_dir="${HOME}/Library/LaunchAgents" plist_name="my.gitsign-credential-cache.plist" plist_path="${launch_agents_dir}/${plist_name}" gitsign_cache_dir="${HOME}/Library/Caches/sigstore/gitsign" gitsign_cache_path="${gitsign_cache_dir}/cache.sock" if [ -f "${plist_path}" ]; then echo "The plist file ${plist_path} already exists. Please remove it or use a different name." exit 1 fi if [ -f "${gitsign_cache_path}" ]; then echo "The gitsign cache path ${gitsign_cache_path} already exists. Please remove it or use a different name." exit 1 fi mkdir -pv "${launch_agents_dir}" # https://github.com/sigstore/gitsign/blob/main/cmd/gitsign-credential-cache/README.md cat << EOF > "${plist_path}" KeepAlive Label my.gitsign-credential-cache ProgramArguments /opt/homebrew/bin/gitsign-credential-cache RunAtLoad StandardErrorPath /opt/homebrew/var/log/gitsign-credential-cache.log StandardOutPath /opt/homebrew/var/log/gitsign-credential-cache.log EOF chmod 644 "${plist_path}" chown $(whoami) "${plist_path}" echo "Created plist file: ${plist_path}" launchctl load -wF "${plist_path}" plutil -lint "${plist_path}" if [ ! -d "${gitsign_cache_dir}" ]; then echo "The gitsign cache directory ${gitsign_cache_dir} does not exist. Creating it now." mkdir -pv "${gitsign_cache_dir}" fi export GITSIGN_CREDENTIAL_CACHE="${gitsign_cache_path}" if [ -f "${HOME}/.zshrc" ]; then shell_config_file="${HOME}/.zshrc" elif [ -f "${HOME}/.bashrc" ]; then shell_config_file="${HOME}/.bashrc" else echo "No .bashrc or .zshrc found in your home directory." exit 1 fi export_line="export GITSIGN_CREDENTIAL_CACHE=\"${gitsign_cache_path}\"" if ! grep -qF -- "${export_line}" "${shell_config_file}"; then echo "${export_line}" >> "${shell_config_file}" echo "Added GITSIGN_CREDENTIAL_CACHE to ${shell_config_file}. Please restart your shell to apply the changes: 'source ${shell_config_file}'" else echo "GITSIGN_CREDENTIAL_CACHE already exists in ${shell_config_file}!" fi EOF chmod +x /tmp/gitsign-credential-cache.sh echo "Running the script to create the launchctl service..." /tmp/gitsign-credential-cache.sh ``` Once you did that now you should be able to see the `gitsign-credential-cache` service running by running the following command: ```sh $ launchctl list | grep -i "my.gitsign" 2398 0 my.gitsign-credential-cache ``` and of course if you would like to tail the logs of your service you can do so by running the following command: ```sh tail -f /opt/homebrew/var/log/gitsign-credential-cache.log ```gitsign-0.13.0/cmd/gitsign-credential-cache/main.go000066400000000000000000000061111477253552300221060ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "log" "net" "net/rpc" "os" "path/filepath" "github.com/coreos/go-systemd/v22/activation" "github.com/spf13/pflag" "github.com/sigstore/gitsign/internal/cache/service" "github.com/sigstore/gitsign/pkg/version" ) var ( // Action flags versionFlag = pflag.BoolP("version", "v", false, "print the version number") systemdFlag = pflag.Bool("systemd-socket-activation", false, "use systemd socket activation") ) func main() { pflag.Parse() if *versionFlag { v := version.GetVersionInfo() fmt.Printf("gitsign-credential-cache version %s\n", v.GitVersion) os.Exit(0) } var connChan = make(chan net.Conn) if *systemdFlag { // Stop if we're not running under systemd. if os.Getenv("LISTEN_PID") == "" { log.Fatalf("systemd socket activation requested but not running under systemd") } listeners, err := activation.Listeners() if err != nil { log.Fatalf("error getting systemd listeners: %v", err) } var validCount int for _, l := range listeners { if l == nil { continue } fmt.Println(l.Addr().String()) go connToChan(l, connChan) validCount++ } if validCount == 0 { log.Fatalf("no valid systemd listeners found") } } else { user, err := os.UserCacheDir() if err != nil { log.Fatalf("error getting user cache directory: %v", err) } dir := filepath.Join(user, "sigstore", "gitsign") if err := os.MkdirAll(dir, 0700); err != nil { log.Fatalf("error creating %s: %v", dir, err) } path := filepath.Join(dir, "cache.sock") if _, err := os.Stat(path); err == nil { os.Remove(path) } fmt.Println(path) l, err := net.Listen("unix", path) if err != nil { log.Fatalf("error opening socket: %v", err) } // Previously, we used syscall.Umask(0077) to ensure this was // permissioned only to the current user. Windows doesn't have this // syscall, so we're switching over to an explicit Chmod on the socket // path. // Also see https://github.com/golang/go/issues/11822 if err := os.Chmod(path, 0700); err != nil { log.Fatalf("error setting socket permissions: %v", err) } go connToChan(l, connChan) } srv := rpc.NewServer() if err := srv.Register(service.NewService()); err != nil { log.Fatalf("error registering RPC service: %v", err) } for conn := range connChan { go srv.ServeConn(conn) } } func connToChan(l net.Listener, connChan chan net.Conn) { for { conn, err := l.Accept() if err != nil { log.Fatalf("error accepting connection: %v", err) } connChan <- conn } } gitsign-0.13.0/contrib/000077500000000000000000000000001477253552300147145ustar00rootroot00000000000000gitsign-0.13.0/contrib/systemd/000077500000000000000000000000001477253552300164045ustar00rootroot00000000000000gitsign-0.13.0/contrib/systemd/gitsign-credential-cache.service000066400000000000000000000002271477253552300246040ustar00rootroot00000000000000[Unit] Description=GitSign credential cache [Service] Type=simple ExecStart=%h/.local/bin/gitsign-credential-cache [Install] WantedBy=default.target gitsign-0.13.0/contrib/systemd/gitsign-credential-cache.socket000066400000000000000000000002371477253552300244350ustar00rootroot00000000000000[Unit] Description=GitSign credential cache socket [Socket] ListenStream=%C/sigstore/gitsign/cache.sock DirectoryMode=0700 [Install] WantedBy=default.target gitsign-0.13.0/docs/000077500000000000000000000000001477253552300142045ustar00rootroot00000000000000gitsign-0.13.0/docs/cli/000077500000000000000000000000001477253552300147535ustar00rootroot00000000000000gitsign-0.13.0/docs/cli/gitsign.md000066400000000000000000000024201477253552300167370ustar00rootroot00000000000000## gitsign Keyless Git signing with Sigstore! ``` gitsign [flags] ``` ### Options ``` -a, --armor create ascii armored output -b, --detach-sign make a detached signature -h, --help help for gitsign --include-certs int -3 is the same as -2, but omits issuer when cert has Authority Information Access extension. -2 includes all certs except root. -1 includes all certs. 0 includes no certs. 1 includes leaf cert. >1 includes n from the leaf. Default -2. (default -2) -u, --local-user string use USER-ID to sign -s, --sign make a signature --status-fd int write special status strings to the file descriptor n. (default -1) -v, --verify verify a signature --version print Gitsign version ``` ### SEE ALSO * [gitsign attest](gitsign_attest.md) - add attestations to Git objects * [gitsign initialize](gitsign_initialize.md) - Initializes Sigstore root to retrieve trusted certificate and key targets for verification. * [gitsign show](gitsign_show.md) - Show source predicate information * [gitsign verify](gitsign_verify.md) - Verify a commit * [gitsign verify-tag](gitsign_verify-tag.md) - Verify a tag * [gitsign version](gitsign_version.md) - print Gitsign version gitsign-0.13.0/docs/cli/gitsign_attest.md000066400000000000000000000006401477253552300203250ustar00rootroot00000000000000## gitsign attest add attestations to Git objects ``` gitsign attest [flags] ``` ### Options ``` -f, --filepath string attestation filepath -h, --help help for attest --objtype string [commit | tree] - Git object type to attest (default "commit") --type string specify a predicate type URI ``` ### SEE ALSO * [gitsign](gitsign.md) - Keyless Git signing with Sigstore! gitsign-0.13.0/docs/cli/gitsign_initialize.md000066400000000000000000000032321477253552300211620ustar00rootroot00000000000000## gitsign initialize Initializes Sigstore root to retrieve trusted certificate and key targets for verification. ### Synopsis Initializes Sigstore root to retrieve trusted certificate and key targets for verification. The following options are used by default: - The current trusted Sigstore TUF root is embedded inside gitsign at the time of release. - Sigstore remote TUF repository is pulled from the CDN mirror at tuf-repo-cdn.sigstore.dev. To provide an out-of-band trusted initial root.json, use the -root flag with a file or URL reference. This will enable you to point gitsign to a separate TUF root. Any updated TUF repository will be written to $HOME/.sigstore/root/. Trusted keys and certificate used in gitsign verification (e.g. verifying Fulcio issued certificates with Fulcio root CA) are pulled form the trusted metadata. ``` gitsign initialize [flags] ``` ### Examples ``` gitsign initialize -mirror -out # initialize root with distributed root keys, default mirror, and default out path. gitsign initialize # initialize with an out-of-band root key file, using the default mirror. gitsign initialize -root # initialize with an out-of-band root key file and custom repository mirror. gitsign initialize -mirror -root ``` ### Options ``` -h, --help help for initialize --mirror string GCS bucket to a Sigstore TUF repository, or HTTP(S) base URL, or file:/// for local filestore remote (air-gap) (default "https://tuf-repo-cdn.sigstore.dev") --root string path to trusted initial root. defaults to embedded root ``` ### SEE ALSO * [gitsign](gitsign.md) - Keyless Git signing with Sigstore! gitsign-0.13.0/docs/cli/gitsign_show.md000066400000000000000000000010211477253552300177730ustar00rootroot00000000000000## gitsign show Show source predicate information ### Synopsis Show source predicate information Prints an in-toto style predicate for the specified revision. If no revision is specified, HEAD is used. This command is experimental, and its CLI surface may change. ``` gitsign show [revision] [flags] ``` ### Options ``` -h, --help help for show -r, --remote string git remote (used to populate subject) (default "origin") ``` ### SEE ALSO * [gitsign](gitsign.md) - Keyless Git signing with Sigstore! gitsign-0.13.0/docs/cli/gitsign_verify-tag.md000066400000000000000000000056731477253552300211110ustar00rootroot00000000000000## gitsign verify-tag Verify a tag ### Synopsis Verify a tag. verify-tag verifies a tag against a set of certificate claims. This should generally be used over git verify-tag, since verify-tag will check the identity included in the signature's certificate. ``` gitsign verify-tag [flags] ``` ### Options ``` --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. -h, --help help for verify-tag --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 --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. ``` ### SEE ALSO * [gitsign](gitsign.md) - Keyless Git signing with Sigstore! gitsign-0.13.0/docs/cli/gitsign_verify.md000066400000000000000000000057421477253552300203350ustar00rootroot00000000000000## gitsign verify Verify a commit ### Synopsis Verify a commit. verify verifies a commit against a set of certificate claims. This should generally be used over git verify-commit, since verify will check the identity included in the signature's certificate. If no revision is specified, HEAD is used. ``` gitsign verify [commit] [flags] ``` ### Options ``` --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. -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 --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. ``` ### SEE ALSO * [gitsign](gitsign.md) - Keyless Git signing with Sigstore! gitsign-0.13.0/docs/cli/gitsign_version.md000066400000000000000000000003171477253552300205070ustar00rootroot00000000000000## gitsign version print Gitsign version ``` gitsign version [flags] ``` ### Options ``` -h, --help help for version ``` ### SEE ALSO * [gitsign](gitsign.md) - Keyless Git signing with Sigstore! gitsign-0.13.0/docs/cli/main.go000066400000000000000000000022531477253552300162300ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:generate go run . package main import ( "fmt" "os" "github.com/sigstore/gitsign/internal/commands/root" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" ) var ( dir string cmd = &cobra.Command{ Use: "gendoc", Short: "Generate help docs", Args: cobra.NoArgs, RunE: func(*cobra.Command, []string) error { return doc.GenMarkdownTree(root.New(nil), dir) }, } ) func init() { cmd.Flags().StringVarP(&dir, "dir", "d", ".", "Path to directory in which to generate docs") } func main() { if err := cmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } gitsign-0.13.0/docs/committer-verification.md000066400000000000000000000026471477253552300212220ustar00rootroot00000000000000# Committer Verification Gitsign can be optionally configured to verify that the committer user identity matches the git user configuration (i.e. `user.name` and `user.email`) To enable committer verification, run `git config gitsign.matchCommitter true`. Committer verification is done by matching the certificate Subject Alternative Name (SAN) against the issued Fulcio certificate in the following order: 1. An `EmailAddresses` cert value matches the committer `user.email`. This should be used for most human committer verification. 2. A `URI` cert value matches the committer `user.name`. This should be used for most automated user committer verification. In the event that multiple SAN values are provided, verification will succeed if at least one value matches. ## Configuring Automated Users If running in an environment where the authenticated user does **not** have a user email to bind against (i.e. GitHub Actions, other workload identity workflows), users are expected to be identified by the SAN URI instead. See https://github.com/sigstore/fulcio/blob/main/docs/oidc.md for more details ### GitHub Actions ```sh # This configures the SAN URI for the expected identity in the Fulcio cert. $ git config user.name "https://myorg/myrepo/path/to/workflow" # This configures GitHub UI to recognize the commit as coming from a GitHub Action. $ git config user.email 1234567890+github-actions@users.noreply.github.com ``` gitsign-0.13.0/docs/timestamp.md000066400000000000000000000733611477253552300165430ustar00rootroot00000000000000# Timestamping Gitsign includes support for signing commits with a [RFC 3161 timestamping authority (TSA)](https://datatracker.ietf.org/doc/html/rfc3161). #### Sign To use a TSA during signing, set the `timestampURL` config option (one-time setup) to the RFC 3161 URL to use. For example, using the [Digicert TSA](https://knowledge.digicert.com/generalinformation/INFO4231.html): ```sh $ git config --local gitsign.timestampURL http://timestamp.digicert.com $ git commit ``` #### Verify By default, Gitsign will use your system certificate pool to verify TSA signatures. To specify additional certificates to use for verification, set the `timestampCert` config option to the path containing a PEM-encoded TSA certificate chain. ```sh $ git config --local gitsign.timestampCert tsa.pem $ git verify-commit head tlog index: 8031421 gitsign: Signature made using certificate ID 0xe615fa467ce0aaae5f81f1965dd19e89f859f24a | CN=sigstore-intermediate,O=sigstore.dev gitsign: Good signature from [billy@chainguard.dev] Validated Git signature: true Validated Rekor entry: true ```
Sample Signature ``` PKCS7: type: pkcs7-signedData (1.2.840.113549.1.7.2) d.sign: version: 1 md_algs: algorithm: sha256 (2.16.840.1.101.3.4.2.1) parameter: contents: type: pkcs7-data (1.2.840.113549.1.7.1) d.data: cert: cert_info: version: 2 serialNumber: 0x0DF7625EECD9A10EB6290E515E1931EAE3AD770C signature: algorithm: ecdsa-with-SHA384 (1.2.840.10045.4.3.3) parameter: issuer: O=sigstore.dev, CN=sigstore-intermediate validity: notBefore: Nov 28 18:34:56 2022 GMT notAfter: Nov 28 18:44:56 2022 GMT subject: key: algor: algorithm: id-ecPublicKey (1.2.840.10045.2.1) parameter: OBJECT:prime256v1 (1.2.840.10045.3.1.7) public_key: (0 unused bits) 0000 - 04 c3 23 27 2b 1d 8d 28-ef b5 2b 43 7d fa ..#'+..(..+C}. 000e - 2d 3e cc 4d a4 9b ee 29-cf 68 3e 20 e1 ce ->.M...).h> .. 001c - a5 c8 f4 89 53 57 aa 63-8f 09 da a6 60 88 ....SW.c....`. 002a - 8e 1b 55 33 77 a7 aa 1b-0f a7 92 73 5c 80 ..U3w......s\. 0038 - c3 f8 b7 f2 d9 0b 1a 68-bd .......h. issuerUID: subjectUID: extensions: object: X509v3 Key Usage (2.5.29.15) critical: TRUE value: 0000 - 03 02 07 80 .... object: X509v3 Extended Key Usage (2.5.29.37) critical: BOOL ABSENT value: 0000 - 30 0a 06 08 2b 06 01 05-05 07 03 03 0...+....... object: X509v3 Subject Key Identifier (2.5.29.14) critical: BOOL ABSENT value: 0000 - 04 14 9c 25 58 18 16 a0-ae 74 77 51 93 ...%X....twQ. 000d - fb 6e 63 55 cf 00 a9 24-7f .ncU...$. object: X509v3 Authority Key Identifier (2.5.29.35) critical: BOOL ABSENT value: 0000 - 30 16 80 14 df d3 e9 cf-56 24 11 96 f9 0.......V$... 000d - a8 d8 e9 28 55 a2 c6 2e-18 64 3f ...(U....d? object: X509v3 Subject Alternative Name (2.5.29.17) critical: TRUE value: 0000 - 30 16 81 14 62 69 6c 6c-79 40 63 68 61 0...billy@cha 000d - 69 6e 67 75 61 72 64 2e-64 65 76 inguard.dev object: undefined (1.3.6.1.4.1.57264.1.1) critical: BOOL ABSENT value: 0000 - 68 74 74 70 73 3a 2f 2f-61 63 63 6f 75 https://accou 000d - 6e 74 73 2e 67 6f 6f 67-6c 65 2e 63 6f nts.google.co 001a - 6d m object: undefined (1.3.6.1.4.1.11129.2.4.2) critical: BOOL ABSENT value: 0000 - 04 7a 00 78 00 76 00 dd-3d 30 6a c6 c7 .z.x.v..=0j.. 000d - 11 32 63 19 1e 1c 99 67-37 02 a2 4a 5e .2c....g7..J^ 001a - b8 de 3c ad ff 87 8a 72-80 2f 29 ee 8e ..<....r./).. 0027 - 00 00 01 84 bf 85 54 75-00 00 04 03 00 ......Tu..... 0034 - 47 30 45 02 21 00 ca ea-6a 46 60 ff 87 G0E.!...jF`.. 0041 - 36 2a ec 6c 8d 81 ae 61-a4 83 78 96 59 6*.l...a..x.Y 004e - b0 57 e3 27 b4 35 8d 49-dd 53 9f 52 02 .W.'.5.I.S.R. 005b - 20 67 8c b5 4a 35 2c 67-d3 1d db ba 42 g..J5,g....B 0068 - 09 0d a8 24 e4 65 c1 68-f9 6f 74 25 d9 ...$.e.h.ot%. 0075 - 6b 3b eb a3 c2 fe e6 k;..... sig_alg: algorithm: ecdsa-with-SHA384 (1.2.840.10045.4.3.3) parameter: signature: (0 unused bits) 0000 - 30 66 02 31 00 99 63 90-80 70 11 6a 56 26 57 0f.1..c..p.jV&W 000f - 27 3b d8 6b 62 ce 64 88-68 fb 00 01 72 11 f6 ';.kb.d.h...r.. 001e - 33 eb f6 28 c5 b8 5c 15-6e 9e 4a 47 84 d4 24 3..(..\.n.JG..$ 002d - f4 ad fe e5 36 d4 fa 30-02 31 00 d2 81 e0 5b ....6..0.1....[ 003c - 00 bb c3 8b 0a 3f e2 df-01 47 1c 1a 69 4a 70 .....?...G..iJp 004b - d7 83 74 60 b8 77 73 e2-11 b0 93 79 45 8a cc ..t`.ws....yE.. 005a - 99 41 0e fb e5 f3 1b cc-7d 5a f6 c2 f5 3b .A......}Z...; crl: signer_info: version: 1 issuer_and_serial: issuer: O=sigstore.dev, CN=sigstore-intermediate serial: 0x0DF7625EECD9A10EB6290E515E1931EAE3AD770C digest_alg: algorithm: sha256 (2.16.840.1.101.3.4.2.1) parameter: auth_attr: object: contentType (1.2.840.113549.1.9.3) value.set: OBJECT:pkcs7-data (1.2.840.113549.1.7.1) object: signingTime (1.2.840.113549.1.9.5) value.set: UTCTIME:Nov 28 18:34:57 2022 GMT object: messageDigest (1.2.840.113549.1.9.4) value.set: OCTET STRING: 0000 - a7 50 00 ac c9 64 0f fc-da 55 46 d0 be .P...d...UF.. 000d - 79 66 f1 e1 68 e2 93 96-95 8d 19 4c f2 yf..h......L. 001a - 89 44 9c 61 ee 32 .D.a.2 digest_enc_alg: algorithm: ecdsa-with-SHA256 (1.2.840.10045.4.3.2) parameter: enc_digest: 0000 - 30 46 02 21 00 cf 9e a9-96 f8 1d 47 38 0b 40 0F.!.......G8.@ 000f - 07 1e d0 16 24 98 da d0-e3 81 9a af e9 87 63 ....$.........c 001e - 4d 6d c1 64 b8 9f 08 02-21 00 e5 b6 d0 49 c5 Mm.d....!....I. 002d - 55 a7 e0 d7 3b 2c 8e 69-68 ae 86 d3 a9 fa 66 U...;,.ih.....f 003c - 1c 90 2c fc 74 72 d4 9e-be 72 48 63 ..,.tr...rHc unauth_attr: object: id-smime-aa-timeStampToken (1.2.840.113549.1.9.16.2.14) value.set: SEQUENCE: 0:d=0 hl=4 l=5946 cons: SEQUENCE 4:d=1 hl=2 l= 9 prim: OBJECT :pkcs7-signedData 15:d=1 hl=4 l=5931 cons: cont [ 0 ] 19:d=2 hl=4 l=5927 cons: SEQUENCE 23:d=3 hl=2 l= 1 prim: INTEGER :03 26:d=3 hl=2 l= 15 cons: SET 28:d=4 hl=2 l= 13 cons: SEQUENCE 30:d=5 hl=2 l= 9 prim: OBJECT :sha256 41:d=5 hl=2 l= 0 prim: NULL 43:d=3 hl=3 l= 139 cons: SEQUENCE 46:d=4 hl=2 l= 11 prim: OBJECT :id-smime-ct-TSTInfo 59:d=4 hl=2 l= 124 cons: cont [ 0 ] 61:d=5 hl=2 l= 122 prim: OCTET STRING [HEX DUMP]:307802010106096086480186FD6C07013031300D06096086480165030402010500042044EA0D73B5310D94F1698A184F5BADE0A7EC3810049CCA7721792E97241321C6021100D94C446A7368BF029F9C984726957C89180F32303232313132383138333435375A021100F3C335CD7279074B937BC50A9CEEF149 185:d=3 hl=4 l=4871 cons: cont [ 0 ] 189:d=4 hl=4 l=1728 cons: SEQUENCE 193:d=5 hl=4 l=1192 cons: SEQUENCE 197:d=6 hl=2 l= 3 cons: cont [ 0 ] 199:d=7 hl=2 l= 1 prim: INTEGER :02 202:d=6 hl=2 l= 16 prim: INTEGER :0C4D69724B94FA3C2A4A3D2907803D5A 220:d=6 hl=2 l= 13 cons: SEQUENCE 222:d=7 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption 233:d=7 hl=2 l= 0 prim: NULL 235:d=6 hl=2 l= 99 cons: SEQUENCE 237:d=7 hl=2 l= 11 cons: SET 239:d=8 hl=2 l= 9 cons: SEQUENCE 241:d=9 hl=2 l= 3 prim: OBJECT :countryName 246:d=9 hl=2 l= 2 prim: PRINTABLESTRING :US 250:d=7 hl=2 l= 23 cons: SET 252:d=8 hl=2 l= 21 cons: SEQUENCE 254:d=9 hl=2 l= 3 prim: OBJECT :organizationName 259:d=9 hl=2 l= 14 prim: PRINTABLESTRING :DigiCert, Inc. 275:d=7 hl=2 l= 59 cons: SET 277:d=8 hl=2 l= 57 cons: SEQUENCE 279:d=9 hl=2 l= 3 prim: OBJECT :commonName 284:d=9 hl=2 l= 50 prim: PRINTABLESTRING :DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA 336:d=6 hl=2 l= 30 cons: SEQUENCE 338:d=7 hl=2 l= 13 prim: UTCTIME :220921000000Z 353:d=7 hl=2 l= 13 prim: UTCTIME :331121235959Z 368:d=6 hl=2 l= 70 cons: SEQUENCE 370:d=7 hl=2 l= 11 cons: SET 372:d=8 hl=2 l= 9 cons: SEQUENCE 374:d=9 hl=2 l= 3 prim: OBJECT :countryName 379:d=9 hl=2 l= 2 prim: PRINTABLESTRING :US 383:d=7 hl=2 l= 17 cons: SET 385:d=8 hl=2 l= 15 cons: SEQUENCE 387:d=9 hl=2 l= 3 prim: OBJECT :organizationName 392:d=9 hl=2 l= 8 prim: PRINTABLESTRING :DigiCert 402:d=7 hl=2 l= 36 cons: SET 404:d=8 hl=2 l= 34 cons: SEQUENCE 406:d=9 hl=2 l= 3 prim: OBJECT :commonName 411:d=9 hl=2 l= 27 prim: PRINTABLESTRING :DigiCert Timestamp 2022 - 2 440:d=6 hl=4 l= 546 cons: SEQUENCE 444:d=7 hl=2 l= 13 cons: SEQUENCE 446:d=8 hl=2 l= 9 prim: OBJECT :rsaEncryption 457:d=8 hl=2 l= 0 prim: NULL 459:d=7 hl=4 l= 527 prim: BIT STRING 990:d=6 hl=4 l= 395 cons: cont [ 3 ] 994:d=7 hl=4 l= 391 cons: SEQUENCE 998:d=8 hl=2 l= 14 cons: SEQUENCE 1000:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Key Usage 1005:d=9 hl=2 l= 1 prim: BOOLEAN :255 1008:d=9 hl=2 l= 4 prim: OCTET STRING [HEX DUMP]:03020780 1014:d=8 hl=2 l= 12 cons: SEQUENCE 1016:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Basic Constraints 1021:d=9 hl=2 l= 1 prim: BOOLEAN :255 1024:d=9 hl=2 l= 2 prim: OCTET STRING [HEX DUMP]:3000 1028:d=8 hl=2 l= 22 cons: SEQUENCE 1030:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Extended Key Usage 1035:d=9 hl=2 l= 1 prim: BOOLEAN :255 1038:d=9 hl=2 l= 12 prim: OCTET STRING [HEX DUMP]:300A06082B06010505070308 1052:d=8 hl=2 l= 32 cons: SEQUENCE 1054:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Certificate Policies 1059:d=9 hl=2 l= 25 prim: OCTET STRING [HEX DUMP]:30173008060667810C010402300B06096086480186FD6C0701 1086:d=8 hl=2 l= 31 cons: SEQUENCE 1088:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Authority Key Identifier 1093:d=9 hl=2 l= 24 prim: OCTET STRING [HEX DUMP]:30168014BA16D96D4D852F7329769A2F758C6A208F9EC86F 1119:d=8 hl=2 l= 29 cons: SEQUENCE 1121:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Subject Key Identifier 1126:d=9 hl=2 l= 22 prim: OCTET STRING [HEX DUMP]:0414628ADED061FC8F3114ED970BCD3D2A9414DF529C 1150:d=8 hl=2 l= 90 cons: SEQUENCE 1152:d=9 hl=2 l= 3 prim: OBJECT :X509v3 CRL Distribution Points 1157:d=9 hl=2 l= 83 prim: OCTET STRING [HEX DUMP]:3051304FA04DA04B8649687474703A2F2F63726C332E64696769636572742E636F6D2F44696769436572745472757374656447345253413430393653484132353654696D655374616D70696E6743412E63726C 1242:d=8 hl=3 l= 144 cons: SEQUENCE 1245:d=9 hl=2 l= 8 prim: OBJECT :Authority Information Access 1255:d=9 hl=3 l= 131 prim: OCTET STRING [HEX DUMP]:308180302406082B060105050730018618687474703A2F2F6F6373702E64696769636572742E636F6D305806082B06010505073002864C687474703A2F2F636163657274732E64696769636572742E636F6D2F44696769436572745472757374656447345253413430393653484132353654696D655374616D70696E6743412E637274 1389:d=5 hl=2 l= 13 cons: SEQUENCE 1391:d=6 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption 1402:d=6 hl=2 l= 0 prim: NULL 1404:d=5 hl=4 l= 513 prim: BIT STRING 1921:d=4 hl=4 l=1710 cons: SEQUENCE 1925:d=5 hl=4 l=1174 cons: SEQUENCE 1929:d=6 hl=2 l= 3 cons: cont [ 0 ] 1931:d=7 hl=2 l= 1 prim: INTEGER :02 1934:d=6 hl=2 l= 16 prim: INTEGER :073637B724547CD847ACFD28662A5E5B 1952:d=6 hl=2 l= 13 cons: SEQUENCE 1954:d=7 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption 1965:d=7 hl=2 l= 0 prim: NULL 1967:d=6 hl=2 l= 98 cons: SEQUENCE 1969:d=7 hl=2 l= 11 cons: SET 1971:d=8 hl=2 l= 9 cons: SEQUENCE 1973:d=9 hl=2 l= 3 prim: OBJECT :countryName 1978:d=9 hl=2 l= 2 prim: PRINTABLESTRING :US 1982:d=7 hl=2 l= 21 cons: SET 1984:d=8 hl=2 l= 19 cons: SEQUENCE 1986:d=9 hl=2 l= 3 prim: OBJECT :organizationName 1991:d=9 hl=2 l= 12 prim: PRINTABLESTRING :DigiCert Inc 2005:d=7 hl=2 l= 25 cons: SET 2007:d=8 hl=2 l= 23 cons: SEQUENCE 2009:d=9 hl=2 l= 3 prim: OBJECT :organizationalUnitName 2014:d=9 hl=2 l= 16 prim: PRINTABLESTRING :www.digicert.com 2032:d=7 hl=2 l= 33 cons: SET 2034:d=8 hl=2 l= 31 cons: SEQUENCE 2036:d=9 hl=2 l= 3 prim: OBJECT :commonName 2041:d=9 hl=2 l= 24 prim: PRINTABLESTRING :DigiCert Trusted Root G4 2067:d=6 hl=2 l= 30 cons: SEQUENCE 2069:d=7 hl=2 l= 13 prim: UTCTIME :220323000000Z 2084:d=7 hl=2 l= 13 prim: UTCTIME :370322235959Z 2099:d=6 hl=2 l= 99 cons: SEQUENCE 2101:d=7 hl=2 l= 11 cons: SET 2103:d=8 hl=2 l= 9 cons: SEQUENCE 2105:d=9 hl=2 l= 3 prim: OBJECT :countryName 2110:d=9 hl=2 l= 2 prim: PRINTABLESTRING :US 2114:d=7 hl=2 l= 23 cons: SET 2116:d=8 hl=2 l= 21 cons: SEQUENCE 2118:d=9 hl=2 l= 3 prim: OBJECT :organizationName 2123:d=9 hl=2 l= 14 prim: PRINTABLESTRING :DigiCert, Inc. 2139:d=7 hl=2 l= 59 cons: SET 2141:d=8 hl=2 l= 57 cons: SEQUENCE 2143:d=9 hl=2 l= 3 prim: OBJECT :commonName 2148:d=9 hl=2 l= 50 prim: PRINTABLESTRING :DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA 2200:d=6 hl=4 l= 546 cons: SEQUENCE 2204:d=7 hl=2 l= 13 cons: SEQUENCE 2206:d=8 hl=2 l= 9 prim: OBJECT :rsaEncryption 2217:d=8 hl=2 l= 0 prim: NULL 2219:d=7 hl=4 l= 527 prim: BIT STRING 2750:d=6 hl=4 l= 349 cons: cont [ 3 ] 2754:d=7 hl=4 l= 345 cons: SEQUENCE 2758:d=8 hl=2 l= 18 cons: SEQUENCE 2760:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Basic Constraints 2765:d=9 hl=2 l= 1 prim: BOOLEAN :255 2768:d=9 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:30060101FF020100 2778:d=8 hl=2 l= 29 cons: SEQUENCE 2780:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Subject Key Identifier 2785:d=9 hl=2 l= 22 prim: OCTET STRING [HEX DUMP]:0414BA16D96D4D852F7329769A2F758C6A208F9EC86F 2809:d=8 hl=2 l= 31 cons: SEQUENCE 2811:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Authority Key Identifier 2816:d=9 hl=2 l= 24 prim: OCTET STRING [HEX DUMP]:30168014ECD7E382D2715D644CDF2E673FE7BA98AE1C0F4F 2842:d=8 hl=2 l= 14 cons: SEQUENCE 2844:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Key Usage 2849:d=9 hl=2 l= 1 prim: BOOLEAN :255 2852:d=9 hl=2 l= 4 prim: OCTET STRING [HEX DUMP]:03020186 2858:d=8 hl=2 l= 19 cons: SEQUENCE 2860:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Extended Key Usage 2865:d=9 hl=2 l= 12 prim: OCTET STRING [HEX DUMP]:300A06082B06010505070308 2879:d=8 hl=2 l= 119 cons: SEQUENCE 2881:d=9 hl=2 l= 8 prim: OBJECT :Authority Information Access 2891:d=9 hl=2 l= 107 prim: OCTET STRING [HEX DUMP]:3069302406082B060105050730018618687474703A2F2F6F6373702E64696769636572742E636F6D304106082B060105050730028635687474703A2F2F636163657274732E64696769636572742E636F6D2F446967694365727454727573746564526F6F7447342E637274 3000:d=8 hl=2 l= 67 cons: SEQUENCE 3002:d=9 hl=2 l= 3 prim: OBJECT :X509v3 CRL Distribution Points 3007:d=9 hl=2 l= 60 prim: OCTET STRING [HEX DUMP]:303A3038A036A0348632687474703A2F2F63726C332E64696769636572742E636F6D2F446967694365727454727573746564526F6F7447342E63726C 3069:d=8 hl=2 l= 32 cons: SEQUENCE 3071:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Certificate Policies 3076:d=9 hl=2 l= 25 prim: OCTET STRING [HEX DUMP]:30173008060667810C010402300B06096086480186FD6C0701 3103:d=5 hl=2 l= 13 cons: SEQUENCE 3105:d=6 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption 3116:d=6 hl=2 l= 0 prim: NULL 3118:d=5 hl=4 l= 513 prim: BIT STRING 3635:d=4 hl=4 l=1421 cons: SEQUENCE 3639:d=5 hl=4 l=1141 cons: SEQUENCE 3643:d=6 hl=2 l= 3 cons: cont [ 0 ] 3645:d=7 hl=2 l= 1 prim: INTEGER :02 3648:d=6 hl=2 l= 16 prim: INTEGER :0E9B188EF9D02DE7EFDB50E20840185A 3666:d=6 hl=2 l= 13 cons: SEQUENCE 3668:d=7 hl=2 l= 9 prim: OBJECT :sha384WithRSAEncryption 3679:d=7 hl=2 l= 0 prim: NULL 3681:d=6 hl=2 l= 101 cons: SEQUENCE 3683:d=7 hl=2 l= 11 cons: SET 3685:d=8 hl=2 l= 9 cons: SEQUENCE 3687:d=9 hl=2 l= 3 prim: OBJECT :countryName 3692:d=9 hl=2 l= 2 prim: PRINTABLESTRING :US 3696:d=7 hl=2 l= 21 cons: SET 3698:d=8 hl=2 l= 19 cons: SEQUENCE 3700:d=9 hl=2 l= 3 prim: OBJECT :organizationName 3705:d=9 hl=2 l= 12 prim: PRINTABLESTRING :DigiCert Inc 3719:d=7 hl=2 l= 25 cons: SET 3721:d=8 hl=2 l= 23 cons: SEQUENCE 3723:d=9 hl=2 l= 3 prim: OBJECT :organizationalUnitName 3728:d=9 hl=2 l= 16 prim: PRINTABLESTRING :www.digicert.com 3746:d=7 hl=2 l= 36 cons: SET 3748:d=8 hl=2 l= 34 cons: SEQUENCE 3750:d=9 hl=2 l= 3 prim: OBJECT :commonName 3755:d=9 hl=2 l= 27 prim: PRINTABLESTRING :DigiCert Assured ID Root CA 3784:d=6 hl=2 l= 30 cons: SEQUENCE 3786:d=7 hl=2 l= 13 prim: UTCTIME :220801000000Z 3801:d=7 hl=2 l= 13 prim: UTCTIME :311109235959Z 3816:d=6 hl=2 l= 98 cons: SEQUENCE 3818:d=7 hl=2 l= 11 cons: SET 3820:d=8 hl=2 l= 9 cons: SEQUENCE 3822:d=9 hl=2 l= 3 prim: OBJECT :countryName 3827:d=9 hl=2 l= 2 prim: PRINTABLESTRING :US 3831:d=7 hl=2 l= 21 cons: SET 3833:d=8 hl=2 l= 19 cons: SEQUENCE 3835:d=9 hl=2 l= 3 prim: OBJECT :organizationName 3840:d=9 hl=2 l= 12 prim: PRINTABLESTRING :DigiCert Inc 3854:d=7 hl=2 l= 25 cons: SET 3856:d=8 hl=2 l= 23 cons: SEQUENCE 3858:d=9 hl=2 l= 3 prim: OBJECT :organizationalUnitName 3863:d=9 hl=2 l= 16 prim: PRINTABLESTRING :www.digicert.com 3881:d=7 hl=2 l= 33 cons: SET 3883:d=8 hl=2 l= 31 cons: SEQUENCE 3885:d=9 hl=2 l= 3 prim: OBJECT :commonName 3890:d=9 hl=2 l= 24 prim: PRINTABLESTRING :DigiCert Trusted Root G4 3916:d=6 hl=4 l= 546 cons: SEQUENCE 3920:d=7 hl=2 l= 13 cons: SEQUENCE 3922:d=8 hl=2 l= 9 prim: OBJECT :rsaEncryption 3933:d=8 hl=2 l= 0 prim: NULL 3935:d=7 hl=4 l= 527 prim: BIT STRING 4466:d=6 hl=4 l= 314 cons: cont [ 3 ] 4470:d=7 hl=4 l= 310 cons: SEQUENCE 4474:d=8 hl=2 l= 15 cons: SEQUENCE 4476:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Basic Constraints 4481:d=9 hl=2 l= 1 prim: BOOLEAN :255 4484:d=9 hl=2 l= 5 prim: OCTET STRING [HEX DUMP]:30030101FF 4491:d=8 hl=2 l= 29 cons: SEQUENCE 4493:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Subject Key Identifier 4498:d=9 hl=2 l= 22 prim: OCTET STRING [HEX DUMP]:0414ECD7E382D2715D644CDF2E673FE7BA98AE1C0F4F 4522:d=8 hl=2 l= 31 cons: SEQUENCE 4524:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Authority Key Identifier 4529:d=9 hl=2 l= 24 prim: OCTET STRING [HEX DUMP]:3016801445EBA2AFF492CB82312D518BA7A7219DF36DC80F 4555:d=8 hl=2 l= 14 cons: SEQUENCE 4557:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Key Usage 4562:d=9 hl=2 l= 1 prim: BOOLEAN :255 4565:d=9 hl=2 l= 4 prim: OCTET STRING [HEX DUMP]:03020186 4571:d=8 hl=2 l= 121 cons: SEQUENCE 4573:d=9 hl=2 l= 8 prim: OBJECT :Authority Information Access 4583:d=9 hl=2 l= 109 prim: OCTET STRING [HEX DUMP]:306B302406082B060105050730018618687474703A2F2F6F6373702E64696769636572742E636F6D304306082B060105050730028637687474703A2F2F636163657274732E64696769636572742E636F6D2F4469676943657274417373757265644944526F6F7443412E637274 4694:d=8 hl=2 l= 69 cons: SEQUENCE 4696:d=9 hl=2 l= 3 prim: OBJECT :X509v3 CRL Distribution Points 4701:d=9 hl=2 l= 62 prim: OCTET STRING [HEX DUMP]:303C303AA038A0368634687474703A2F2F63726C332E64696769636572742E636F6D2F4469676943657274417373757265644944526F6F7443412E63726C 4765:d=8 hl=2 l= 17 cons: SEQUENCE 4767:d=9 hl=2 l= 3 prim: OBJECT :X509v3 Certificate Policies 4772:d=9 hl=2 l= 10 prim: OCTET STRING [HEX DUMP]:300830060604551D2000 4784:d=5 hl=2 l= 13 cons: SEQUENCE 4786:d=6 hl=2 l= 9 prim: OBJECT :sha384WithRSAEncryption 4797:d=6 hl=2 l= 0 prim: NULL 4799:d=5 hl=4 l= 257 prim: BIT STRING 5060:d=3 hl=4 l= 886 cons: SET 5064:d=4 hl=4 l= 882 cons: SEQUENCE 5068:d=5 hl=2 l= 1 prim: INTEGER :01 5071:d=5 hl=2 l= 119 cons: SEQUENCE 5073:d=6 hl=2 l= 99 cons: SEQUENCE 5075:d=7 hl=2 l= 11 cons: SET 5077:d=8 hl=2 l= 9 cons: SEQUENCE 5079:d=9 hl=2 l= 3 prim: OBJECT :countryName 5084:d=9 hl=2 l= 2 prim: PRINTABLESTRING :US 5088:d=7 hl=2 l= 23 cons: SET 5090:d=8 hl=2 l= 21 cons: SEQUENCE 5092:d=9 hl=2 l= 3 prim: OBJECT :organizationName 5097:d=9 hl=2 l= 14 prim: PRINTABLESTRING :DigiCert, Inc. 5113:d=7 hl=2 l= 59 cons: SET 5115:d=8 hl=2 l= 57 cons: SEQUENCE 5117:d=9 hl=2 l= 3 prim: OBJECT :commonName 5122:d=9 hl=2 l= 50 prim: PRINTABLESTRING :DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA 5174:d=6 hl=2 l= 16 prim: INTEGER :0C4D69724B94FA3C2A4A3D2907803D5A 5192:d=5 hl=2 l= 13 cons: SEQUENCE 5194:d=6 hl=2 l= 9 prim: OBJECT :sha256 5205:d=6 hl=2 l= 0 prim: NULL 5207:d=5 hl=3 l= 209 cons: cont [ 0 ] 5210:d=6 hl=2 l= 26 cons: SEQUENCE 5212:d=7 hl=2 l= 9 prim: OBJECT :contentType 5223:d=7 hl=2 l= 13 cons: SET 5225:d=8 hl=2 l= 11 prim: OBJECT :id-smime-ct-TSTInfo 5238:d=6 hl=2 l= 28 cons: SEQUENCE 5240:d=7 hl=2 l= 9 prim: OBJECT :signingTime 5251:d=7 hl=2 l= 15 cons: SET 5253:d=8 hl=2 l= 13 prim: UTCTIME :221128183457Z 5268:d=6 hl=2 l= 43 cons: SEQUENCE 5270:d=7 hl=2 l= 11 prim: OBJECT :id-smime-aa-signingCertificate 5283:d=7 hl=2 l= 28 cons: SET 5285:d=8 hl=2 l= 26 cons: SEQUENCE 5287:d=9 hl=2 l= 24 cons: SEQUENCE 5289:d=10 hl=2 l= 22 cons: SEQUENCE 5291:d=11 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:F387224D8633829235A994BCBD8F96E9FE1C7C73 5313:d=6 hl=2 l= 47 cons: SEQUENCE 5315:d=7 hl=2 l= 9 prim: OBJECT :messageDigest 5326:d=7 hl=2 l= 34 cons: SET 5328:d=8 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:7ABBAAE033F3BF47F633E1F842029C469CB04ABF495B66404E3823848CB94B3C 5362:d=6 hl=2 l= 55 cons: SEQUENCE 5364:d=7 hl=2 l= 11 prim: OBJECT :1.2.840.113549.1.9.16.2.47 5377:d=7 hl=2 l= 40 cons: SET 5379:d=8 hl=2 l= 38 cons: SEQUENCE 5381:d=9 hl=2 l= 36 cons: SEQUENCE 5383:d=10 hl=2 l= 34 cons: SEQUENCE 5385:d=11 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:C7F4E1BE32288920ABE2263ABE1AC4FC4FE6781C2D64D04C807557A023B5B6FA 5419:d=5 hl=2 l= 13 cons: SEQUENCE 5421:d=6 hl=2 l= 9 prim: OBJECT :rsaEncryption 5432:d=6 hl=2 l= 0 prim: NULL 5434:d=5 hl=4 l= 512 prim: OCTET STRING [HEX DUMP]:89FE4197E5B95156F97B5FB4998A45D78E3C8893158B934076DB2A31D0922CFBD9A6AA5550416501D1BF73196E55B1CF3F253102CFE248017D3763017FDE86FFD829C1635C08B97A72F13157C40380197668842F7BF047A10BA0BC3BAE6685C6CB305C00BFA594BA4DE69134FA43BDA851B06C9B50335C3D5D2BC195F824046A6F7E38E5F53CE945F30C5A08FC44165B96E7259E7CD231AB8DDFE39F3F50AD8CE633543676E7ED21A0991BD793917703F2138B1C6DD51E1BBDD7E5706DD3D982F007DB6D7EF9F06AE2474D31A2FB6FB046636160DA035B266765B15853454DE864D2D50087F1BBB8D52277DB49FF18B6CECEC0061441E721CC376E73808C7DE2CD833CCCCE7254C83FBF085B0D9551A7427D1A6919ECC9816764A53AC51EBA72B8C41A67B8FFC1E2714B80C6A5327781AB60A717C7A6638F18E8865FFE1918347B70DE905BB854EBC2CAC8F418C4920CA49BBD53B6CC46A61CF0BAB077400D9E98BAF0B992EFAF0879FD85A84FB15EFE3ECD7799AF9BB0837F00CB64251E07EF0CA390DC1E33E47DD5A04AF5C267042AABCC4932337E51C4224D33606C556D305E8F3125C675ECBF4D7500DC81068CD95C51E5E330AA5E366896CC7DF41323E976CC1B12444EB17DE7279886E58D0B924E2980767595F733F151FDBEF26FDF3BF512A094B90016C9FEFB22C767357AD8ABFEDBD2FBC7F8750B80E3979620D9D9 Certificate: Data: Version: 3 (0x2) Serial Number: 0d:f7:62:5e:ec:d9:a1:0e:b6:29:0e:51:5e:19:31:ea:e3:ad:77:0c Signature Algorithm: ecdsa-with-SHA384 Issuer: O=sigstore.dev, CN=sigstore-intermediate Validity Not Before: Nov 28 18:34:56 2022 GMT Not After : Nov 28 18:44:56 2022 GMT Subject: Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:c3:23:27:2b:1d:8d:28:ef:b5:2b:43:7d:fa:2d: 3e:cc:4d:a4:9b:ee:29:cf:68:3e:20:e1:ce:a5:c8: f4:89:53:57:aa:63:8f:09:da:a6:60:88:8e:1b:55: 33:77:a7:aa:1b:0f:a7:92:73:5c:80:c3:f8:b7:f2: d9:0b:1a:68:bd ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature X509v3 Extended Key Usage: Code Signing X509v3 Subject Key Identifier: 9C:25:58:18:16:A0:AE:74:77:51:93:FB:6E:63:55:CF:00:A9:24:7F X509v3 Authority Key Identifier: keyid:DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F X509v3 Subject Alternative Name: critical email:billy@chainguard.dev 1.3.6.1.4.1.57264.1.1: https://accounts.google.com 1.3.6.1.4.1.11129.2.4.2: .z.x.v..=0j...2c....g7..J^..<....r./)........Tu.....G0E.!...jF`..6*.l...a..x.Y.W.'.5.I.S.R. g..J5,g....B. .$.e.h.ot%.k;..... Signature Algorithm: ecdsa-with-SHA384 30:66:02:31:00:99:63:90:80:70:11:6a:56:26:57:27:3b:d8: 6b:62:ce:64:88:68:fb:00:01:72:11:f6:33:eb:f6:28:c5:b8: 5c:15:6e:9e:4a:47:84:d4:24:f4:ad:fe:e5:36:d4:fa:30:02: 31:00:d2:81:e0:5b:00:bb:c3:8b:0a:3f:e2:df:01:47:1c:1a: 69:4a:70:d7:83:74:60:b8:77:73:e2:11:b0:93:79:45:8a:cc: 99:41:0e:fb:e5:f3:1b:cc:7d:5a:f6:c2:f5:3b -----BEGIN CERTIFICATE----- MIICoTCCAiagAwIBAgIUDfdiXuzZoQ62KQ5RXhkx6uOtdwwwCgYIKoZIzj0EAwMw NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl cm1lZGlhdGUwHhcNMjIxMTI4MTgzNDU2WhcNMjIxMTI4MTg0NDU2WjAAMFkwEwYH KoZIzj0CAQYIKoZIzj0DAQcDQgAEwyMnKx2NKO+1K0N9+i0+zE2km+4pz2g+IOHO pcj0iVNXqmOPCdqmYIiOG1Uzd6eqGw+nknNcgMP4t/LZCxpovaOCAUUwggFBMA4G A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUnCVY GBagrnR3UZP7bmNVzwCpJH8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y ZD8wIgYDVR0RAQH/BBgwFoEUYmlsbHlAY2hhaW5ndWFyZC5kZXYwKQYKKwYBBAGD vzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgorBgEEAdZ5AgQC BHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGEv4VU dQAABAMARzBFAiEAyupqRmD/hzYq7GyNga5hpIN4llmwV+MntDWNSd1Tn1ICIGeM tUo1LGfTHdu6QgkNqCTkZcFo+W90JdlrO+ujwv7mMAoGCCqGSM49BAMDA2kAMGYC MQCZY5CAcBFqViZXJzvYa2LOZIho+wABchH2M+v2KMW4XBVunkpHhNQk9K3+5TbU +jACMQDSgeBbALvDiwo/4t8BRxwaaUpw14N0YLh3c+IRsJN5RYrMmUEO++XzG8x9 WvbC9Ts= -----END CERTIFICATE----- ```
gitsign-0.13.0/docs/verification.md000066400000000000000000000333751477253552300172230ustar00rootroot00000000000000# Verification ## Offline Verification ### How we sign In offline Rekor storage mode Gitsign will store a HashedRekord in Rekor corresponding to the commit or tag content. Unfortunately this is a bit complex to query manually. Roughly this is: ``` sha256(der(sort(system time | commit data | content type))) ``` The resulting Rekor log entry fields and inclusion proof will be stored in the PKCS7 object as unauthenticated (i.e. not included in the cryptographic signature) attributes. ### How we verify For commits: 1. Recompute and compare commit content checksum from commit. 2. Get Rekor LogEntry from signature. 3. Verify Certificate against commit content checksum (ignoring cert NotAfter time). 4. (if present) Verify signature against TSA cert. 5. Verify Rekor LogEntry inclusion (offline). For tags: 1. Recompute and compare tag content checksum from tag. 2. Get Rekor LogEntry from signature. 3. Verify Certificate against tag content checksum (ignoring cert NotAfter time). 4. (if present) Verify signature against TSA cert. 5. Verify Rekor LogEntry inclusion (offline). ### What's stored in the signature For commits: - Commit content checksum (sha256) - Commit signing time (untrusted system time) - Protobuf encoded [Rekor TransparencyLogEntry](https://github.com/sigstore/protobuf-specs/blob/91485b44360d343dadd98fb7297a500f05e0b5b1/protos/sigstore_rekor.proto#L91) - (optional) TSA signature + cert For tags: - Tag content checksum (sha256) - Tag signing time (untrusted system time) - Protobuf encoded [Rekor TransparencyLogEntry](https://github.com/sigstore/protobuf-specs/blob/91485b44360d343dadd98fb7297a500f05e0b5b1/protos/sigstore_rekor.proto#L91) - (optional) TSA signature + cert Sample encoded TransparencyLogEntry: ``` unauth_attr: object: Rekor TransparencyLogEntry proto (1.3.6.1.4.1.57264.3.1) value.set: OCTET STRING: 0000 - 08 af d5 d6 08 12 22 0a-20 c0 d2 3d 6a ......". ..=j 000d - d4 06 97 3f 95 59 f3 ba-2d 1c a0 1f 84 ...?.Y..-.... 001a - 14 7d 8f fc 5b 84 45 c2-24 f9 8b 95 91 .}..[.E.$.... 0027 - 80 1d 1a 15 0a 0c 68 61-73 68 65 64 72 ......hashedr 0034 - 65 6b 6f 72 64 12 05 30-2e 30 2e 31 20 ekord..0.0.1 0041 - a1 fc f5 a1 06 2a 49 0a-47 30 45 02 21 .....*I.G0E.! 004e - 00 fd ab 1a 0d 0b 39 fe-d5 0f f2 4d 87 ......9....M. 005b - 40 06 bd 2d 84 e8 ca d8-a2 39 99 e5 d9 @..-.....9... 0068 - 8a 3e b2 48 04 44 67 02-20 15 a5 02 7a .>.H.Dg. ...z 0075 - 61 0b d1 58 46 81 b1 ff-53 e8 46 be b3 a..XF...S.F.. 0082 - 70 9b f1 55 07 0c e8 32-bb 61 4e aa ce p..U...2.aN.. 008f - 61 16 32 81 05 08 c8 c6-d8 06 12 20 3f a.2........ ? 009c - 5f bc 03 da 94 4e 17 05-44 a8 c2 1b e9 _....N..D.... 00a9 - a7 6c 84 7d 39 66 4b 07-2f c2 7b 49 3d .l.}9fK./.{I= 00b6 - 2b da 9a 84 30 18 c9 c6-d8 06 22 20 34 +...0....." 4 00c3 - 8d 79 2a f5 5b 0d e8 8f-6e 6b 3f 39 8e .y*.[...nk?9. 00d0 - 43 02 2a d3 b3 c3 6b d5-d1 c6 84 cd 7f C.*...k...... 00dd - 08 24 2f a6 6e 22 20 64-47 c9 39 2b 77 .$/.n" dG.9+w 00ea - ba 3b b5 36 7f bd ea 8f-36 ef 32 33 14 .;.6....6.23. 00f7 - 2a e2 ec 2d 57 51 a6 4b-8f 00 59 d2 5e *..-WQ.K..Y.^ 0104 - 22 20 c0 d8 57 e5 d0 82-b2 b8 cf 26 b0 " ..W......&. 0111 - 58 e3 85 e5 71 ba 34 ab-5c 1b 49 5a 5e X...q.4.\.IZ^ 011e - c4 20 7b 7a 47 d6 02 0b-22 20 21 52 30 . {zG..." !R0 012b - e1 48 37 62 5c 39 56 bc-78 a6 84 d5 c3 .H7b\9V.x.... 0138 - df 3d ea e4 75 80 07 a3-25 b9 c9 42 e6 .=..u...%..B. 0145 - 34 8e 49 22 20 4a 88 54-e3 e8 ed dd f0 4.I" J.T..... 0152 - 4b f4 e2 95 55 da a8 44-be 87 85 e6 d9 K...U..D..... 015f - 57 52 8f 97 b3 3a d3 d7-96 32 f9 22 20 WR...:...2." 016c - 35 b2 b6 5b 9f 02 a8 bc-7d d2 f8 64 30 5..[....}..d0 0179 - d5 04 b1 c4 bb 2e 0c c8-bd 00 18 52 bb ...........R. 0186 - 40 ad 84 6c 2d 68 22 20-4c 82 cf f1 63 @..l-h" L...c 0193 - 90 df b5 b4 3a 8b 0f bf-04 43 3e 52 0e ....:....C>R. 01a0 - ef f6 d0 0e d3 c0 01 31-b1 8f 1b 68 82 .......1...h. 01ad - 74 22 20 ec 4c 65 15 56-3a 67 6a 41 1e t" .Le.V:gjA. 01ba - 44 ad 06 b2 df 2d ff da-2c 03 77 87 ee D....-..,.w.. 01c7 - ba 00 c9 5b c3 b5 34 59-55 22 20 d6 30 ...[..4YU" .0 01d4 - 92 c2 27 78 05 dc b4 cb-36 1b ea 6e 09 ..'x....6..n. 01e1 - ac 7e d9 e9 e9 19 27 24-b8 f5 1e 57 e5 .~....'$...W. 01ee - 4b df 35 31 22 20 9e 04-00 66 df e5 f0 K.51" ...f... 01fb - 20 04 65 83 86 ac 66 cf-0b b6 ff e8 57 .e...f.....W 0208 - ed 71 cb 33 7c 7f 55 45-ec f4 55 8b 2a .q.3|.UE..U.* 0215 - fe 01 0a fb 01 72 65 6b-6f 72 2e 73 69 .....rekor.si 0222 - 67 73 74 6f 72 65 2e 64-65 76 20 2d 20 gstore.dev - 022f - 32 36 30 35 37 33 36 36-37 30 39 37 32 2605736670972 023c - 37 39 34 37 34 36 0a 31-34 30 33 33 37 794746.140337 0249 - 33 37 0a 50 31 2b 38 41-39 71 55 54 68 37.P1+8A9qUTh 0256 - 63 46 52 4b 6a 43 47 2b-6d 6e 62 49 52 cFRKjCG+mnbIR 0263 - 39 4f 57 5a 4c 42 79 2f-43 65 30 6b 39 9OWZLBy/Ce0k9 0270 - 4b 39 71 61 68 44 41 3d-0a 54 69 6d 65 K9qahDA=.Time 027d - 73 74 61 6d 70 3a 20 31-36 38 31 37 35 stamp: 168175 028a - 31 35 38 35 32 37 34 35-37 36 37 30 31 1585274576701 0297 - 0a 0a e2 80 94 20 72 65-6b 6f 72 2e 73 ..... rekor.s 02a4 - 69 67 73 74 6f 72 65 2e-64 65 76 20 77 igstore.dev w 02b1 - 4e 49 39 61 6a 42 45 41-69 42 31 56 4a NI9ajBEAiB1VJ 02be - 48 46 6e 34 47 4e 63 32-65 38 65 42 78 HFn4GNc2e8eBx 02cb - 48 6f 4b 41 6c 56 6f 77-44 77 4a 51 72 HoKAlVowDwJQr 02d8 - 34 32 53 50 56 37 64 2f-6e 72 73 47 34 42SPV7d/nrsG4 02e5 - 77 49 67 4c 49 73 36 77-2b 59 75 39 42 wIgLIs6w+Yu9B 02f2 - 2f 35 2b 73 6b 6e 72 51-65 36 58 33 72 /5+sknrQe6X3r 02ff - 68 6e 6b 41 65 6a 6d 76-55 6d 4d 5a 5a hnkAejmvUmMZZ 030c - 69 51 75 4d 53 49 59 3d-0a iQuMSIY=. ``` This OID are defined by [Rekor](https://github.com/sigstore/rekor) and are used during verification to reconstruct the Rekor log entry and verify the commit signature. ### What's stored in Rekor For commits, a HashedRekord containing: - Commit content checksum - Fulcio certificate - Public Key - [Signer Identity info](https://github.com/sigstore/fulcio/blob/main/docs/oidc.md) For tags, a HashedRekord containing: - Tag content checksum - Fulcio certificate - Public Key - [Signer Identity info](https://github.com/sigstore/fulcio/blob/main/docs/oidc.md) ## Online Verification Note: Gitsign is in the process of migrating clients to offline verification, but this section explains how verification used to work. ### How we sign In online Rekor storage mode Gitsign will store the Git commit SHA in Rekor rather that persisting the Rekor log details in the commit itself. This works by: 1. Get Fulcio Cert 2. Sign the commit body using cert 3. Generate commit SHA (commit doesn't actually exist yet because the commit includes the signature) 4. Sign the commit SHA using the same cert 5. Upload HashedRekord of commit SHA to Rekor 6. Store the signed commit body signature in commit ### How we verify As part of signature verification, `gitsign` not only checks that the given signature matches the commit, but also that the commit exists within the Rekor transparency log. This is done by: 1. Recompute and compare commit content checksum from commit. 2. Validate the checksum signature using the public key in the signature's cert (ignoring cert NotAfter time). 3. (if present) Verify signature against TSA cert. 4. Search Rekor for an entry matching the commit SHA + cert. (this is what makes the process online) 5. Verify Rekor LogEntry inclusion (offline). We can manually validate that the commit exists in the transparency log by running: ```sh $ uuid=$(rekor-cli search --artifact <(git rev-parse HEAD | tr -d '\n') | tail -n 1) $ rekor-cli get --uuid=$uuid --format=json | jq . LogID: c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d Index: 2212633 IntegratedTime: 2022-05-02T20:51:49Z UUID: d0444ed9897f31fefc820ade9a706188a3bb030055421c91e64475a8c955ae2c Body: { "HashedRekordObj": { "data": { "hash": { "algorithm": "sha256", "value": "05b4f02a24d1c4c2c95dacaee30de2a6ce4b5b88fa981f4e7b456b76ea103141" } }, "signature": { "content": "MEYCIQCeZwhnq9dgS7ZvU2K5m785V6PqqWAsmkNzAOsf8F++gAIhAKfW2qReBZL34Xrzd7r4JzUlJbf5eoeUZvKT+qsbbskL", "publicKey": { "content": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNGVENDQVp1Z0F3SUJBZ0lVQUxZY1ZSbUZTcG05VnhJTjdIVzdtaHBPeSs4d0NnWUlLb1pJemowRUF3TXcKS2pFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUkV3RHdZRFZRUURFd2h6YVdkemRHOXlaVEFlRncweQpNakExTURJeU1EVXhORGRhRncweU1qQTFNREl5TVRBeE5EWmFNQUF3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPClBRTUJCd05DQUFUc1lFdG5xaWpaTlBPRG5CZWx5S1dIWHQ3YndtWElpK2JjeEcrY2gyQUZRaGozdHcyUEJ2RmkKenBwWm5YRVNWUnZEMU1lUXBmWUt0QnF6RHFjOVRoSTRvNEhJTUlIRk1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBVApCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBekFNQmdOVkhSTUJBZjhFQWpBQU1CMEdBMVVkRGdRV0JCU2dzZW9ECnhRaEtjSk1oMnFPZ0MweFZTZE1HUFRBZkJnTlZIU01FR0RBV2dCUll3QjVma1VXbFpxbDZ6SkNoa3lMUUtzWEYKK2pBaUJnTlZIUkVCQWY4RUdEQVdnUlJpYVd4c2VVQmphR0ZwYm1kMVlYSmtMbVJsZGpBc0Jnb3JCZ0VFQVlPLwpNQUVCQkI1b2RIUndjem92TDJkcGRHaDFZaTVqYjIwdmJHOW5hVzR2YjJGMWRHZ3dDZ1lJS29aSXpqMEVBd01ECmFBQXdaUUl4QUsrKzliL25CZlVWNGdlRlNBRE9nUjQrdW5zaDArU2tpdWJsT0o4QmloWnNUTk9VcjNmd2ZXNngKblBrcCtTeTFFd0l3ZE91bFdvcDNvSlYvUW83ZmF1MG1sc3kwTUNtM2xCZ3l4bzJscEFhSTRnRlJ4R0UyR2hwVgo3TitrQ29TMUEyNFMKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=" } } } } $ sig=$(rekor-cli get --uuid=$uuid --format=json | jq -r .Body.HashedRekordObj.signature.content) $ cert=$(rekor-cli get --uuid=$uuid --format=json | jq -r .Body.HashedRekordObj.signature.publicKey.content) $ cosign verify-blob --cert <(echo $cert | base64 --decode) --signature <(echo $sig | base64 --decode) <(git rev-parse HEAD | tr -d '\n') tlog entry verified with uuid: d0444ed9897f31fefc820ade9a706188a3bb030055421c91e64475a8c955ae2c index: 2212633 Verified OK $ echo $cert | base64 --decode | openssl x509 -text Certificate: Data: Version: 3 (0x2) Serial Number: b6:1c:55:19:85:4a:99:bd:57:12:0d:ec:75:bb:9a:1a:4e:cb:ef Signature Algorithm: ecdsa-with-SHA384 Issuer: O=sigstore.dev, CN=sigstore Validity Not Before: May 2 20:51:47 2022 GMT Not After : May 2 21:01:46 2022 GMT Subject: Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:ec:60:4b:67:aa:28:d9:34:f3:83:9c:17:a5:c8: a5:87:5e:de:db:c2:65:c8:8b:e6:dc:c4:6f:9c:87: 60:05:42:18:f7:b7:0d:8f:06:f1:62:ce:9a:59:9d: 71:12:55:1b:c3:d4:c7:90:a5:f6:0a:b4:1a:b3:0e: a7:3d:4e:12:38 ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature X509v3 Extended Key Usage: Code Signing X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: A0:B1:EA:03:C5:08:4A:70:93:21:DA:A3:A0:0B:4C:55:49:D3:06:3D X509v3 Authority Key Identifier: keyid:58:C0:1E:5F:91:45:A5:66:A9:7A:CC:90:A1:93:22:D0:2A:C5:C5:FA X509v3 Subject Alternative Name: critical email:billy@chainguard.dev 1.3.6.1.4.1.57264.1.1: https://github.com/login/oauth Signature Algorithm: ecdsa-with-SHA384 30:65:02:31:00:af:be:f5:bf:e7:05:f5:15:e2:07:85:48:00: ce:81:1e:3e:ba:7b:21:d3:e4:a4:8a:e6:e5:38:9f:01:8a:16: 6c:4c:d3:94:af:77:f0:7d:6e:b1:9c:f9:29:f9:2c:b5:13:02: 30:74:eb:a5:5a:8a:77:a0:95:7f:42:8e:df:6a:ed:26:96:cc: b4:30:29:b7:94:18:32:c6:8d:a5:a4:06:88:e2:01:51:c4:61: 36:1a:1a:55:ec:df:a4:0a:84:b5:03:6e:12 -----BEGIN CERTIFICATE----- MIICFTCCAZugAwIBAgIUALYcVRmFSpm9VxIN7HW7mhpOy+8wCgYIKoZIzj0EAwMw KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y MjA1MDIyMDUxNDdaFw0yMjA1MDIyMTAxNDZaMAAwWTATBgcqhkjOPQIBBggqhkjO PQMBBwNCAATsYEtnqijZNPODnBelyKWHXt7bwmXIi+bcxG+ch2AFQhj3tw2PBvFi zppZnXESVRvD1MeQpfYKtBqzDqc9ThI4o4HIMIHFMA4GA1UdDwEB/wQEAwIHgDAT BgNVHSUEDDAKBggrBgEFBQcDAzAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSgseoD xQhKcJMh2qOgC0xVSdMGPTAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF +jAiBgNVHREBAf8EGDAWgRRiaWxseUBjaGFpbmd1YXJkLmRldjAsBgorBgEEAYO/ MAEBBB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwCgYIKoZIzj0EAwMD aAAwZQIxAK++9b/nBfUV4geFSADOgR4+unsh0+SkiublOJ8BihZsTNOUr3fwfW6x nPkp+Sy1EwIwdOulWop3oJV/Qo7fau0mlsy0MCm3lBgyxo2lpAaI4gFRxGE2GhpV 7N+kCoS1A24S -----END CERTIFICATE----- ``` Notice that **the Rekor entry includes the same cert that was used to generate the git commit signature**. This can be used to correlate the 2 messages, even though they signed different content! Note that for Git tags, the annotated tag object SHA is what is used (i.e. the output of `git rev-parse `), **not** the SHA of the underlying tagged commit. ### What's stored in the signature For commits: - Commit content checksum (sha256) - Commit signing time (untrusted system time) - (optional) TSA signature + cert For tags: - Tag content checksum (sha256) - Tag signing time (untrusted system time) - (optional) TSA signature + cert ### What's stored in Rekor For commits, a HashedRekord containing: - Commit SHA checksum - Fulcio certificate - Public Key - [Signer Identity info](https://github.com/sigstore/fulcio/blob/main/docs/oidc.md) For tags, a HashedRekord containing: - Tag SHA checksum - Fulcio certificate - Public Key - [Signer Identity info](https://github.com/sigstore/fulcio/blob/main/docs/oidc.md)gitsign-0.13.0/e2e/000077500000000000000000000000001477253552300137275ustar00rootroot00000000000000gitsign-0.13.0/e2e/sign_test.go000066400000000000000000000060151477253552300162570ustar00rootroot00000000000000// Copyright 2024 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e // +build e2e package e2e import ( "context" "encoding/json" "os" "testing" "time" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/storage/memory" "github.com/sigstore/cosign/v2/pkg/providers" "github.com/sigstore/gitsign/internal/git/gittest" "github.com/sigstore/gitsign/pkg/fulcio" gsgit "github.com/sigstore/gitsign/pkg/git" "github.com/sigstore/gitsign/pkg/gitsign" "github.com/sigstore/gitsign/pkg/rekor" "github.com/sigstore/sigstore/pkg/oauth" "github.com/sigstore/sigstore/pkg/oauthflow" // Enable OIDC providers _ "github.com/sigstore/cosign/v2/pkg/providers/all" ) func TestSign(t *testing.T) { ctx := context.Background() var flow oauthflow.TokenGetter = &oauthflow.InteractiveIDTokenGetter{ HTMLPage: oauth.InteractiveSuccessHTML, } if providers.Enabled(ctx) { // If automatic token provisioning is enabled, use it. token, err := providers.Provide(ctx, "sigstore") if err != nil { t.Fatal(err) } flow = &oauthflow.StaticTokenGetter{ RawToken: token, } } fulcio, err := fulcio.NewClient("https://fulcio.sigstore.dev", fulcio.OIDCOptions{ ClientID: "sigstore", Issuer: "https://oauth2.sigstore.dev/auth", TokenGetter: flow, }) if err != nil { t.Fatal(err) } rekor, err := rekor.NewWithOptions(ctx, "https://rekor.sigstore.dev") if err != nil { t.Fatal(err) } signer, err := gitsign.NewSigner(ctx, fulcio, rekor) if err != nil { t.Fatal(err) } // Make a commit + sign it storage := memory.NewStorage() repo, err := git.Init(storage, memfs.New()) if err != nil { panic(err) } w, err := repo.Worktree() if err != nil { panic(err) } sha, err := w.Commit("example commit", &git.CommitOptions{ Author: &object.Signature{ Name: "John Doe", Email: "john@example.com", When: time.UnixMicro(1234567890).UTC(), }, Signer: signer, AllowEmptyCommits: true, }) if err != nil { t.Fatal(err) } commit, err := repo.CommitObject(sha) if err != nil { t.Fatal(err) } body := gittest.MarshalCommitBody(t, commit) sig := []byte(commit.PGPSignature) // Verify the commit verifier, err := gsgit.NewDefaultVerifier(ctx) if err != nil { t.Fatal(err) } summary, err := gsgit.Verify(ctx, verifier, rekor, body, sig, true) if err != nil { t.Fatal(err) } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") enc.Encode(summary.LogEntry) } gitsign-0.13.0/go.mod000066400000000000000000000340751477253552300143730ustar00rootroot00000000000000module github.com/sigstore/gitsign go 1.23.4 require ( github.com/coreos/go-oidc/v3 v3.12.0 github.com/coreos/go-systemd/v22 v22.5.0 github.com/github/smimesign v0.2.0 github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-git/v5 v5.14.0 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/google/go-cmp v0.7.0 github.com/in-toto/attestation v1.1.1 github.com/in-toto/in-toto-golang v0.9.0 github.com/jonboulle/clockwork v0.5.0 github.com/mattn/go-tty v0.0.7 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/secure-systems-lab/go-securesystemslib v0.9.0 github.com/sigstore/cosign/v2 v2.4.3 github.com/sigstore/fulcio v1.6.6 github.com/sigstore/protobuf-specs v0.4.0 github.com/sigstore/rekor v1.3.9 github.com/sigstore/sigstore v1.9.1 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 golang.org/x/crypto v0.36.0 golang.org/x/oauth2 v0.28.0 golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 google.golang.org/protobuf v1.36.5 ) require ( cloud.google.com/go v0.116.0 // indirect cloud.google.com/go/auth v0.14.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/iam v1.2.2 // indirect cloud.google.com/go/kms v1.20.5 // indirect cloud.google.com/go/longrunning v0.6.2 // indirect dario.cat/mergo v1.0.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.5 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect github.com/alibabacloud-go/cr-20181201 v1.0.10 // indirect github.com/alibabacloud-go/darabonba-openapi v0.2.1 // indirect github.com/alibabacloud-go/debug v1.0.0 // indirect github.com/alibabacloud-go/endpoint-util v1.1.1 // indirect github.com/alibabacloud-go/openapi-util v0.1.0 // indirect github.com/alibabacloud-go/tea v1.2.2 // indirect github.com/alibabacloud-go/tea-utils v1.4.5 // indirect github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/aliyun/credentials-go v1.3.4 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.29.6 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.59 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // 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.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.37.18 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 // indirect github.com/aws/smithy-go v1.22.2 // indirect github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/buildkite/agent/v3 v3.92.1 // indirect github.com/buildkite/go-pipeline v0.13.3 // indirect github.com/buildkite/interpolate v0.1.5 // indirect github.com/buildkite/roko v1.3.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.6.0 // 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/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/docker/cli v27.5.0+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // 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-piv/piv-go/v2 v2.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/certificate-transparency-go v1.3.1 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-containerregistry v0.20.3 // indirect github.com/google/go-github/v55 v55.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/trillian v1.7.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.5 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/vault/api v1.16.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect github.com/jellydator/ttlcache/v3 v3.3.0 // indirect github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect github.com/magiconair/properties v1.8.9 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/pkcs11 v1.1.1 // 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/mozillazg/docker-credential-acr-helper v0.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/oleiade/reflections v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.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.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/segmentio/ksuid v1.0.4 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/sigstore-go v0.7.0 // indirect github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.15 // indirect github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.15 // indirect github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.15 // indirect github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.15 // indirect github.com/sigstore/timestamp-authority v1.2.4 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.3.1 // 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.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/viper v1.19.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/theupdateframework/go-tuf/v2 v2.0.2 // indirect github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 // indirect github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 // indirect github.com/tink-crypto/tink-go/v2 v2.3.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/vbatts/tar-split v0.11.6 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/zeebo/errs v1.4.0 // indirect gitlab.com/gitlab-org/api/client-go v0.123.0 // indirect go.mongodb.org/mongo-driver v1.15.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect go.step.sm/crypto v0.57.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.36.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.10.0 // indirect google.golang.org/api v0.221.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect google.golang.org/grpc v1.70.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.30.2 // indirect k8s.io/apimachinery v0.30.2 // indirect k8s.io/client-go v0.30.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a // indirect k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/release-utils v0.11.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) retract v0.7.0 gitsign-0.13.0/go.sum000066400000000000000000002662521477253552300144240ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0= cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= cloud.google.com/go/kms v1.20.5 h1:aQQ8esAIVZ1atdJRxihhdxGQ64/zEbJoJnCz/ydSmKg= cloud.google.com/go/kms v1.20.5/go.mod h1:C5A8M1sv2YWYy1AE6iSrnddSG9lRGdJq5XEdBy28Lmw= cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= 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.0 h1:q4W5I+RtDIA27rslQyyt6sWkXX0YS9qm43+U1/3e0kU= cuelang.org/go v0.12.0/go.mod h1:B4+kjvGGQnbkz+GuAv1dq/R308gTkp0sO28FdMrJ2Kw= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0 h1:kcnfY4vljxXliXDBrA9K9lwF8IoEZ4Up6Eg9kWTIm28= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/provider v0.14.0/go.mod h1:tlqp9mUGbsP+0z3Q+c0Q5MgSdq/OMwQhm5bffR3Q3ss= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1 h1:1mvYtZfWQAnwNah/C+Z+Jb9rQH95LPE2vlmMuWAHJk8= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.1/go.mod h1:75I/mXtme1JyWFtz8GocPHVFyH421IBoZErnO16dd0k= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1 h1:Bk5uOhSAenHyR5P61D/NzeQCv+4fEVV8mOkJ82NqpWw= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.1/go.mod h1:QZ4pw3or1WPmRBxf0cHd1tknzrT54WPBOQoGutCPvSU= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0 h1:7rKG7UmnrxX4N53TFhkYqjc+kVUZuw0fL8I3Fh+Ld9E= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0/go.mod h1:Wjo+24QJVhhl/L7jy6w9yzFF2yDOf3cKECAa8ecf9vE= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4= github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8= github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM= github.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo= github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc= github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4= github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/cr-20160607 v1.0.1 h1:WEnP1iPFKJU74ryUKh/YDPHoxMZawqlPajOymyNAkts= github.com/alibabacloud-go/cr-20160607 v1.0.1/go.mod h1:QHeKZtZ3F3FOE+/uIXCBAp8POwnUYekpLwr1dtQa5r0= github.com/alibabacloud-go/cr-20181201 v1.0.10 h1:B60f6S1imsgn2fgC6X6FrVNrONDrbCT0NwYhsJ0C9/c= github.com/alibabacloud-go/cr-20181201 v1.0.10/go.mod h1:VN9orB/w5G20FjytoSpZROqu9ZqxwycASmGqYUJSoDc= github.com/alibabacloud-go/darabonba-openapi v0.1.12/go.mod h1:sTAjsFJmVsmcVeklL9d9uDBlFsgl43wZ6jhI6BHqHqU= github.com/alibabacloud-go/darabonba-openapi v0.1.14/go.mod h1:w4CosR7O/kapCtEEMBm3JsQqWBU/CnZ2o0pHorsTWDI= github.com/alibabacloud-go/darabonba-openapi v0.2.1 h1:WyzxxKvhdVDlwpAMOHgAiCJ+NXa6g5ZWPFEzaK/ewwY= github.com/alibabacloud-go/darabonba-openapi v0.2.1/go.mod h1:zXOqLbpIqq543oioL9IuuZYOQgHQ5B8/n5OPrnko8aY= github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= github.com/alibabacloud-go/debug v1.0.0 h1:3eIEQWfay1fB24PQIEzXAswlVJtdQok8f3EVN5VrBnA= github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8= github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= github.com/alibabacloud-go/openapi-util v0.0.9/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/openapi-util v0.0.10/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils v1.3.9/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/aliyun/credentials-go v1.3.4 h1:X5nse+8s7ft00ANpoG3+bFJIqZVpjHbOg7G9gWQshVY= github.com/aliyun/credentials-go v1.3.4/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= github.com/aws/aws-sdk-go-v2 v1.36.1/go.mod h1:5PMILGVKiW32oDzjj6RU52yrNrDPUHcbZQYr1sM7qmM= github.com/aws/aws-sdk-go-v2/config v1.29.6 h1:fqgqEKK5HaZVWLQoLiC9Q+xDlSp+1LYidp6ybGE2OGg= github.com/aws/aws-sdk-go-v2/config v1.29.6/go.mod h1:Ft+WLODzDQmCTHDvqAH1JfC2xxbZ0MxpZAcJqmE1LTQ= github.com/aws/aws-sdk-go-v2/credentials v1.17.59 h1:9btwmrt//Q6JcSdgJOLI98sdr5p7tssS9yAsGe8aKP4= github.com/aws/aws-sdk-go-v2/credentials v1.17.59/go.mod h1:NM8fM6ovI3zak23UISdWidyZuI1ghNe2xjzUZAyT+08= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 h1:KwsodFKVQTlI5EyhRSugALzsV6mG/SGrdjlMXSZSdso= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28/go.mod h1:EY3APf9MzygVhKuPXAc5H+MkGb8k/DOSQjWS0LgkKqI= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32 h1:BjUcr3X3K0wZPGFg2bxOWW3VPN8rkE3/61zhP+IHviA= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.32/go.mod h1:80+OGC/bgzzFFTUmcuwD0lb4YutwQeKLFpmt6hoWapU= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32 h1:m1GeXHVMJsRsUAqG6HjZWx9dj7F5TR+cF1bjyfYyBd4= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.32/go.mod h1:IitoQxGfaKdVLNg0hD8/DXmAqNy0H4K2H2Sf91ti8sI= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 h1:Pg9URiobXy85kgFev3og2CuOZ8JZUBENF+dcgWBaYNk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= 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.2 h1:D4oz8/CzT9bAEYtVhSBmFj2dNOtaHOtMKc2vHBwYizA= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2/go.mod h1:Za3IHqTQ+yNcRHxu1OFucBh0ACZT4j4VQFF0BqpZcLY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 h1:SYVGSFQHlchIcy6e7x12bsrxClCXSP5et8cqVhL8cuw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13/go.mod h1:kizuDaLX37bG5WZaoxGPQR/LNFXpxp0vsUnqfkWXfNE= github.com/aws/aws-sdk-go-v2/service/kms v1.37.18 h1:pi9M/9n1PLayBXjia7LfwgXwcpFdFO7Q2cqKOZa1ZmM= github.com/aws/aws-sdk-go-v2/service/kms v1.37.18/go.mod h1:vZXvmzfhdsPj/axc8+qk/2fSCP4hGyaZ1MAduWEHAxM= github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 h1:/eE3DogBjYlvlbhd2ssWyeuovWunHLxfgw3s/OJa4GQ= github.com/aws/aws-sdk-go-v2/service/sso v1.24.15/go.mod h1:2PCJYpi7EKeA5SkStAmZlF6fi0uUABuhtF8ILHjGc3Y= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 h1:M/zwXiL2iXUrHputuXgmO94TVNmcenPHxgLXLutodKE= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14/go.mod h1:RVwIw3y/IqxC2YEXSIkAzRDdEU1iRabDPaYjpGCbCGQ= github.com/aws/aws-sdk-go-v2/service/sts v1.33.14 h1:TzeR06UCMUq+KA3bDkujxK1GVGy+G8qQN/QVYzGLkQE= github.com/aws/aws-sdk-go-v2/service/sts v1.33.14/go.mod h1:dspXf/oYWGWo6DEvj98wpaTeqt5+DMidZD0A9BYTizc= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1 h1:50sS0RWhGpW/yZx2KcDNEb1u1MANv5BMEkJgcieEDTA= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1/go.mod h1:ErZOtbzuHabipRTDTor0inoRlYwbsV1ovwSxjGs/uJo= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/buildkite/agent/v3 v3.92.1 h1:6HLdDbU5z6ZyJ3TCt/UQEcLv2nhg/gdS4ApnsrUwhOE= github.com/buildkite/agent/v3 v3.92.1/go.mod h1:mUNebi1cYh66iBjqVdJTgEn+sm53x8zC/XQQfpZSk9A= github.com/buildkite/go-pipeline v0.13.3 h1:llI7sAdZ7sqYE7r8ePlmDADRhJ1K0Kua2+gv74Z9+Es= github.com/buildkite/go-pipeline v0.13.3/go.mod h1:1uC2XdHkTV1G5jYv9K8omERIwrsYbBruBrPx1Zu1uFw= github.com/buildkite/interpolate v0.1.5 h1:v2Ji3voik69UZlbfoqzx+qfcsOKLA61nHdU79VV+tPU= github.com/buildkite/interpolate v0.1.5/go.mod h1:dHnrwHew5O8VNOAgMDpwRlFnhL5VSN6M1bHVmRZ9Ccc= github.com/buildkite/roko v1.3.1 h1:t7K30ceLLYn6k7hQP4oq1c7dVlhgD5nRcuSRDEEnY1s= github.com/buildkite/roko v1.3.1/go.mod h1:23R9e6nHxgedznkwwfmqZ6+0VJZJZ2Sg/uVcp2cP46I= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589/go.mod h1:OuDyvmLnMCwa2ep4Jkm6nyA0ocJuZlGyk2gGseVzERM= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f h1:eHnXnuK47UlSTOQexbzxAZfekVz6i+LKRdj1CU5DPaM= github.com/cyberphone/json-canonicalization v0.0.0-20231217050601-ba74d44ecf5f/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936 h1:foGzavPWwtoyBvjWyKJYDYsyzy+23iBV7NKTwdk+LRY= github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936/go.mod h1:ttKPnOepYt4LLzD+loXQ1rT6EmpyIYHro7TAJuIIlHo= github.com/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/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I= github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/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/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/github/smimesign v0.2.0 h1:Hho4YcX5N1I9XNqhq0fNx0Sts8MhLonHd+HRXVGNjvk= github.com/github/smimesign v0.2.0/go.mod h1:iZiiwNT4HbtGRVqCQu7uJPEZCuEE5sfSSttcnePkDl4= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= 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-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-piv/piv-go/v2 v2.3.0 h1:kKkrYlgLQTMPA6BiSL25A7/x4CEh2YCG7rtb/aTkx+g= github.com/go-piv/piv-go/v2 v2.3.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI= github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/certificate-transparency-go v1.3.1 h1:akbcTfQg0iZlANZLn0L9xOeWtyCIdeoYhKrqi5iH3Go= github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/google/trillian v1.7.1 h1:+zX8jLM3524bAMPS+VxaDIDgsMv3/ty6DuLWerHXcek= github.com/google/trillian v1.7.1/go.mod h1:E1UMAHqpZCA8AQdrKdWmHmtUfSeiD0sDWD1cv00Xa+c= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/in-toto/attestation v1.1.1 h1:QD3d+oATQ0dFsWoNh5oT0udQ3tUrOsZZ0Fc3tSgWbzI= github.com/in-toto/attestation v1.1.1/go.mod h1:Dcq1zVwA2V7Qin8I7rgOi+i837wEf/mOZwRm047Sjys= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ= github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 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/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q= github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mozillazg/docker-credential-acr-helper v0.4.0 h1:Uoh3Z9CcpEDnLiozDx+D7oDgRq7X+R296vAqAumnOcw= github.com/mozillazg/docker-credential-acr-helper v0.4.0/go.mod h1:2kiicb3OlPytmlNC9XGkLvVC+f0qTiJw3f/mhmeeQBg= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.1.0 h1:D+I/UsXQB4esMathlt0kkZRJZdUDmhv5zGi/HOwYTWo= github.com/oleiade/reflections v1.1.0/go.mod h1:mCxx0QseeVCHs5Um5HhJeCKVC7AwS8kO67tky4rdisA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/open-policy-agent/opa v1.1.0 h1:HMz2evdEMTyNqtdLjmu3Vyx06BmhNYAx67Yz3Ll9q2s= github.com/open-policy-agent/opa v1.1.0/go.mod h1:T1pASQ1/vwfTa+e2fYcfpLCvWgYtqtiUv+IuA/dLPQs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20180811024354-2b5b3bfb099b/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.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/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/cosign/v2 v2.4.3 h1:UAU/6Z33gVBCV01b2l1fdvMml9IJTrsDiYQDB5K+sQI= github.com/sigstore/cosign/v2 v2.4.3/go.mod h1:6vZ2vHarfJB3N4FHYV/5M1qdHiWi2PM1c8ogNPCe2jA= github.com/sigstore/fulcio v1.6.6 h1:XaMYX6TNT+8n7Npe8D94nyZ7/ERjEsNGFC+REdi/wzw= github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk= github.com/sigstore/protobuf-specs v0.4.0 h1:yoZbdh0kZYKOSiVbYyA8J3f2wLh5aUk2SQB7LgAfIdU= github.com/sigstore/protobuf-specs v0.4.0/go.mod h1:FKW5NYhnnFQ/Vb9RKtQk91iYd0MKJ9AxyqInEwU6+OI= github.com/sigstore/rekor v1.3.9 h1:sUjRpKVh/hhgqGMs0t+TubgYsksArZ6poLEC3MsGAzU= github.com/sigstore/rekor v1.3.9/go.mod h1:xThNUhm6eNEmkJ/SiU/FVU7pLY2f380fSDZFsdDWlcM= github.com/sigstore/sigstore v1.9.1 h1:bNMsfFATsMPaagcf+uppLk4C9rQZ2dh5ysmCxQBYWaw= github.com/sigstore/sigstore v1.9.1/go.mod h1:zUoATYzR1J3rLNp3jmp4fzIJtWdhC3ZM6MnpcBtnsE4= github.com/sigstore/sigstore-go v0.7.0 h1:bIGPc2IbnbxnzlqQcKlh1o96bxVJ4yRElpP1gHrOH48= github.com/sigstore/sigstore-go v0.7.0/go.mod h1:4RrCK+i+jhx7lyOG2Vgef0/kFLbKlDI1hrioUYvkxxA= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.15 h1:g/hPoaemFv/6ZJIRyb5I1lA4qU9PZwCTu/GkvFV5jEw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.15/go.mod h1:n2yKi/b29+JB54PyONruHvvha4zugC7jzr+A16cNLvw= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.15 h1:K2GstKWXftcpmg/wHfcJFYKWuj+YRSoTgwxm3ox2FjE= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.15/go.mod h1:tOSdKYXCkplk54FSR/58UYQm1S/GlQK4Y1GgMhiq40U= github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.15 h1:ThpZMfR2TecI6Ji7s/nFlcCIkwXYhZUYziJdZs3pOaw= github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.15/go.mod h1:x+4wvq6tzIQRZaSdMS6/VT9nuCoepypozfzP4Tqwnqw= github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.15 h1:mR+VaOSx2sUpaE8lXarinHcT8UXi+fKE4ESNBzDRAtQ= github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.15/go.mod h1:6olKNL2BGrsZPLbO/7kiJzZPxU74270nDI5G3HSSykw= github.com/sigstore/timestamp-authority v1.2.4 h1:RjXZxOWorEiem/uSr0pFHVtQpyzpcFxgugo5jVqm3mw= github.com/sigstore/timestamp-authority v1.2.4/go.mod h1:ExrbobKdEuwuBptZIiKp1IaVBRiUeKbiuSyZTO8Okik= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= 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/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= github.com/theupdateframework/go-tuf/v2 v2.0.2 h1:PyNnjV9BJNzN1ZE6BcWK+5JbF+if370jjzO84SS+Ebo= github.com/theupdateframework/go-tuf/v2 v2.0.2/go.mod h1:baB22nBHeHBCeuGZcIlctNq4P61PcOdyARlplg5xmLA= github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 h1:N9UxlsOzu5mttdjhxkDLbzwtEecuXmlxZVo/ds7JKJI= github.com/tink-crypto/tink-go-awskms/v2 v2.1.0/go.mod h1:PxSp9GlOkKL9rlybW804uspnHuO9nbD98V/fDX4uSis= github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 h1:3B9i6XBXNTRspfkTC0asN5W0K6GhOSgcujNiECNRNb0= github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw= github.com/tink-crypto/tink-go/v2 v2.3.0 h1:4/TA0lw0lA/iVKBL9f8R5eP7397bfc4antAMXF5JRhs= github.com/tink-crypto/tink-go/v2 v2.3.0/go.mod h1:kfPOtXIadHlekBTeBtJrHWqoGL+Fm3JQg0wtltPuxLU= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q= github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg= github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU= github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= gitlab.com/gitlab-org/api/client-go v0.123.0 h1:W3LZ5QNyiSCJA0Zchkwz8nQIUzOuDoSWMZtRDT5DjPI= gitlab.com/gitlab-org/api/client-go v0.123.0/go.mod h1:Jh0qjLILEdbO6z/OY94RD+3NDQRUKiuFSFYozN6cpKM= go.mongodb.org/mongo-driver v1.15.1 h1:l+RvoUOoMXFmADTLfYDm7On9dRm7p4T80/lEQM+r7HU= go.mongodb.org/mongo-driver v1.15.1/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.step.sm/crypto v0.57.0 h1:YjoRQDaJYAxHLVwjst0Bl0xcnoKzVwuHCJtEo2VSHYU= go.step.sm/crypto v0.57.0/go.mod h1:+Lwp5gOVPaTa3H/Ul/TzGbxQPXZZcKIUGMS0lG6n9Go= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/api v0.221.0 h1:qzaJfLhDsbMeFee8zBRdt/Nc+xmOuafD/dbdgGfutOU= google.golang.org/api v0.221.0/go.mod h1:7sOU2+TL4TxUTdbi0gWgAIg7tH5qBXxoyhtL+9x3biQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 h1:2duwAxN2+k0xLNpjnHTXoMUgnv6VPSp5fiqTuwSxjmI= google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= 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-20240521193020-835d969ad83a h1:zD1uj3Jf+mD4zmA7W+goE5TxDkI7OGJjBNBzq5fJtLA= k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/release-utils v0.11.0 h1:FUVSw2dO67M7mfcQx9AITEGnTHoBOdJNbbQ3FT3o8mA= sigs.k8s.io/release-utils v0.11.0/go.mod h1:wAlXz8xruzvqZUsorI64dZ3lbkiDnYSlI4IYC6l2yEA= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= gitsign-0.13.0/hack/000077500000000000000000000000001477253552300141625ustar00rootroot00000000000000gitsign-0.13.0/hack/presubmit.sh000077500000000000000000000016501477253552300165350ustar00rootroot00000000000000#!/bin/bash # Copyright 2022 The Sigstore Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" pushd "${PROJECT_ROOT}" trap popd EXIT # Verify that generated Markdown docs are up-to-date. tmpdir=$(mktemp -d) go run docs/cli/main.go --dir "${tmpdir}" diff -Naur -I '###### Auto generated' -x "*.go" "${tmpdir}" docs/cligitsign-0.13.0/images/000077500000000000000000000000001477253552300145215ustar00rootroot00000000000000gitsign-0.13.0/images/unverified.png000066400000000000000000002501521477253552300173740ustar00rootroot00000000000000PNG  IHDRDo miCCPICC ProfileHWXS[BzDj)!ҋ`#$cBP ]D"mDŮ,/TuQ*oB+;79s?Dj/.&3Ƥ3HO 8u.O&aE(˻Qܯ:+9_E/@Aɗ!>'@T警HxĺR ī8[w*p7 $%! ˕fqlȣbW1_$@s8X3?=GKl!v;cMX=``X֊Ud`w zK'o**)sqr+L-P<$4([X`\3\Pk_o!~7ݼo?M ~ox3Va\Z Kh“f̀/@( 0z!R0sA (jl[N gEp\w/Ax!!4! ℸ!L$ E$ @1"Gf 2dقT# <ҎF"]TT5Em(eQh:F'E|t ZV:zv/^ `>f9cLbX&faX9Vb9_:n#N8w;8Oyd|_SU!ރ%&'/CC&L! Y$#D;7yy)y|IhS($Je.RK9MGyn.R^OCT#MGSPwPSoSh4[Z-V@[B=}Рkhp45*54h$kh4'hikԼ٭EֲbkqfiUjֺիMX{y:$[P|:'u1MѷO;uv2=m=z:zz)zS*uc<o205` \1xo80P`Xj'#QQrzƸqƧV:;&It&&fӵ'M͂rV32W7`1X3ݳO@t`::lp:z: +/9N^N" N }W Luf9:8?twv)vwy5zD#ΎH#G6|stNssC's/^^RZ.ok 78b9Ol&^|s|(m[sw026tZrY=c9rXY]߳}3CҐPu,òjz=ç DDE,18՜HșQQE;FKG#G}/&FS b9+cM;O02aFDz]&MO'hKNy"c̈13\L3N5Sҷ zl8q%n?u &9;`!#5cWgn,ۛ\c^] ,YϳWfw n[N:'"gSyy{3uĹS&M.qH:&N^=G%.Cde Un/I0Ô)jOOm8mѴgEaELǧ̰1wÙ[f!2g̶=v9;RصxE_R57?g)(\`B|ha"Ek}-^(s-+/#I֒^K7.#./ebݓcN^;tsgΜ<:|\y/0/_Xz7y]prccWr5εcHq減λN᝾s׺_A80aGw|"{sSgϪ=o bΗ}%h_ gLOk7_-q{_Ώ̏g?~z73s/_p܁O 4+ 7;@}eDٿ 🰲_/j{|7 mWPwY ƒg#˲/[aw<.V !žas̗LoDٟ~wxϐ-%eXIfMM*>F(iNxDASCIIScreenshotY pHYs%%IR$iTXtXML:com.adobe.xmp 508 836 Screenshot \&iDOT(AW@IDATx]`E~i$ %tBHQ(QDE.JUtC%wi$7̛73m7ρ &F`F`F(yJhR[0#0#0 Ll@$F`F`F ̽dF`F`ـI#0#0#0!{0#0#060!F`F`F7`B3`F`F`l`Bd '1#0#0o 7g%#0#0# NbF`F`@ o|KF`F`FA (0#0#0"#0#0#`"P8`F`F`|&D=s/F`F`F&D6p#0#0#Ee]̀DȂtHOKHπ$(S + ("*Bo|sKF`F`F3E]x \Ґ7'É2.BdPRhذ1T]AAPF غ};o⒡jdU$Gy0#0#0@EH,C"p1o UʆBj5!4(Ξ:HՇZA!f-pAH2e!lU#0#0#0V !ȀPjU1#GS+C T\!!9p ݙxPqS +tP*вe}{F`F#b gvArQHO: 3b7퀟*aPB*n5,$qF ((91.^̂\2> +/| /e@ U*UvPn=([9GU=IR*TZ 3-ˇ#ʆ+H,Y7#0#P(~S3R'Wr)[5Z /@ A8HGP@`0gf4nޜ~ )Pv5v=ЮukeqcAw"KV 3 9 J=G! /KTH0W(!_w`FHOMc[~л^B]TWoa@Hh]heP<pubF% 68x=Zyq`re%D񱐉$IQ )7aۿG0Qԋ2  (=!{͚ X?~A*TĤd(xeRGK1#\Nσ=+-I5Z5Q -g8s508 | \#Pl(%)"A7/ >9'쇾AƍYƂ# ! efE$D!p18y|t4^:(]<:iHP\)=rP˅/y&`FD"V*]FC Kd_S9#xn?X)ٷ\Ys.#`e!DiD-=UT bc .S)puvнSGtC5肪W Y(]ܻ_@.WCCZ&z_@/s. Y"amF`yc1Gn+ V\voZhGdÞɗMF8"P脈,41b,$CD22/A)DV#?2#!-<\wH:$&Cu=mY=U+7TIQrZ&,_@%`%+!ՕVyV,f2%{+Wz!2rq(H2Vq/+f4mI.x Ƭ#P(%% 2Rɽ!qZʕ{8hT:lRRvkz+>ݺvʢGT M[w@xdun୐V (KX+%RFV2P>"H~s^~5mjV/޿:|nnHFG>=pqp3Ag|;o?ǎ;(U4%6ҥq_c y /?Z4oV5ˉ\&Gdei ݿ?/W$(TBt)Ţ?ӏȐf%*NN;e]мaTXׯ P ɅW7\GA&AP~ns0dB:Gu6rꕬDXoQ |L|CooW{?S>>ލ=;}q: ץs'gzHl_rpэkzS>4u&_}3]}$hެ)_ Ng!S`V5ZPJ>ws&`}Zll.܃ 3Y(@3.FLlAzYG싯DSҥC3NoaB q2;~Z% yUߍPh󑘘Gjb9ln-DU/?CmjXb%zxȻor9%}s^뇫H+2C9]NDtsH~ȷPi7xqxkàqǽ._CQ2Z2 U b"{ɐo& ՝8z><h=_m[`\]Pvt;\"ԂSТmp* Uz +_~iETBp/3رc)}m/xpbʓ7Ï?$JGƌZþВ{rXdo.JؼګУ{7 ƠN/d)h'p2~ʑ>{D|;ivyjzMNǹ3u/{zÏ\F]\MzC=WDH"?$܂/UuS"Xm;tZ{uȫ*Ki;Uנ1AZ]A>iT&_O~n: Aw&`aOYuȭ'EDIqRPnwjݦ{"{vY}ў{V]/Ia,tPJ 1}T߭qGLj1XuސxrHA%Lo>N*QWY\j˲` ]ny*le+T2Gve> A1sB$,=FpQF7(4BVl q&D"T\ xV4:J8 6^bMtݱCKؑCDMFW1E56J\. B@Z:|,z]A"I56Yf*V~kj̰{qNd-*h8|ؽRL$M֎0H/s76=s]6HN[6BHp !gHQ4)SM1J4Y4z=ڿod D>̜tAz؝ZN>!TCkhw#sYh0{Pe=&j'Ӿi"Vo v''NB[ԇO<[%zcO>h t]Iu0g$YJ !))Z1?rY^"7tM_[WO\W5kZgl޲Up-,q>4x$2,dE$iM_T9=ODno1/ŬXR⊕DTxKϘeYb7L!~:y.ˇj=҅ 9 90k$r5BWy %ĢC $~|H"\ADDy ӧM!w 7±{Wp7D8s%c~x{$6>t{eReX1_&2$"lDV1O=h$7ZOҋر nEj S{q#ÔC׈[+aPnϜ5^}-YLC>5X' Bj!RˑKt$3̳Nh9Ә\,CG'ڴf2cퟬ&*Wj:2'OOΝ;`7@OU ko=о̘MH?M=xND4@XzhvzK2PY!JXdڣcF RCDȚ1-gїћvڻ"CAwGhL*.K>UZQEBs`\%r־7t8+p&f YX|iY#.;VfΖ7nU@7Ɩ0bZzU숲iRӁݩf+-8ڎE-֟Y:LxwE6YU>hѬX*Mu=4kD>K)Gz0sDŽ(jshJf;lCb)8q>p /stҐ !xX"-s)QPiUy|LDR2ɉ 7Oh/έ$-a㺿V "waXkAV"5XϑbI2v#zbziǠ!*a?pfI"zyS_"Ӈ}ͦDXGdˢMve@&4yBGI_cyeAeVd񐖱E ~cťr9 ? &jݪ M7Y/N r,a oۧzi+hl[~9Uut-N 됼އHAʷ[D|Úd!D$u>w`d($+/-$<6i 'hMuO $74Q:K%*Dz?GUkۆI4~ aiEx5y? 滋/oYgJ 463V,*ֱ㹧@ρCK D1fpv h5maq!_֥QBgkG@dW!5Q- Zbv݃Sug }>R!;EՏ91GFsdzTpw]H|`i{@m RsQ%ʳ'S>)7H삺jO˲G~um*r>t,9핔UC{ނj$uEܗq~kN"Rzjbضl:r"<oʔŷ@YD(, GI3y&p1-J]j¡]vܞaЫ[/Cph:nM4, %s΋!D&j:YeZ@j9e_mkG&+aP U^нP\L"qͿoGhYmjPSv`-z8-sݍz>ZGdHRRRv$" ˗g7 @௿4eInʧo0an,NdUJX+fwֻ U%R?MiMް׭[c5%yL7oL~2TLa4J>ZTܓuC-!/'ED.hߤ%։;_Kw"3K2dEW=ys0+=jQ7@U+-6r9;pם4Œ\L^lGPu&C4^g%z/ K"?B@ݪ"rY, rL&!!2.Gu BxKQC4@zvr:xhn!r6S."YO~]zh_#owq+xU>KbUN͑u«i3.I{_l޸Vf]a׌] *O!`A\U =b>#MO&.z,߬Ĺfд1k&!懷o҉T˫Ae_d->a8NQ lc+AeNQF&k׺Dm/byNB̴d5nͪ٨3>=)gŸP:h-9@B^o[Gw%G^Qfz",U kǮ*\v"a*4Bpn"-DS o&`#!*R 0\BܫZ{d3UmwЪYcq^Q̹{^8|4]j7h 55вd% 7ӛD- y IRD( *TB'jW}MKt;x6!{A5m5C51[ 6;Kq-퓏>y>y8۰+Z$ɨ4넝L考:4 ڎ/l%.?"^y 7 5deSr]ixZBDk=PD]Wf{tYm)޶^#z4v̚AcGםySt|A^’w&{ :=;+(B4蹅±ޜ~cjCDz&$n]+a}6C}j:\?o,q%BbI!;տ\Ś"""w>!#DB~,rNB9K',=k_7?6 6İTܨC_Y);iݎ8ѳ$)|3%++#P( QJRIGND ]9L \~:_}i&AJd.R%M۶мUkh:m;_.KCL|Tsl#"a|$Br v\p퇺&s䖗gyÏ*zݷ&kжM؟=8"*h% VBD2O~B;onh-BU&Y#>ZV"ʻmᡎțf~DѱmUY]Bʐw7_Z/K~Cw#>gm:h {hr'qoAY w!Ғ9"y EE־xƼ* Ln`+oO t|$@g'QXSg"~#A&]Dt0.S В5"& ӞIt!|!Z1Eصvq?@B1"GM:܊{Ky oZ(} ?CiAbB$k@@A '%%Q!@t#MXYp\yB5% /C&L8  :"JAMoܤ;$]D+1sP6<JTREHNMA H%\ -W>Wߞj "+X Ͼ0^bD&L^rp&'ҍl'R+'\\Ch-SC^ }_rrIzsZV-dsKy: 9|$}ZT槒'&DR9"BD֑͍7V@]:\Jd9IavH2DPU[ d!f>ŀDFVVJGɓ9L@i]܇SP'ˆȡ$jljOM筷YfX/St-^48!O*\SiٝtA[]ѫi^B;Uqyʘ9U8]o}5rmyGٯ ܀K&m㻷4\8ߤ;aoJ#^A g2ҩ#KI+:x I^(<5k=OFйI]',:M:t9Dmߡó¶NCqאK_P[wJ2v'_|C˭^N۳w_j7t28yT8 2zs,ǥzd+]uD쉧]e8m8ŗY߼ ?TOz^[ r8.Au v߱ =*=9.daz6ͪvSdPuʶY۾yGv3&\Tg-Lv2爞'z80r暗>қlC~ؘ C:M#5-qyDZg)Ht;6m"hҤLhÕ-7vmմ(DŽ:~xi'8cӂK;v^h߬A͆ꕣt[ql&TDbbcqzd#)1!{9I77dI/<% [nͷV>?qw>m6c!-!"LU_}M,hNСú̼$.<=&t4a)h9"\7ߑ\G>kh tE#XuF?IlEVJ6->o7uviuY"ZIxFo󩃛 )"zsŁ("²pQ-#;NPqII*~@t>/Yf'p UC+D@zR,4 BKA<.kٴ)ԉSCJCfWCi' UjA5s\E@=ՃGczHhM}R!.KKDn]n44ن˿6C]=;ʊYA@)X=XǁƇv=[JrOEm e*ԅn..:t~uo٣;n՗oYydp6l A~bK~T*n)d@X9C$u~8\2{ݏ|ֳMU북[N$diЊ9/k\Ae"d1X9g<\3[˒_ a4FF"`rRB%DpZk\v%~cq_O0>$&@U+dhZˠ+7AxgT8v:N$iP*3*U"AC]J 5?D*"BvxzoIyRmXO ԝ"D'a't/bn&4az !ʶnE.B#>`TlO>v N%zO>,)kMt o\(om4ÁjG>x2#hT1>r2f8Yz6oÛnm1smǟh.íx&SЈ_,w2UtgS\oJ@+JGEӐ}&#"Ⱥxd(X w?+$;eW"<_͚S@ZNIlܨ`uC{X\u;.BþD\#++!"=t;>YaozE ۪7 ۗ;DT~K.'!¥":ˈ]Axz_j+lX~`k;&EEr suBcV=eW5(6z \*c<Ĥo"=1ʮRT脈D/z"3Xom&5d9y{`P^]U+ kW;'M5+PQS8pTUMgݺuĉh8yhYΞFGa0`%-CGh/ubHVwՔnhx'MݝAoY+&wvz,C$J?3zS@{i=1{nm4o}j#AU@d+Mf?KJ}3턆,4ɜ,Iy{hеK'Rg wb,0.!laKd]b2 Zӧa#7N7Ï?=O2X X{tR 92D;ƥ-ϚO<.6iE䑳>,'s'qRb›\k֮GOEWz1E;!߰FI@IDAT }P N0΀ "WOP`JB|En>Rbn#$*k܈gLn FoVhdޠ\sϻ@m1J$_ygM"e1Wֺ%7Y{"@kzM?(Gցݵ_s_e7>~Io梵 &w}eKyݓk~~-,T q>EW:t c1ϔ7׿iak5C߅}c"7=l\˭Y= F(, ~O4Va͖uԦBq_y ;=B#Cz`V2DDb-vpԫYMJ94kn|[Őv 8eA %) Р^-pxKxлa OK&w5ĴbQn|CJ4;/sgmYOI r{w}E-YEiG@ FԶRDYKTop|ˏ٨|ii}Ct`z!,Z-%,98|d2LAԗ:S BxSb Ӿ q&RqtXDUʸUfdlY0j]1F!A񶩞#UچFAzZ+qKx^0NgQZ a܈{aؐCWݰcxбh$DxjHqiD)|Cu R!76Vݺv)V(?Dqvkظ/K|636 y~Fcɐ=֤рa_Eh ]i/[&G*H_?ǿ,:XS/ bz43j_\m(ZD @pW_j1PIn1q%EbAu_VBD> Xr|)==B`nJ& 1NQoC=/:>+, H[\p0\$ 'S~C`K2q}@$A+Y}ԙ>.↗lr&$Krdi0oޯX1~=jh]"m>v- nE^ߑ+#  G  NW Y|ey!H!F7K?4A  ·y  К ];/KR0nwp!.^KB,C?Z0#]K? D/6oOxa=d-98t0(8kʍ_#P{b כ_=o;Lěvo4{/KAD :!^k)N?K 9C ): %[?gjh)҃R,KFom B2t"=H&WWym !H"Csh҇ѫR Kt "^ѻSxD4ƃYl&,Pr@dِ rxCO<:=Z—#P',AD}p77=4Ej#8_A@"ެ/)d+|CghvD!΋j/{e'yCWrH" tg;營p<8~8 4#G6NBB .ժFpOX̔~&=lpC`7z;(Tit < Й]DN3*$;M3FrM٣\q*}vǕ,IyO^Q$DjiováG?.GVq8^uNТYS肇vPׄ}8#P'Cf"B =s*2xeˋ@w$}ZBhy3/_KNN']ɛ\OB͖@P,_P)P~$oj-*(_g%#P(WQDȨS%@p9t&F>s8UQ~ uRPVN¼{`//8SH3ğlzj_y;Ha! M*92fdY7m$bĄ(gLY`<@L \G!>t_nBJV<D Q^Tv!60)*xๆb@ C%\Z! 6'HR^I%"F8AUFtCe:2r90y0r sYwN"Cv{YW}6!ΤܭeneϦ0'1ЅxA*bHQdl)IdOBf]F;VyuȲI%ݘM0 i!Id R$&|e0 {ܥm- Ohd):dP붶^ˉHv8 /A㺵 88Z3 `&]SA`.k' m24ʙ(2pt(i3I5ץѣYKsg՘#P8g/ؓ!UU-y|e|rHYez\$OPenA4bdr9COARr*Ԫ H|eyRo=e8ir"CfFlKmqׁhRhn&=屸5#P|8SIQe&E2.d"l`Q`OD,B)o0ݐ5*3MeZLJYr$DV( 5U~gyoNr,ʮVIMWN{: qa;F,K12A9Kȑ ףwqSiNfJ*L9P.sgI9w(q! =wG* WJS1(77(SUo^^JE(GB {zԮKB?*gJ"ɼ7ɟMWvd\ި;;"= Rb:ɶGvVEF rCJ&E 2y83U,)MʍG.UIQz|YKHVU*A@0@DW۝,3+O3E4YJzr:9A[CIHR!RDdQ}8(' _7g.gyQ!&֣5%9bC.g*9j9E@Jt&Wq7BVX1 @SH$)Hv)n, d]os0JJ|Md~Q,"O##EFeCkQD>}N&RTHQ`rd⍀yүM Q&֣ajJr2MTk$*0ݸUNdjUy32äʙh.ﮜc#` ygN&I)bR"qFs\{RNQ,II~b!-CE-̠Dgv-9򣨐j#dyWFpE ,CZTRĖ"+:|" İ渝?J!MUT΍etyKbUh5!bXhA3GF#:7fk,xdg\'9PuF^BO"uBD=)E$ȗ1X@hUVOF(zxZ;(#&J﹘odo>!1~߉܈;g.6"ZD!'&CRJ*g@K2#P'6P kˑ !rLEu#sIHT%DhL$!"BME v7#p( 2$ŤH"WFh"6 dCtRDrN\ Έ!*PrF[\ .`aZFJhei2<=r4rEO[Ӳew)Œ$8}6k+/ ʖRA(/2t'#0#]b(L/@FVahʘ#ș2]\p']he#PPDA28]%D`Ȑ.Q7Fi6-ݥ@D(R8pRHF`F( ,8{>N'FU*EDn4RD49\Ǝ)^0p@]:ioAX g/rae6gF`"Fq hvȊN" f4bi(G^2ȟ"`&"0\'Bfy| Cbc!Jȼ E{8#0#P23P%)s TRAAcK gD!EL,-#kăz1ܒ!Cv vj!)+'\\h\U|;d%0#0%"Eq+*kT(0#E"e1!*я w|FZrKFng]t鷘e;G-Kp QIZpUr`F`J08IC/{lUDy"ELJ]crB ,$$-3 ,yoHc;jA9Ҳ i N89Q\.0#0@':{.FX"+v2 Z95n"_zz3;2D̲RA^#s y#(M2d#)S厞8)iPfU BF`FAFf&> eBC F0h04 YHf;U#0#H Q5p:B1)q1nH5XA#O' )#P0;G좕$dr4Y}4Q@M$C7CYҧeB2L `F` Q#\2B s?>UqEH΋=ND3`J`%CoWYJ5 ɠ:GA\1`2Hq0&^)' Bs"-„H~|eF`BKp!%\C`'?rΡ%AI"~ϛF-:hCFբzFbG!H4`a Q #OZ(M V$2Eŏ}Gxɜ6GF`G@'Dujm &49Hg ?HA^48hX>̲2SM)bq%DI2 R#8!Dx=p`FutBC$^j\HL<1!'_0,'!1ȌL&;=K19orE E.i8%s:aF`|9D%0ڋWs\J9HLMȲcF2@AMD b7ΫYVf@t"4ƀsXFd8 8ɂi4KWF`F!.Fxt^$4As*#L|YbJ(fCZ !2tQGc1N # B%#MF$1%*hq4&DN0#0`&Dr3 jLG܃$m>;WI##P0ikne%)6s5F`4-@dkNiH$g q2#0$DBD9ؽ{\ye+bVdC 9IJJ>l <3pLs B4{PZ5Bwug hnE%5DB$,CYwti*X'0bQ0>51x`AUVHyh#ȉT# `EH L$l"AZ5&D 9;'1bEY<ȪDKO?"źd0]*U"%Na+_§S?[e"C:w ޶9g@M{9='))0rð}S|qgݐ:uzr {~ϑˏ#`-!ݯ>o@%D4}^⌃"ιLyNH&$9 [|$@aNV+!Jӭ&0&F2mAy+EqɜA.?ȐD\%Em%Rzxsco)DB0Ez~7,Yt!(S&V, N_TN'~FtCou!d?'-7m@:Q "Dr)%r; yF.j"mãq-X{]]EfAzoҫ4Q. t lV*vVwuyɝ{wēd2LIr7M|PI6}< 9c~}T-ܳ^ac→R^F JA.s A/M IQ("HVE :V74@$Ed UG^4qzl IG>Jẅ́RSi9.7cꄈwDRXt|~?3? % H_6i;8I'}eʨ>Z5]~;[WtOJbeU9`u[.m/})3qcSժU ]Ww~UDR%*}R.v"E()=/:B!oW{"Bt9+[# ܫ#?se ? Adh8jLE89D~&, #^7AbA "yH)lAH:_'DE4~ $v"[[$ack4FRdDJgT^z:جk̾}/uvrJߩSN9EODW}W疐xǾn&ɔIuϺxR}:cQ"پd{~%j&u薅+YgdEI zNm'i$4"DrtRf^&&ȩtByń6ra,DKKg$YL$ ڔ}=f!-8Q4P4F?k.yNRyb ukS֮['LY/չSuM5LT嗷7.$@׽0i-%ijsyʄcJV \Zz[Bc{.nYi`;1c oߩF<3{MJ,*WGTCPFT 6T]_'7z:pkuW?cըI3٣|C%)ءtGlBZ|}n֭*U"Prj=b@}?4qaG}WZU#4(^,z;Zq9}(6>/ڮxP^M7Vwk{s5o7οRb59 O> UN}am[wUfqj9 ]%YƪyvԳs zKT7IVW]WϾD=8 I(8^ka&` "B,>zô% Ui)aB @>Ґ*Nȋ Ev4y2h5z(O  ү)';y]IXC>ݧה ItAKDΟPPAtCJj:aXUawة:uoG ?bNg7{{:b'Mn|YlkFenŜ2 S3fΒsf&# `پ} " "X7m.y4iHmzq XG~XP`HKjyDt.25~b) P[ ͛jAZ Ҏ_艷7lAt|v!ap1b峲] "1?Z[|@ڈQ{(>ǎVUVhXoW]%۳4yvql Qwq%KCYY ~@2yX^qb,C ;Wx Atgj#DCNۇ(cH  Hb(# +np  yDp|y?%; }A*4DFAb싴vJʮsDK5PWG?/mݽOn#8rU]^ݖ͟Zvā9oVFF%Gn9稏?ڼ%رܳ+[ j"?э2aE^?a9G(Ick&y:u{ 2yhLUTQE{fuհ!NcϤ-v5T?jښܽvW lu9]6=Keuw]ں޵5czs_V\%C4կWW]yKعKAl>-Wv6[!Ӎ6]YoUzG%/[e?ɵ'hSsF9JtJEDemذ$T۶mVn]tIϙu?l֭ K$K/D߰}w֗K'rС#y L UtL|[Eի5>R/\ q=!-zD׶N;MVXeI&@K$}6/VC:"Gq޹k|@[_շ{7\цLl9%DiA3T/j7YG0 d #-#7IׂN*P5&*ɝ8e'r,v͋)\:)K.tD&U疇-Ri%* B7Ӧ}ha,`u5p 6DիtUnP؍7?ڲVz7۲ySظzD-YLXxB!+;O]ʊ2 {FdB4AA5f(cШנe /0d!B>{;Z{j %\os͈|9V_|V`Fd^Ը+xfFMK -yFanؼ93Չ'][([&lAS!X*DO=}X d$཮ApOL֑J>5kJ ^7_#8,00D%ƶj&F^BKf3e.Ϯݽw8<0>)QLL "c1c]?XޗR,O/ .)u5x@Wxi z vnqmi ~W$A˅q>!Az)4A? u>p'?ƴ ,=-o>rCHE^ ߌ!BdXczv?p}ӥhv}]m%9Dce Z?`:g ֭X 1qӌAzŲ%yQnSR4A^ͩ%Ʊ?0NU|/ ʷ9+{`h`Àyg0$ &lyD8n=\?a|,T5oEm1z R#;C?_dЈn[=nEӖjAdOmǢٽC g;Yn(.;Ttųj2'm8<@OMn7I[5kD x *еXmqəH9G=mGrV7N` G:q}s_29bD"Ťw$"{ i' JylQ.l[,bsf Z]6k)i;)9k0qGkۂ*d*v+y" "ۺ:; Bco󦍁Y׌nmfB,/̱X32e Ixtlر^EMj G 0UѸ!?XV`aB[mz~:U,S-lAdXv=:&֓"sj}ci2%ٵzёsS6{i[l -`Ew Ys}OOG$SpL :sFbʱ[7H ;[g^xG^&>u"pc"" H/t2W.s8^21lc L2_j;)cOa#(K 1|smθ s #`7h$ jp:0vIIEW_Sww$r  g<Ϡ`[lAdw1I^GG f͘.^yGVp~c)BH ˶]-s>(؍HHɯ ]o莥G<{,?%عS^Տڋzl!?(/lR}9h1aE2V ԮU"GIOAƣ[^\lYuM_n ` "V@]c}UmEH-}ӺAb '}5B`E0結F+#BQHo {JN9' (ts| "Xc!RNSRngfak]z$A4p`D&a嶚G~0N .3 ub9b,RCnǧdtC$ ځD6w?icl+/o(0V2KѮ^ѝXÿ>ToEbuf& "0ڇsAKx06?ȮˠΞط+䪕M1r2{-9"5v'8GA7 f[R`\9LJ N^d]]n# AdvH䳋ؖ|:e{Gޮx%)5ʸX1v/7T|OIa`$hѼ~ۻ WvO CsH Kxi됒.s"fD 2GDv`Q>!}39ۙ.e "Knlߢ=]Acwv |4:(َonM.S?[k8[0a+[ovcԶqg/AfI/ \P]eb" FPb|K*Ud ц]TZUƸ x :ErVw?،=z&A&Bdg!zkT-Y}RAsEէoNsuaRW_}UznaÆUe"ܛ-[Z!͞B q'*y4iH]v%:2dzvQp6mۻ`=F (.1ysfO>I A`,D8&]dgu;o7mUG],xjyq  "M GHDoȖ,) %.I K $[(?6"e.BZuQVxvkBU4K.''˜ko/(;{Fdv:{}֗A {[CTQfä;uPl!wg By11SOp'E\BW-DoZ,o>|X,5h!HE!{ R7x牂3rO *Pۭ4<v\0hjh Ad[L͝ B'Epf{_eՈIzפ8p^ѵ{gϿޡ};~OSXH76`"AMB $ R%ĢExu[_H)a O&~sB "{JQdxC2k B7'&;n分-h,ÕrPؿ3?HUÒڽ[,6fIn[jZlBoŲܷo5zڵkG!rD%9Ǔ]Vt)۱_vPwk\*ۍk?i/nz@5_ ^=]wavySիVDk=O]xV+p̫d[Wӧ?2_ZUiF{СCjg)(UZ{ L-pr2cgbK]g'3 d|d2Mnj|~ Pz9pfz!C#<͡'€ں±.ID ~Azv7L18lJ(wi 2 sYA6uo RL&k wWWa_Yfn襳JATo$^( ba/#Qp1n^c;tl\F(86-D(m%2ӕˠؤc5P4؟)uc>zt?N+߯O76>_tq`|4If )=saΣ 4,!?`oūq4݄_O8X܎~pown_t=թ[pGWhѰ&".p')kASewc( H]Pxjִ?uw{_]sԬE\e5k֪c&OYS $yXːtIk9bx>/xLoUbe)L!'`cWrׯoou{6W"hyw3k#'BСחSS%ev$Ui^ƛʕ c=ҩSWsj}{+kv;-tAd2ÏH̚!-WN isazA Qܫ@j P?ϮT*AB*Dr Gc>Ε=Qu0'͢M&OyB>U{aӋOްA}u]RJO?]zT*hMϫc=֓G7(M؛?HB(x+@{H*QXx43YMj4yBWR. ~:B!oWx|Ԍ?ncZai;[WJ|IaAAh)F9[## 〰I=3g!)tKAdSI:|ŗoQN+.8|g'4B8=cD薴Տ?>,٪H"g~3x ;ϿB}W3{#'Ï?}_S}:SgSO=5ևIǻ;ν gPܗagq۷_//n'e+ _5m# mK9eR {.5QlҴ̳#;Z [󜾆jDž>wěfؙک&Mu7T}WҾKZo|կIשcչS7gx,z7ά|S }uv5lăjn[jU՚Ԡ',SLR_ kש Z=|m!1a1EO>1QQիڹc*x$v(|; d,DAc2`"C$9K|ڽljvIc "4%F37)(1~hUjOa#ԊJv-?R6&M/n @mOl䗙gX* X#~nʕԐae'Pe˖qٍy9} AԂ%GCK6m) o<0lN!F2&UqkaynuvTuM5j FL[mbOcNթK7gyhӦ?L$|XźXx%]>_{ "zdߔU YnOzqcT*7Jt4ATk@{ؤ;j? ϟyeEQN;HzHFc)}\ %1 s^AE zIBDžI[$.sD9S}avz)Dɒ窺ujkBi9CL4URi "5.iq'_(Ge֭UByU[7/~mEqhlb 2ťR/g>?ĈaCƙImYfwѢgQ)Lê\rH{v|Jߣ{Wm)j'т艩'yvv}Wsdݶ[VUWU|ų*9{v:WK&|2 DqX># bK땒יŋ˜VˏD&ab] e˗ -qI <_.͟}7hKg,TVU)?. >­xE+PN'v%iIrj_pi԰/]̡[( "t9~6F=6Ƶ.\0Ws^ɒxH׀ɞ}qݤiλu7e3 2$ J[LM+rbqaM+)OQUT "6ܙXV,Tv$O?X=2rrMjԣȺݨBk6.n+VmءfG8z >t_2ibFulK&*h}9L{Uu5u4ڶosD'Q0!{LzUWy΄Zr݂)e3{fXW-[_V=z㖭 Vz~$Z6nNjwɂs?a?ux ;˜oC]I֮^%u51G>lW+G<}E'vc,D%ܣ/j.[ofr# ";\}8~u=s$QՒ e$"(k3uN\.qGu2qi  _U3ֵXD5}-FuQN8AFٚnusuKa[^tK inb}{I&[7(?Q}rD|!U[NIcaA@6teC7U+?O oyikWu.k†`aC쮕ϮX0O,a峺tG-9./Q+g߹kww U+º">rqlk;s}lHFA-DogW, |/t-FAH Q2clD]Pz|R|~-Nne6#dP77,Xfj66 ʬ}،+| ` ƒڡAڵd?DvlE+PEmwcn`ƹa_$'A!ct!lxߔcT&gkͮD{x5 2Wk;p[.=cΛH0$(x9 d. ̭t)~&cxPh&RymAԠ~=5tFן>Zm; Lv~QtCT~QA{0_H[;VAI(Λ/祑5i4&S󖺳M;)i[ڞꞜ:EYDSW\,hl v3㓦H]L98%WB\]_ϝ-eCe9ƿL *5rD;!DgI +PeE5"lOkv 0<~9]7Dӕ}GGvIAٌ'% daPaE@s_!yz#`dcU];w RCT,]D*WޙݽVZ8oDc= { mAGhXِ&{}XD(rJDKT~ǪQf8 0nvD}ߩ?H"ru{T x\q{l!칞pN "H Q2clD]Rz{>41Da6vE~T+$|?vtDafk[/g[BV&{"E=a;VAdqAeäDŽd "ӶQn{^]wA_m _𘗶 A'S7Vsf=q{q3ndԬUu oyfNя=*ƸҼޗIlo!GErs1<͝=u-?TM:cqQ؟pիb~ihch#$ "-v[MUFIe P BAPTsR. Aj]ʩoVғKճӛ`. ڰy@mڤ*[C _\7"P2ñhlхz-re˪#|"Ϭ7kD=xlE2!a%,*S*-قs80 VsAxL`c F)@p 4~+?q-sgψY@}ÇfIvmT]ͦ,A Q$~A.Zj2vDbJ(T( Uu'bH3wo w^ït[aw 9 t[v; /bq OsZ0?բ3>ZtV=G"DkcIVZg~IB?+V>f͞>l-ր;Z.3d9JAm`fs|I>s4u]RPߖyY7ެfO*샵=TjUq%]Byd;9>HnqO.[^ԫ[GaRx3fx0& qw:2&*l F+kv3@)c0.sG޵{6}jZlN9d=Pt7hСCjzRg\>&}R$ח+ke, o +rvUk{ &L@<^QsmٺU $ ;w.8nZ<\F(6NLEQ\06u-*Cu֙ICO?3N?]zIx \L[NsS nw+A߿_ħvZctc\@ǒ?JH4+8(˂.iy? q+8FϤPkA"GaE/z +mJAUÂ@j P?N$@$@$D̳@ ʸ*cIHHA(x Dy$@$@$@DH Pba$ @ ʲ @Pŋ$!  HgD\;, Q $@$@$@I#@A4< d ̪/HHH (ǍG@ * hD H Pba$ @ ʲ @Pŋ$!  HgD\;, Q $@$@$@I#@A4< d ̪/HHH (ǍG@ * hD H Pba$ @ ʲ @Pŋ$!  HgD\;, Q $@$@$@I#@A4< d ̪/HHH (ǍG@ * hD H Pba$ @ ʲ @Pŋ$!  HgD\;, Q $@$@$@I#@A4< d ̪/HHH (ǍG@ * hD H Pba$ @ ʲ @Pŋ$!  HgD\;, Q $@$@$@I#@A4< d ̪/HHH (ǍG@ * hD H Pba$ @ ʲ @Pŋ$!  HgD\;, Q $@$@$@I#@A4< d ̪/HHH (ǍG@ * hD H Pba$ @ ʲ @Pŋ$!  HgD\;, Q $@$@$@I#@A4< d ̪/HHH (ǍG@ * hD H Pba$ @ ʲ @A/V̇HHH \xrGqb6O:K>x: @>+RoZD׃D9iqׅ,(?Z-l뗏NcM't83>'Z2x>f    +$@ `ăCC gDY޴Rg_՗]`]WIHH #\6Qa}^3 D!9DQPq @ cI (Iy  t ҌQUC$@$@$D LI Pe~ HHHr'@A;# BIPV;/HH  BW`QlHHH Pev$0D CˌIHH҈QUBD(je!  H De$(2Y|   PńH *|u+&  H0:b @A$&!  xD_H  pe$@$@$@E(ꃥ!!@A6U $Q2kdD\{,; @(b%t$PP @!%@ATH+M ʍ d lE^ $Q2K   #@AvU@z Jz`)HHHK(|; d, :HHH (II0 *Lk%  K= ;IHHQT$/M(D @: JZaH PA%$@$@$@ '@Ap< d& ̬7HHH o(ƋI *4U %  BMPW?/" ̆{HHHQ%J(8 @ JӊaH (R]@@@} |Cv>uD4QFU,sj."D}a ][V4)KDDYC 0"+RDA0"I@@@'D>A1"7|t@@܈ 6 ~!AZH[K͛Tv-ʚ5޿O,ޫKe|6;pTo9R$  "0!sfes.y<Sx(i$ǜR \_s՚ud}?"Fh(4q41 EnZlN۷e'Dtܝnۮ}_~޻Z頕1x`?U"r@  o׮]O,[,ޘ[ByFԢY 69i_zE4o)cCX6i0~Qr @@Щ}V1hZe-MǏ"AX$Q2D;޽=z]{ia5jTesҌYs*/OӧޕMNZRHN۷l -;8')^Z%ϤIPpTX[9BytiYsȑ!Њ20j:ZGOH%KޝvQ@@  %@$A|+C 7'NYU+B޼ycǢci A| $M1[r͊6v+~ t1GǼEzuo4[9V9Ft9֫j~w bVNEٲfq:|k4E_ؐ B?%VР! (^:wlGiӤ޽w6nJN7+SM0/ "ۣxrKΥr^]I2Q` "gt rR !-ȝPYو@ oAm:zijJ.>H߻wN9#3x &̙3MK* u+V,SMoܠ$I 16[._I6oUYh`>Z*?G^z^~-EdXQ?}u¢UkrFLɓ')'qHr:?\+%OχEX˔)e+k'o[tM+ \$LҦMCiRh,+w 6qyqWw 2P9p©Wq2cχ`pUy,CT(Q6ϡ=w^3fLTV%qW Ν3VLJ)N̑ݰuRkzziSP8wϖ;v߿Og,JsE8kN  rAǏx`mkcG@%غJ.'=C~1#|22Xm:GdxΟ/kؐTjѧ"&띺k^ .XİZ/M7.;hGEKY۽~H='mܿwY3'*M;ޥ˔]{-T0?o ]yXff[?'C)S0D:|7kjJڻL6Ln#sXXҸe6:b=yTix}A߱6"ATÁƀ 0n]:R&|WfFZƓV}0W|!(D.UR;Tn\^qɾ/&EPim=y[$a,X$9 d=&[*4YΛ3xJDuk$jgje7GьYЄISo\R ޕgԪmGj[a6yŎ_cm`D8"4QI U sfOPv7޲v/ZDL6 ۹x7WLՅfA߸Zz\AxH/\097ōc>{:+g рACŋteC^9¤ϟC}w*̼%C(qt%T>lo7` /Vn#F y., ʳ4а /_C9qu4tBJ5jP_j%W>|HVFjb1WN z /Tܐ|iHo#{x g׿o/Mȕ~[KH"j:2Zbx@ !K(P&d_ PI2zO6p m+/,xEhڕsgD3OZ;D)fPMETWԲySiяXL|%"DO,r_[ >L G&'}}fy9(|ZREqD̉EnLChZ : gKAeG8a~7lG H1 As&JbB]f-Zo\#xI5jڒ9sԣVx./6%WAWbHI9'`9,Q bPFfϘJEWU|< b˗LgDlUH)g[oyC9ԡswra+Adnwi䩆Cs:>l<Dg:2A?";p \˵#x &ΛCyo 2;&"7Q#tЖZhc|FdZo2f",}fE7Uo%垿xA#1qeAt?œo h̨աg X0F BW=Z4y*?(0IۓWx%e߁tiBxpN^:#u9z6b?f+wf_;|DA|?7;^bA{k{A2g~ 2;&"7Q#2o͈QciނEZ^DώDbeOUlb'6u1JP+s<W_/feaN>C5Ђ>-Ol 6y6o:w멅;+ǂƍsŸg]c? rBKA X @njOONT /՚3gIz% ~ nzH)V>J(& A~OBq},hX(7}Do*|PNEd$ݓ׫ɧy u~&dh;rImf^!asX mڷKvbF9>g3%LؠAXTbqYO\˟iz\Us 酔 ' _Zg9HTliP!.}f?,$INcM?7O q3ƚ@sZ!F(Я{.̛gi :k1ݱs780l8ƽ7x-u,ϖ)Q͘:k˜ VǠDgΦzM/JsMNs5lD\HU@(1IjKlp[NkѼCt`a Y 6[7֣mݶ]+a)ThaW\0˟#1M4pMq nٜgJ׮K /5$޾}K4EqijUs|j.ݴC{/RVNokJ.-qh2nCpdpZDN;4h,Ϭ[ng0H /^K @ta跸gl@vD|R D=@z1b1wVixmb-Ad>Ogu$/2N2~l`mP0 !޵5mPK DVK9+WQY1;[NӧWwjP!cmȄ#AtCs qC|.BO-l0!vl-HNglGyyyQZlZ6/WF+n3amCY1~i,Aٸy unT7f>q&6l EŪ6&0XGۻ!/ vZ!BԬҩkڲui:p߱VMsԨQqDqǎӌYsjp,X;kIm>gğ  n*[U ptUzī%_Ė0>@l '޶f_H:^ <}*ϖJ"GPŝ( /|Q/A=lj[߹sˆqO)VC^?l%_E3;U6%*,Efvlި /)y6F-7$NvVzso_sc1(E~sG\/ABQȏZNI) [zk0z7 I`#AlQ"/B nկGhT   "PH(*.@l̗]Cp   *P@( ".H@/بAMhB t.-4@ @9i  B>%+ qز~Ts$AtQ60"<4@@@aDBBp/D5-+"wy|!A D Db |D%8"S     0W%A#v_h!-"7lt@@ܘ>> @@@B 22@@@@S !O(-zDA5K raCA@@Hȏ܅4   MȽ A@("A]$A4Q r֑A@ @z`!A,Q "3@@@ 9@-@0    # `m.] Bx7 h  % pbDN<8h$A@@QpF= b \l\ 6dO(1z   @AOMD6 0nݾC /7oRڵ(k>{ďO#GZH0 n  BA @2.ӝw'ŋGI$?!Z֬K# }1bDC?S֭Z\ͩc6ݑrn#7xt@_ @  1 ]-XD^a,$7mD-5aL㌁/_y ~ ʔ1!eg~C(Ah9VQ` B(P0} wL߽{G]{8T/%Ә(jԨD̥hU,_<ҧO+OHo@a„QIEٓ4i*T ?.T+"|+P@4oՖN:-k9=7[쌂*@Q  oyeaft)JyEݛ7oXt̙9$oi3fkUY>{Ă.FZ޻Z項^C{WQBYv}:sVՋg5pyQpq@=  "G(! !%VР! (^:wlGiӤ&M[iteJI‚QA[[H#njӒ-Y8~*gD g A 6 Ay-ȝPYو@ oA-YzijJ.>H߻wN9#3x &̙3MKu-T))VX߸AI$&bl-]6mުؿVϑW^ׯ_t1,Y,VΟ@?}u6UkrFLɓ')'qHr:?\+%OχEX˔)e+k'o[tM+ \$LҦMCiRh,+w 6qy*w 2P9p©Wq28r9/(uꔲ)w[?}u~{Cp|,0 [g'vlJ2Y;///:v$ݾs?~,ǔ?,sݿ<>Ԃg*<''OЏrmPE(T ':G(XT[SkױmްH 3{te zjg}!\ʕ&KSAi޶6vp) [ώDrB(373ʗ+#+'ϿO9osKaCRU|,Gʕԩk)`q`jl7mܐt@x?*X9Dcq*`J&͇ I֬SA' cGQluԞթe7bX7M!Nhajj u BݐC 8 oM.YF>f㕙*ߐ'-YXA(Y *]4/ԪdYo ;>UAf1OoIGKD's[> "6+e כ֮$o` "/7jɞWRmD,_@܂[ 3: ~'AwfQBtT`%񖭵6heĿx"bW4ڻa;Ws93)rH2Yi|7PV/+( v;o❚'vx¿zmW@ڋ-zzރZ5<Q֬YhزFxP[Ĵ Q >Mҹ9sY>d҅%JY. 3X+j8Er֭2_bS]VԮMK*qa'C6^mڞIyuEm㸣ܘ[̞1)$k HD/ @ի`~wmDM*? A=? 32?g#e!Ѕ|nfmZ|Oʔ)w!Kh+̂W6])= lpA<(gD@ UkA-78|%"Do Ȃ*e<(?+lz#nKt`D_s&>eEO稔 }QPAė RRr4f:#Ǚ.QsSSe  ^ k[pèӧO9e $U3_b3͖Rx\M͂QԫGW[Eha""\rgFxEv=>|8ٲsZk!Rgj8 TRԋPl:Nlm0 "*[ KYBg$o@o٧ִbUSljsI;c׿o/YM2J(? rAGADP x۷OV:s(EjhaTbytA6H(__jʓo>3taÊ,'s;> sy{Lb믥*X>7hѸ qEhD}snp D1Nh%;An>? у{=?"6]|9,5mFkk8wtܝx[#?(0ɷ$8/ӷ A[e4ޚwPA4v$=g)^z&Ӛ]|4qPH  \~DA\jhZpmU M %kɔ1Ţ*Gr_X1c(>?Ëc,̫ΘJhtɊ*(/tdх>>Q`7As"ع[Pm,l3\rѪCy hԘZCȗ;!}hmykXթE&e9uЎkOχTx)-_$-X7mP^T\[kwo#l+ =ףAd6fo c'MUD<._UOf*z1b1wVibD*Ė ;5qR{ĺCn,y? wi׼N_=YOz 8/ i6_NA@ D @Ϧ8``l0!vX6L$GyyyQZKVp˗+#}sD\Εר6!ucFh мaNiȖ >ˤ/cm-8˴;wm.V\ CjW9LZlNʧYq8V6*#{6YҜ R$ Ӏ@@#A63߶c'͛[z6n;nDbk M[6-ζL;ʕ=Wmgi tJ^p7j+obSz7OU~>opZn ,nyUh&#Gnyq_ulF/U&[Uz^ a+W5`J0J&:gx|c|9axqq<6Ųg.8V5$/VYgӫ;erɟ= -tƬ?l~[aUIl3_k^3"gL5O<<#ztJ(!eϖjS  C}=? .$pQD.:ph65&A@@3NH M ?W^QX>T   B?j%A2C[9|D/^ɒRt)Y&L{!&‡o3 |H?|b+f  [\e|F LUTPN(0%A_r.]IY@aYzT@^W#"QvMO=_FkUK4maEܑu'OW"D  J@D7@ @6QjZQXy^h!!bjRX! "ֱ5̞EE[=!h]g~ԧWwc.-.8=WK+UUnްҥMգ.p+Dn5,8NqVM9{bw,D K(aػӈqS2v*T/ZYE&JѢ}{ 4AVNDs\+0#twQe"ФуaVy?|H]z [bĈN3&gΚ%#ΪpgAs9g}Jq%޺  QhQQ KV]d:PL8rM5Wܖg$zA=kf:{ oި-ߪ&БLjE )S_KSͬD UNÇ bE ]rnH{[n˸Q]+4j8J"-]4ϐi3h}r;Gƍ ݻvx|nVz c9r$.PغQ/]-[сCƮP7ToB+WQӖ2khcn7a@b0$܅y v/V.H˗S'AC@ R[w%Iцw/_Po+ zA4fX5~*=6ArI^&qCI>Zxr9/O7ʖ)Ukiei<իVUkI./^[w+-iR7L*6qՋg8@T͞\r9 agϝɤ%{jPq:=+-֙4Ї HG m` AqmdE>mq%zA4A?Q~CeIAuNK^iVJ*8LX~ܾ(1*W Ö z)%V*4| *?/ ;N>Uk)*˗/_sԬe[OLF4x\k,ب-qFu{NLXw 5Vc3uxm^qJQ)er_b V4=}+Kȑl -u-Tܗ%q1p   \zx:DAVJJSj,Bݦ;i٪uU*PeTn#XtdȒSwnwʈa+pe̐X8(gKm۱w*)n6ɭwǎ5AfAԪmGfNFlTlC>M6Sng6[%H}5+(S үD5tXDf̦ISD֮dIʼhݞ "4/ծiXUZ5h`>~ :uMKw2eZ.$-@xnܸI:-6_MӥG .G !A<4j-+ʐ. eG~ٞ r;IfY,,V^ۭ3Xt9c<ٛv[q-ATZ-b ԧ^=,[T9y V+DMbeX **Om +-> "eߜөp ACiيURx)w OQނE 2k+K|>G8#"g Q -FBs[|f5al:Y,9g\=xajT_sʈSifDWąJ;w۶D*͍7ŪI:.aذCԫ4Peuf5Ҩ7L.krM _&Mjbo8_mM>}"7=t'#Ad怀 X~fITh!7EwhݔK}D5]>VX/I,~ 2CelC+cKYz [8CjHb2g[e;6P9V8-%tQ|8 "jo~|DFmk޴1u܁@@ T Ë΁ @_r}A9E)'Uړ/o=x5i1)erU:zV{B s.m۾SnE*VbŊe?`4ɽ{-Ζ b/a̩MNIn ]B=@l݁=;(aTeǖy뉓k d>CRf׬XJYŅD&LYb!_aJ/͛hf  c#ArC@ \˺MҪd,v4CyO0aWif+ 39"8%+i}*[D6#Px9qIj y%Ė bhUOȑdfٌ^q:/Je!c+Dt 2ןTH!ս+%OL3ui` ٳeo[96"1O49"x[_ue|o a!k̲>I}qJ+ݲ""AB@p >yKh#J#G$խUU[8Ggsx,2q'la )|V9vڳh =AlFsŊy!ca4o/yłYW8 ѣk+J=EzWU\kޟ_5g1Y(-_bsDqZ>uoY=K7Jp!D.4Xh*'mkj&i\;3F9g5GѕkipAIc*qҨwkEa**>xIS EI…g6 Ѥ cLR6<&_@qġԩRʳ?lnV x2 #@IDAT>J3W 啫>KS*L=\ gKq]xVyS㕛BSu)A2L8UYGuݎ c׭Hluf@\+ @( ۩}z(S SdImUc';`C<(8b˞- E[ͮG~R@T(_ƍa? aaDj[l&El#bĈv?Fb 3T͏8"@3NH M 1T/%q>na Zf5*U(bmE  ~#A7^H nCmu@i5Zj6(#{6RvT  I9'AC86b5f͖UZmw[LqD!>h8'"* i[f2e aX!À$A䂃&@p ʨ@@@ @~pRDN:0h@ T( B3 }D ܚ[?:  nCm /pMD9nh59 G @@@ HGmlDM@(!AJx K}=w"AN@,":4QE   A9#AtC "A 4 >ϟիW+V,dG#Ab@p .z>gc% ŏ=eʐ$NdL@'[\e|F LUT :P56А(}| $ApQ42=n㟴avSHF-קdIیG` >}jk dq6yB G ^^^t3yd>|x:Y,[A )ӎ]h)@ A*'ALȫBc&M\ҒĊƏG޽{/_d\aKV#[f--|DM#Gɉ8wϪ4nٌl*LCQ \Jk7lЭ[e\g.t5fL.gG2mٻOnHE];Qxdz>7TnzyFH9E|Kimtaⶱ+T0?3ըlUWP-eiǮݒo€HaHB /}k~(^)\*/9r0?)\ u܁N3DSEg-nZ]g~-_YiӪիSKE   tmo'ms;=e:zϞ/ӵiސ <'41 %4Kh9R$ SΟǍeJk,V*WE|TZVYgeŋԸyk:sBcEbdj~)_f:zBڻٓ+W?gA4=wת'(^v٫e ">Cӣ[gjb|BnϢr 5Ҝ- ~mK-5kZb*:t(5nfn^GSyoOJ AÌN @rid%SݭEA *-EW5;'ҽmڼ&MBӅ8@_?>J4G ʕ*hl ^j5jBÇR0a4hM<'+W,+/ӧL,_jߙY˶2#{;-s5jחmbFŠ-ݏլ#{NL}҈Qce:=S'׶'TU\RL!{z V4=}+KȑݻO -u-V/%Kb|t|5A8_o*Z.wm3;E/x'о~Њ &CbD$Á"bW9t*W~SlebۗxWa]'˿>IǏ~z؆b{wwo޾ۯO̫ׯnJ[zC2Wg㮉36c6oނEVouʴ_wcˑ;#iB@ieع[ W!_TPٲMעu;C8߲ eZy7Sgh/_1GZX%{ Oog4AwI2f q^BN!Dٚ-Ƽ:\?!j|Xe'5/P܁j^{ÜC?9 {~~PL(h b=z=9\E]e;]/UVNQ &zA4fDYN5Ѭe<8x rOun~}oH.ۖ_OKD[ {X6b>X܌Yx,9W\2vex𑣲ٳg(Aܘ@` 0-Ip_0a,+ƴ ,iDɿZ|{"%Βj8_pD _!Ceٳ߿ƭ:˶)U׮bJ7l+Z(?5ol9b?{̙;F\ϗWl/rd& 2Wa:~լQ g"j4R`%ծP˓&u*a)U8:VAR횶ި;~ԪA̝:vЦtkmQr <7n$!dЦ)}h+]h]҈9`xr-sm~܀ACI Qҥhl,AB2A>萻0 BpXVhieJ*ڒFS?2T-ӻ b Qi`.O?nf}IT,Kի)9K?31z |D??Qj 44jPz/FoaenդqhhT&6:uh8|1+W,bT`U&|z zfr|z5}%>szdžZ4oB]:>'7b>r]@ۢƌ/mP4uSRH:ns? z @ޱE@ @"Ǣ f}нܙ+6]{܅dt(g"\G@l!F N:M{ӧhlj(gKitJąJ;w۶&*N=oܼ)VmNq ;(dž F 0<~T]׶4oE!^:L bi9_劶7!4}J\P5 Ҹ#dlB1 h[FȆ)X6lB޵t~~s;  sk%hծիP-sρJ_:$ê݌"&›ÇV"ǖ Z#LBcwCU)-sV P{ Kw!N׬$81%$PQݾh|*&?7AiVMSWqGP@rՖD3E´w6J?]ΧU>}pD3)p;/ gק{ʜ1Uy*0`~+U5g}UF7s.m۾Sƪb(VXjU޾E%@~)#$L9};;ڴ酪Oy%W>o{x݁=;(aTeǖy뉓j5)yd2!)kV,&3(G_:Kl1c7YIS͛hf  ci^&s82Vm,Šӛֳ4i qٹkvڌR[2gp  @ `M,Ac>x,!խY.ms>-^xEK\f0~4E ՟)?1ųLωKR3b^ %C`U9>9r$Q', (TW:v. 'e|ޅ]"W~ ,jϢs +,"Lq\s^*X KX0O͓rFѣG3sdya*UEz>yDKVڻÓKR Qr"N‡/SKΥL3hI&MF Î9LQ媵4E|Wi*_;Dy ee@ 4@i(hڍt:PU RY%@]l`¤¢HB[LF KUs:ōRJ)5CGX "se:|fZ+Wlŋ* g3]xVyS㕛BKj ėaЪ#_:~ذMQOq,[>qluf$a9 iҼ9+w34dHiOq[-HU aM(Ϊ0]={9,Fb6//gxq .r^mѳY>{\I/mU7@@"[T ]bjAH7OO*Rgc&>l@1S z .Dȅ M$z;"o+Ukv|G9/K՟Qqx8'" BQdx;iԘ6[Vj߷7٤@p^D;6h(ŏʝukv)SRA1 8pAD.8hh2ࠌ:@@@BQH'%Af*@ʼn@  =c'Ad b@ @   6 fQ"Bj$AV@ rĨ@@@ @9  qT&&ADQDd  @  wsp'D4+`!) r١CA h @-_    c p:DN7$h@ (B0o |#xpSDn:6"7pt%A()peD r1GA!DaB"'A@P *(Bg(Aր7P0DQ# 2Q7&@E@@={Eb1|XJC4'Uٻ}RSđCiO׭FcoPhZ+D7dM 6 -잊"cxNߧQqu9 *ݦ^SAU PK(-:#A0~枺!)B:1%ِO>Q>Wd^)srFRz3=|‡ b㧯S Z"Wvxƭ;˜+ħKChR(rO^_٦T"Qb)+:%vKF7KJ{n@ :(\Q Qqv5AmM8gmՁTԴLGj/V&XMiűlsAt;3+ Q@!/AS `M,AxH!e@J = 7EH@@Ȁ#^C}{EcF~8Ez ҿz GEmO^>:9{/Z7u_l"A{>[x~0 wT-'}}3\%᫗/;zxi{ϞX|,~ fgQs/+,?yvT^~k.  ywp1* 4fslnϝ;*3,f+/}OCܧKwOʟ3|Gal8j9jKSG)b퇖s%>q[j*׳12qv A.ZpԮR|U4|w4m#x=O.a(,xqE>2{9{-g`]xՙ.Id*)VvοGQ$ؖ%_)%eOVJV{qG9>C._h4urld&E^O^_YַB<fIӴL|j^ֲ=;t+*= ;Ӿs/ ǎ?]'?Dz,[϶ M/Eྲྀ%mtXMmFozu%>"UZq5|.fɈ g,TP@lNv|#%Ϥ}9]pdwugnyD)FQ?;e53cȿ+=tJϚ-~XpyFYSRUYP,GtQ#sO̰whg9[dǿfvܝC@ ba#17ɯ}G a-홚bE3ot;J-oUDCڕ-?xo+^Ɠǖɹ>N{ dK侬F#XXiÕ?i2^4n $ggfQI'N(|#O鿣yBxL\멥{rѹ[*}[tkyfgV,F;Ϻ#WICZE8+f3y2-Oʫ .ҹc V8> W P4Q16oi`#vk4bUJ9%=C(ԣf"is]zL:c #~7y,VoXfV3I "˟ۡ Sl1Σ|=$/4m6)ǟnw !pK;jHj r+-A_?6aʧD<{O%x_]ɹD&e޵[VTf Ca0ViD`21I%&%jbU*$VԔ$&#>|-Ph1 h|U0 0;o1o[j2o"ŷ/d xnﰐ D{\uF34g~^*޹~PUY>_Oy!05Dj*!B1?ms.ɟ{e }mÉ?jt=,XEbJ vMk2yHHmN!fi~Á Whf\qm<Y/1I.aTEsܵR ?CG[ !0%Dws+Duw9=)GYcѧm$t2kuq6v&1B[_{4;JLأ6s2f"3bSsvWwT7\"DxSӱq\^=DQvO>?}v{eW?FlN}ISVCxxh<ՎMd^| W]Hp4r Γ qA?E|6b/ٌ{kB{o?`[>% A}/T_Wy!0eDjlB`eT].!غتM !N[9]?q1ALk^A [Y!¶sQKyq}S,Bpb^?ԇ\prS o}Yg 0xF 'kM+3jp\|S!³λȏ~AwmC A^e(BF`tB>L!`?NiޣNʎ]c+~/Ij${"@@ 3~->Qil|fY;zPo 8t^l_yf4BLz_p7B+/_n?x3 hx`)YV pJ[<xHGrC!D2þרR'"D!uke!nKzl!/!WYx0qXM>Є^+.c?=b9rx^ݶB`*,C[ 8 DfX!D> Zp(MŸkQa o7o:; B&ڨ\iS0ܲpod{wuؽI!¸ιFa'a8L/ޒ݃"D1SH^pvއD :^'|mOz{̓yK8l7۱ضI)F~䅉y7^OLLKſqQo6Qa{3UY>tVC^y|/l}Of7`V$Dѧ]F&Y0`"<3K!x@/]g#aKl_D蟦W?086yI,lgwQ Q}lA1;*^zEo|su3xsg9/7"Dz35!Bd([+Dw_}Kj(=]fV)c̟I/tbCaPxh$&l꾂-u}Sc }~!c0E=R<+'}B0v:\]Oc[/3}>?"D(&xw'El|g[=[!D߰_ D~ڈ&V_ ա ^~ uk0Go&i"VT?VTccu淨7\=0VU2!0VN 9:Ta2 DuBĞ&" %WW[ށa:nvo~p&j᪯zɃ=L=œ ( D8!‹a.5Z7>s36LpS^uakP<#66,d@8-0.ҭ(6t>n.VUPkfI`S^q&WEzxAD@@`p|zm lx!s-XD+O>/N]t >to}>#|ѯM?>W&$SW|W{N>r釺ǏQy!^X-!$6x2^?z$D>!ĸGj{+թ8]V~htpo&|zѹ}KBsMO>~!-U^{_úym{_ mwZ=ǼyoǛjpWJYè0n찊s;3ז~ ?EGa {wJlT96"Y)7ܺcq6?؅JT]{u ?u}zB`9(!rNg)Bi% id QD ! 0eIg L*-t!m/5 KNQ9|NJiH&!0AVK&=Br,r㿾 6=x&Wu{ _ =s~8c!  Dg~J+`gB]u#hp" !hm_G#kuu[(:&g%ΓD9A[^'Vg1Z71 DX"'Bd@(! Qf q.&XEWnZPy'$,r=G9!a BR?D)! X3y B) l᳛ڊB@[گ4xZPIl%YT\F=\A!*5\d+zm,Yhٖ+m%B@lP !:C""\!YnjhI;wAQ"Db!A IFР!oI,|,om) ʊW! B"<(MO-9OB|i$&,BaCh<4uIO\ɱ؝dTtb(Z]rU_)DBt6PWB@! @C DgHqUYb9 &@!018'B1>!ѻ 0 )AJiQ #.ќ6tD ! B;sWG1!҇IL1sh=rBVHlb,62)]%!9TA{Y)! Lk8plCXCF6Q=! &k G1PҺ3d0o/1 o2Ci494q7u{Yu 7 B@! D`UW_m޴;*& 8p0\ JslSf6WB`#0Z,W2!":mЃ$:Q7KGW:u#n ]Dʐniw=tm4! B`eܾgo놛#<6yH 3 s(˄ HR%i!°8B+"/b l喹;[~vGcuB@ 7= {Q$81C 9JY)"vm<}!jn s|& ht!DonBĮkc'bwNNzJ7C,M(e% 9r޻M B@Lm[ vϐ SԔtp(oX VB`_ox$zcmp BD>I ]e'Hk64+# >p{uw1B@! :C`au?drC:͛7q($6zuf6>vɨfRH:JB`qگ7C#ZP"s[p$d$IޤQtH/n eDE]ؠy1EUA*[$P&+)c>A7ge7h J9R(+%B@Qz%թW4uvm< )Q2h9ay 1:a:rZc4̄Ot\sۨDs1/$Bd("ВpHCǃ񷺁H8z>QH,v7'%o6M7$T$,Kx$ ! {]O[;R FR8ț2Ao(Є-5YJک'cFaс~3Lz0K 9TP۩$)W%Qe$^\+lTH! P !^:J֨E5 BY&BHZmWe}mڞ&+/,d%ǜsq.z#W!2h'iEѐuGq} Ť u,1%beq\l,Atqz9z}We h:ަW!r.J妒 [q⁞KVLJ[I! #PjͺyZ/hsR7C2 e ̧k8D1Sus1l<*BD("@c%ji bi2Dk#G :53r^ A- 02`BEReccVt,^}3.Ajڶ͒VB@@L-sbԆiq)Ҩ%(eWЭgYumγ6QShgDLǵ}!׎ Couj0蚗c%:,rYfj\5-7}pETt92WڡMm)"2ԧЏ8ƇpF#X! D`x5 [ 0Yj<5a+Z*J\іbAj3%~%cHWl6]aFA#~SБEucJ_\I*f!/6^:,T2}c:u+: E'FО=)SyCsCtSW^Sڵ|t෴4B`JUsckhZ0 R\^k[umr^]'Z@QtjdZFNM|ehi&8W=pЭ^' 2t7eI=>l҉A`)2dMJs֨DiN;rެG> b#M1xsU 7:P B@!B2 ( ӥ)L 4{A;յ4Uq``Ӝy/&XNEZpz199`?\~aNb׵/8W8[49Zdi7{t@;wi䉨tKd]$*Sm|O/]M}K'n7cju`݂E)ҙhvР@! @`ڼ!H d[Vuk,)L˹$'؎ק2kPޒ6c "=D44us^Lr&ҙ@'[4Ó;ڬ$;.f0t>Q IOf̴Q }Dc!EEzB@!#@bZMYE7r!wmm\S̮f:8ƲQ1!;V B@ eV:VlEUD۬ .u^SBy h${qՎK{Bu>9AJ="FB` 'IHƇ?D͢J¡FҊP꺮_sjQzVeK"WޚG?\&Ǻ[D.Ӕ _<'ҸuF%6M7QRF! Eθnqk|MzAUaWd2>FP%˪na^@'G3IENDB`gitsign-0.13.0/internal/000077500000000000000000000000001477253552300150705ustar00rootroot00000000000000gitsign-0.13.0/internal/attest/000077500000000000000000000000001477253552300163745ustar00rootroot00000000000000gitsign-0.13.0/internal/attest/attest.go000066400000000000000000000245051477253552300202350ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attest import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "os" "path/filepath" "sort" "github.com/go-git/go-git/v5" gitconfig "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/storage" "github.com/go-openapi/strfmt" spb "github.com/in-toto/attestation/go/v1" "github.com/jonboulle/clockwork" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/pkg/types" utils "github.com/sigstore/gitsign/internal" gitsignconfig "github.com/sigstore/gitsign/internal/config" rekorclient "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" dssesig "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/structpb" ) const ( CommitRef = "refs/attestations/commits" TreeRef = "refs/attestations/trees" DigestTypeCommit = "gitCommit" DigestTypeTree = "gitTree" ) var ( clock = clockwork.NewRealClock() ) // rekorUpload stubs out cosign.TLogUploadInTotoAttestation for testing. type rekorUpload func(ctx context.Context, rekorClient *rekorclient.Rekor, signature []byte, pemBytes []byte) (*models.LogEntryAnon, error) type Attestor struct { repo *git.Repository sv *sign.SignerVerifier rekorFn rekorUpload config *gitsignconfig.Config digestType string } func NewAttestor(repo *git.Repository, sv *sign.SignerVerifier, rekorFn rekorUpload, config *gitsignconfig.Config, digestType string) *Attestor { return &Attestor{ repo: repo, sv: sv, rekorFn: rekorFn, config: config, digestType: digestType, } } // WriteFile is a convenience wrapper around WriteAttestation that takes in a filepath rather than an io.Reader. func (a *Attestor) WriteFile(ctx context.Context, refName string, sha plumbing.Hash, path, attType string) (plumbing.Hash, error) { f, err := os.Open(path) if err != nil { return plumbing.ZeroHash, err } defer f.Close() return a.WriteAttestation(ctx, refName, sha, f, attType) } type Reader interface { io.Reader Name() string } type NamedReader struct { io.Reader name string } func (r NamedReader) Name() string { return r.name } func NewNamedReader(r io.Reader, name string) Reader { return NamedReader{ Reader: r, name: name, } } // WriteAttestion writes the given content + a DSSE signed attestation to the corresponding attestation ref. // The SHA of the created commit is returned. // // repo: What repository to write to. // refName: What ref to write to (e.g. refs/attestations/commits) // sha: Commit SHA you are attesting to. // input: Attestation file input. // attType: Attestation (predicate) type URI corresponding to the input (predicate). func (a *Attestor) WriteAttestation(ctx context.Context, refName string, sha plumbing.Hash, input Reader, attType string) (plumbing.Hash, error) { b, err := io.ReadAll(input) if err != nil { return plumbing.ZeroHash, err } // Write the blob we received verbatim. // TODO: is this necessary? should we just extract this data from DSSE? blobHash, err := writeBlob(a.repo.Storer, b) if err != nil { return plumbing.ZeroHash, err } // Step 1: Write the files // Create the DSSE, sign it, store it. sig, err := a.signPayload(ctx, sha, b, attType) if err != nil { return plumbing.ZeroHash, err } sigHash, err := writeBlob(a.repo.Storer, sig) if err != nil { return plumbing.ZeroHash, err } // Create 2 files: 1 mirroring the original file basename, // another using .sig for the DSSE. // TODO: prevent accidental file overwrites. filename := filepath.Base(input.Name()) entries := []object.TreeEntry{ { Name: filename, Mode: filemode.Regular, Hash: blobHash, }, { Name: filename + ".sig", Mode: filemode.Regular, Hash: sigHash, }, } // Step 2: Write the directories // Check current attestation ref to see if there is existing data. // If so, make sure old data is preserved. var attCommit *object.Commit attRef, err := a.repo.Reference(plumbing.ReferenceName(refName), true) if err != nil { if !errors.Is(err, plumbing.ErrReferenceNotFound) { return plumbing.ZeroHash, err } } if attRef != nil { attCommit, err = a.repo.CommitObject(attRef.Hash()) if err != nil { return plumbing.ZeroHash, err } } tree, err := buildTree(a.repo, attCommit, sha, entries) if err != nil { return plumbing.ZeroHash, err } // Step 3: Make the commit // Grab the user from the repository config so we know who to attribute the commit to. cfg, err := a.repo.ConfigScoped(gitconfig.GlobalScope) if err != nil { return plumbing.ZeroHash, err } commit := &object.Commit{ TreeHash: tree, Message: fmt.Sprintf("Gitsign attest %s", filename), Author: object.Signature{ Name: cfg.User.Name, Email: cfg.User.Email, When: clock.Now(), }, Committer: object.Signature{ Name: cfg.User.Name, Email: cfg.User.Email, When: clock.Now(), }, } if attCommit != nil { commit.ParentHashes = []plumbing.Hash{attCommit.Hash} } chash, err := encode(a.repo.Storer, commit) if err != nil { return plumbing.ZeroHash, err } if err := a.repo.Storer.CheckAndSetReference(plumbing.NewHashReference(plumbing.ReferenceName(refName), chash), attRef); err != nil { return plumbing.ZeroHash, err } return chash, nil } type Encoder interface { Encode(o plumbing.EncodedObject) error } func encode(store storage.Storer, enc Encoder) (plumbing.Hash, error) { obj := store.NewEncodedObject() if err := enc.Encode(obj); err != nil { return plumbing.ZeroHash, err } return store.SetEncodedObject(obj) } func generateStatement(pred []byte, attType string, digestType string, sha plumbing.Hash) (*spb.Statement, error) { sub := []*spb.ResourceDescriptor{{ Digest: map[string]string{digestType: sha.String()}, }} var predPb structpb.Struct err := json.Unmarshal(pred, &predPb) if err != nil { return nil, err } return &spb.Statement{ Type: spb.StatementTypeUri, Subject: sub, PredicateType: attType, Predicate: &predPb, }, nil } func (a *Attestor) signPayload(ctx context.Context, sha plumbing.Hash, b []byte, attType string) ([]byte, error) { sh, err := generateStatement(b, attType, a.digestType, sha) if err != nil { return nil, err } payload, err := protojson.Marshal(sh) if err != nil { return nil, err } wrapped := dssesig.WrapSigner(a.sv, types.IntotoPayloadType) envelope, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx)) if err != nil { return nil, err } rekorHost, rekorBasePath := utils.StripURL(a.config.Rekor) tc := &rekorclient.TransportConfig{ Host: rekorHost, BasePath: rekorBasePath, Schemes: []string{"https"}, } rcfg := rekorclient.NewHTTPClientWithConfig(strfmt.Default, tc) // Upload to rekor entry, err := a.rekorFn(ctx, rcfg, envelope, a.sv.Cert) if err != nil { return nil, err } fmt.Println("LogEntry ID", *entry.LogID, *entry.LogIndex) return envelope, nil } func writeBlob(store storage.Storer, b []byte) (plumbing.Hash, error) { obj := store.NewEncodedObject() obj.SetType(plumbing.BlobObject) w, err := obj.Writer() if err != nil { return plumbing.ZeroHash, err } if _, err := w.Write(b); err != nil { return plumbing.ZeroHash, err } return store.SetEncodedObject(obj) } // buildTree creates the tree directory for the attestation commit, preserving existing data if present. // attCommit is the value of the commit that holds the current attestations (i.e. refs/attestations) that we will append values to. If the commit is the zero-SHA, a new, parentless commit will be created. // targetSHA is the value of the target SHA we are attesting to. func buildTree(repo *git.Repository, attCommit *object.Commit, targetSHA plumbing.Hash, newEntries []object.TreeEntry) (plumbing.Hash, error) { attTree := plumbing.ZeroHash if attCommit != nil { attTree = attCommit.TreeHash } // If there's an existing attestation commit, resolve the tree. var shaTree plumbing.Hash if attTree != plumbing.ZeroHash { tree, err := repo.TreeObject(attTree) if err != nil { return plumbing.ZeroHash, err } // Look for existing entry corresponding to the target SHA in existing attestation tree. for _, t := range tree.Entries { if t.Name == targetSHA.String() { shaTree = t.Hash break } } } shaTreeNew, err := appendTree(repo, shaTree, newEntries) if err != nil { return plumbing.ZeroHash, err } return appendTree(repo, attTree, []object.TreeEntry{{ Name: targetSHA.String(), Mode: filemode.Dir, Hash: shaTreeNew, }}) } // appendTree adds a set of entries to an existing tree. // If the existing tree is the zero-SHA, then a new tree is created. func appendTree(repo *git.Repository, treeSHA plumbing.Hash, new []object.TreeEntry) (plumbing.Hash, error) { // Build set of entries. files := map[string]object.TreeEntry{} // If there is already a tree, grab all existing entries. if treeSHA != plumbing.ZeroHash { // Put existing values into the set. filetree, err := repo.TreeObject(treeSHA) if err != nil { return plumbing.ZeroHash, err } for _, t := range filetree.Entries { files[t.Name] = t } } // Append new values - this will overwrite old entries. for _, t := range new { files[t.Name] = t } // Convert back to list. entries := make([]object.TreeEntry, 0, len(files)) for _, e := range files { entries = append(entries, e) } // Git expects trees to be sorted by name. sort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name }) return encode(repo.Storer, &object.Tree{ Entries: entries, }) } gitsign-0.13.0/internal/attest/attest_test.go000066400000000000000000000242561477253552300212770ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attest import ( "bytes" "context" "encoding/base64" "encoding/json" "fmt" "io" "os" "path/filepath" "testing" "time" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/storage/memory" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/jonboulle/clockwork" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" gitsignconfig "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore/pkg/signature" ) func TestMain(m *testing.M) { clock = clockwork.NewFakeClockAt(time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC)) os.Exit(m.Run()) } func TestAttestCommitRef(t *testing.T) { sv := &sign.SignerVerifier{SignerVerifier: fakeSV{}} ctx := context.Background() storer := memory.NewStorage() fs := memfs.New() repo, err := git.Init(storer, fs) if err != nil { t.Fatalf("error creating repo: %v", err) } w, err := repo.Worktree() if err != nil { t.Fatal(err) } sha := writeRepo(t, w, fs, "testdata/foo.txt") name := "test.json" content := readFile(t, filepath.Join("testdata/", name)) cfg, err := gitsignconfig.Get() if err != nil { t.Fatal(err) } attestor := NewAttestor(repo, sv, fakeRekor, cfg, DigestTypeCommit) ad := []gitAttestData{ { sha: sha, predName: "test.json", predicate: readFile(t, "testdata/test.json"), attName: "test.json.sig", attestation: generateAttestation(t, "gitCommit", sha), }, } t.Run("base", func(t *testing.T) { attest1, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } verifyContent(t, repo, attest1, ad) }) t.Run("noop", func(t *testing.T) { // Write same attestation to the same commit - should be a no-op. attest2, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } verifyContent(t, repo, attest2, ad) }) t.Run("new commit", func(t *testing.T) { // Make a new commit, write new attestation. sha, err = w.Commit("empty commit", &git.CommitOptions{ Author: &object.Signature{}, Committer: &object.Signature{}, AllowEmptyCommits: true, }) if err != nil { t.Fatal(err) } attest3, err := attestor.WriteAttestation(ctx, CommitRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } ad = append(ad, gitAttestData{ sha: sha, predName: "test.json", predicate: readFile(t, "testdata/test.json"), attName: "test.json.sig", attestation: generateAttestation(t, "gitCommit", sha), }, ) verifyContent(t, repo, attest3, ad) }) } func TestAttestTreeRef(t *testing.T) { sv := &sign.SignerVerifier{SignerVerifier: fakeSV{}} ctx := context.Background() storer := memory.NewStorage() fs := memfs.New() repo, err := git.Init(storer, fs) if err != nil { t.Fatalf("error creating repo: %v", err) } w, err := repo.Worktree() if err != nil { t.Fatal(err) } sha := resolveTree(t, repo, writeRepo(t, w, fs, "testdata/foo.txt")) name := "test.json" content := readFile(t, filepath.Join("testdata", name)) cfg, _ := gitsignconfig.Get() attestor := NewAttestor(repo, sv, fakeRekor, cfg, DigestTypeTree) ad := []gitAttestData{ { sha: sha, predName: "test.json", predicate: readFile(t, "testdata/test.json"), attName: "test.json.sig", attestation: generateAttestation(t, "gitTree", sha), }, } t.Run("base", func(t *testing.T) { attest1, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } verifyContent(t, repo, attest1, ad) }) t.Run("noop", func(t *testing.T) { // Write same attestation to the same commit - should be a no-op. attest2, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } verifyContent(t, repo, attest2, ad) }) t.Run("new commit same tree", func(t *testing.T) { // Make a new commit, but since this will point to the same tree, attestation is a no-op. sha, err = w.Commit("empty commit", &git.CommitOptions{ Author: &object.Signature{}, Committer: &object.Signature{}, AllowEmptyCommits: true, }) if err != nil { t.Fatal(err) } sha = resolveTree(t, repo, sha) attest3, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } verifyContent(t, repo, attest3, ad) }) t.Run("new commit new tree", func(t *testing.T) { // Make a new commit, write new attestation. sha = resolveTree(t, repo, writeRepo(t, w, fs, "testdata/bar.txt")) attest3, err := attestor.WriteAttestation(ctx, TreeRef, sha, NewNamedReader(bytes.NewBufferString(content), name), "custom-pred-type") if err != nil { t.Fatalf("WriteAttestation: %v", err) } ad = append(ad, gitAttestData{ sha: sha, predName: "test.json", predicate: readFile(t, "testdata/test.json"), attName: "test.json.sig", attestation: generateAttestation(t, "gitTree", sha), }, ) verifyContent(t, repo, attest3, ad) }) } type gitAttestData struct { sha plumbing.Hash predName string predicate string attName string attestation string } func verifyContent(t *testing.T, repo *git.Repository, sha plumbing.Hash, want []gitAttestData) { t.Helper() commit, err := repo.CommitObject(sha) if err != nil { t.Fatal(err) } for _, w := range want { // We'll just check the raw predicate file that was written, // that doesn't get marshaled so it should be untouched. fname := fmt.Sprintf("%v/%v", w.sha, w.predName) gotPredFile, err := commit.File(fname) if err != nil { t.Fatal(err) } gotPred, err := gotPredFile.Contents() if err != nil { t.Fatal(err) } diff := cmp.Diff(w.predicate, gotPred) if diff != "" { t.Errorf("fname %v does not match: %v", fname, diff) } // The attestation does get marshalled though, so we can't do // a simple diff, instead we'll need to parse things... fname = fmt.Sprintf("%v/%v", w.sha, w.attName) gotE := readDsse(t, commit, fname) wantE := parseDsse(t, w.attestation) // Ignore payload because we're going to handle that special diff = cmp.Diff(gotE, wantE, cmpopts.IgnoreFields(dsse.Envelope{}, "Payload")) if diff != "" { t.Errorf("fname %v does not match: %v", fname, diff) } // Now let's check the payload. gotJ := parsePayload(t, gotE) wantJ := parsePayload(t, wantE) diff = cmp.Diff(gotJ, wantJ) if diff != "" { t.Errorf("fname payload %v does not match: %v", fname, diff) } } } type fakeSV struct { signature.SignerVerifier } func (fakeSV) SignMessage(_ io.Reader, _ ...signature.SignOption) ([]byte, error) { return []byte("tacocat"), nil } func fakeRekor(_ context.Context, _ *client.Rekor, _, _ []byte) (*models.LogEntryAnon, error) { id := "foo" index := int64(1) return &models.LogEntryAnon{ LogID: &id, LogIndex: &index, }, nil } func parsePayload(t *testing.T, d *dsse.Envelope) interface{} { p, err := base64.StdEncoding.DecodeString(d.Payload) if err != nil { t.Fatal(err) } var j interface{} err = json.Unmarshal(p, &j) if err != nil { t.Fatal(err) } return j } func parseDsse(t *testing.T, content string) *dsse.Envelope { var e dsse.Envelope err := json.Unmarshal([]byte(content), &e) if err != nil { t.Fatal(err) } return &e } func readDsse(t *testing.T, commit *object.Commit, fname string) *dsse.Envelope { f, err := commit.File(fname) if err != nil { t.Fatal(err) } c, err := f.Contents() if err != nil { t.Fatal(err) } return parseDsse(t, c) } func readFile(t *testing.T, path string) string { t.Helper() b, err := os.ReadFile(path) if err != nil { t.Fatal(err) } return string(b) } func writeRepo(t *testing.T, w *git.Worktree, fs billy.Filesystem, path string) plumbing.Hash { content := readFile(t, path) f, err := fs.Create(filepath.Base(path)) if err != nil { t.Fatal(err) } f.Write([]byte(content)) f.Close() w.Add(f.Name()) sha, err := w.Commit(f.Name(), &git.CommitOptions{ Author: &object.Signature{}, Committer: &object.Signature{}, }) if err != nil { t.Fatal(err) } return sha } func generateAttestation(t *testing.T, digestType string, h plumbing.Hash) string { t.Helper() statement := fmt.Sprintf( `{"_type":"https://in-toto.io/Statement/v1","subject":[{"digest":{"%s":"%s"}}],"predicateType":"custom-pred-type","predicate":{"foo":"bar"}}`, digestType, h.String()) att := dsse.Envelope{ PayloadType: "application/vnd.in-toto+json", Payload: base64.StdEncoding.EncodeToString([]byte(statement)), Signatures: []dsse.Signature{{Sig: "dGFjb2NhdA=="}}, } out, err := json.Marshal(att) if err != nil { t.Fatal(err) } return string(out) } func resolveTree(t *testing.T, repo *git.Repository, h plumbing.Hash) plumbing.Hash { t.Helper() commit, err := repo.CommitObject(h) if err != nil { t.Fatal(err) } return commit.TreeHash } gitsign-0.13.0/internal/attest/testdata/000077500000000000000000000000001477253552300202055ustar00rootroot00000000000000gitsign-0.13.0/internal/attest/testdata/bar.txt000066400000000000000000000000031477253552300215030ustar00rootroot00000000000000bargitsign-0.13.0/internal/attest/testdata/foo.txt000066400000000000000000000000031477253552300215220ustar00rootroot00000000000000foogitsign-0.13.0/internal/attest/testdata/test.json000066400000000000000000000000151477253552300220530ustar00rootroot00000000000000{"foo":"bar"}gitsign-0.13.0/internal/cache/000077500000000000000000000000001477253552300161335ustar00rootroot00000000000000gitsign-0.13.0/internal/cache/api/000077500000000000000000000000001477253552300167045ustar00rootroot00000000000000gitsign-0.13.0/internal/cache/api/api.go000066400000000000000000000016151477253552300200070ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import "github.com/sigstore/gitsign/internal/config" type Credential struct { PrivateKey []byte Cert []byte Chain []byte } type StoreCredentialRequest struct { ID string Credential *Credential } type GetCredentialRequest struct { ID string Config *config.Config } gitsign-0.13.0/internal/cache/cache_test.go000066400000000000000000000047351477253552300205750ustar00rootroot00000000000000// 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 cache_test import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "net" "net/rpc" "os" "path/filepath" "testing" "github.com/github/smimesign/fakeca" "github.com/google/go-cmp/cmp" "github.com/sigstore/gitsign/internal/cache" "github.com/sigstore/gitsign/internal/cache/api" "github.com/sigstore/gitsign/internal/cache/service" "github.com/sigstore/sigstore/pkg/cryptoutils" ) func TestCache(t *testing.T) { ctx := context.Background() path := filepath.Join(t.TempDir(), "cache.sock") l, err := net.Listen("unix", path) if err != nil { t.Fatal(err) } srv := rpc.NewServer() srv.Register(service.NewService()) go func() { for { srv.Accept(l) } }() rpcClient, _ := rpc.Dial("unix", path) defer rpcClient.Close() ca := fakeca.New() client := &cache.Client{ Client: rpcClient, Roots: ca.ChainPool(), } if _, _, _, err := client.GetCredentials(ctx, nil); err == nil { t.Fatal("GetSignerVerifier: expected err, got not") } priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) certPEM, _ := cryptoutils.MarshalCertificateToPEM(ca.Certificate) if err := client.StoreCert(ctx, priv, certPEM, nil); err != nil { t.Fatalf("StoreCert: %v", err) } host, _ := os.Hostname() wd, _ := os.Getwd() id := fmt.Sprintf("%s@%s", host, wd) cred := new(api.Credential) if err := client.Client.Call("Service.GetCredential", &api.GetCredentialRequest{ID: id}, cred); err != nil { t.Fatal(err) } privPEM, _ := cryptoutils.MarshalPrivateKeyToPEM(priv) want := &api.Credential{ PrivateKey: privPEM, Cert: certPEM, } if diff := cmp.Diff(want, cred); diff != "" { t.Error(diff) } gotPriv, gotCert, _, err := client.GetCredentials(ctx, nil) if err != nil { t.Fatal(err) } if !priv.Equal(gotPriv) { t.Fatal("private key did not match") } if ok := cmp.Equal(certPEM, gotCert); !ok { t.Error("stored cert does not match") } } gitsign-0.13.0/internal/cache/client.go000066400000000000000000000070561477253552300177500ustar00rootroot00000000000000// 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 cache import ( "context" "crypto" "crypto/x509" "encoding/asn1" "fmt" "net/rpc" "os" "time" "github.com/sigstore/gitsign/internal/cache/api" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/sigstore/pkg/cryptoutils" ) type Client struct { Client *rpc.Client Roots *x509.CertPool Intermediates *x509.CertPool } func (c *Client) GetCredentials(_ context.Context, cfg *config.Config) (crypto.PrivateKey, []byte, []byte, error) { id, err := id() if err != nil { return nil, nil, nil, fmt.Errorf("error getting credential ID: %w", err) } resp := new(api.Credential) if err := c.Client.Call("Service.GetCredential", api.GetCredentialRequest{ ID: id, Config: cfg, }, resp); err != nil { return nil, nil, nil, err } privateKey, err := cryptoutils.UnmarshalPEMToPrivateKey(resp.PrivateKey, cryptoutils.SkipPassword) if err != nil { return nil, nil, nil, fmt.Errorf("error unmarshalling private key: %w", err) } // Check that the cert is in fact still valid. certs, err := cryptoutils.UnmarshalCertificatesFromPEM(resp.Cert) if err != nil { return nil, nil, nil, fmt.Errorf("error unmarshalling cert: %w", err) } // There should really only be 1 cert, but check them all anyway. for _, cert := range certs { if len(cert.UnhandledCriticalExtensions) > 0 { var unhandledExts []asn1.ObjectIdentifier for _, oid := range cert.UnhandledCriticalExtensions { if !oid.Equal(cryptoutils.SANOID) { unhandledExts = append(unhandledExts, oid) } } cert.UnhandledCriticalExtensions = unhandledExts } if _, err := cert.Verify(x509.VerifyOptions{ Roots: c.Roots, Intermediates: c.Intermediates, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, // We're going to be using this key immediately, so we don't need a long window. // Just make sure it's not about to expire. CurrentTime: time.Now().Add(30 * time.Second), }); err != nil { return nil, nil, nil, fmt.Errorf("stored cert no longer valid: %w", err) } } return privateKey, resp.Cert, resp.Chain, nil } func (c *Client) StoreCert(_ context.Context, priv crypto.PrivateKey, cert, chain []byte) error { id, err := id() if err != nil { return fmt.Errorf("error getting credential ID: %w", err) } privPEM, err := cryptoutils.MarshalPrivateKeyToPEM(priv) if err != nil { return err } if err := c.Client.Call("Service.StoreCredential", api.StoreCredentialRequest{ ID: id, Credential: &api.Credential{ PrivateKey: privPEM, Cert: cert, Chain: chain, }, }, new(api.Credential)); err != nil { return err } return err } func id() (string, error) { // Prefix host name in case cache socket is being shared over a SSH session. host, err := os.Hostname() if err != nil { return "", fmt.Errorf("error getting hostname: %w", err) } wd, err := os.Getwd() if err != nil { return "", fmt.Errorf("error getting working directory: %w", err) } return fmt.Sprintf("%s@%s", host, wd), nil } gitsign-0.13.0/internal/cache/service/000077500000000000000000000000001477253552300175735ustar00rootroot00000000000000gitsign-0.13.0/internal/cache/service/service.go000066400000000000000000000050661477253552300215710ustar00rootroot00000000000000// 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 service import ( "context" "fmt" "os" "time" "github.com/patrickmn/go-cache" "github.com/sigstore/gitsign/internal/cache/api" "github.com/sigstore/gitsign/internal/fulcio" "github.com/sigstore/sigstore/pkg/cryptoutils" ) type Service struct { store *cache.Cache } const ( defaultExpiration = 10 * time.Minute cleanupInterval = 1 * time.Minute ) func NewService() *Service { s := &Service{ store: cache.New(defaultExpiration, cleanupInterval), } return s } func (s *Service) StoreCredential(req api.StoreCredentialRequest, resp *api.Credential) error { fmt.Println("Store", req.ID) if err := s.store.Add(req.ID, req.Credential, 10*time.Minute); err != nil { return err } *resp = *req.Credential return nil } func (s *Service) GetCredential(req api.GetCredentialRequest, resp *api.Credential) error { ctx := context.Background() fmt.Println("Get", req.ID) i, ok := s.store.Get(req.ID) if ok { fmt.Println("gitsign-credential-cache: found credential!") cred, ok := i.(*api.Credential) if !ok { return fmt.Errorf("unknown credential type %T", i) } *resp = *cred return nil } if req.Config == nil { // No config set, nothing to do. return fmt.Errorf("%q not found", req.ID) } // If nothing is in the cache, fallback to interactive flow. fmt.Println("gitsign-credential-cache: no cached credential found, falling back to interactive flow...") idf := fulcio.NewIdentityFactory(os.Stdin, os.Stdout) id, err := idf.NewIdentity(ctx, req.Config) if err != nil { return fmt.Errorf("error getting new identity: %w", err) } privPEM, err := cryptoutils.MarshalPrivateKeyToPEM(id.PrivateKey) if err != nil { return err } cred := &api.Credential{ PrivateKey: privPEM, Cert: id.CertPEM, Chain: id.ChainPEM, } if err := s.store.Add(req.ID, cred, 10*time.Minute); err != nil { // We still generated the credential just fine, so only log the error. fmt.Printf("error storing credential: %v\n", err) } *resp = *cred return nil } gitsign-0.13.0/internal/cert/000077500000000000000000000000001477253552300160255ustar00rootroot00000000000000gitsign-0.13.0/internal/cert/verify.go000066400000000000000000000022251477253552300176610ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cert import ( "crypto/x509" "github.com/sigstore/cosign/v2/pkg/cosign" ) // Verifier verifies a given cert for a set of claims. type Verifier interface { Verify(cert *x509.Certificate) error } // CosignVerifier borrows its certificate verification logic from cosign. type CosignVerifier struct { opts *cosign.CheckOpts } func NewCosignVerifier(opts *cosign.CheckOpts) *CosignVerifier { return &CosignVerifier{opts: opts} } func (v *CosignVerifier) Verify(cert *x509.Certificate) error { _, err := cosign.ValidateAndUnpackCert(cert, v.opts) return err } gitsign-0.13.0/internal/commands/000077500000000000000000000000001477253552300166715ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/attest/000077500000000000000000000000001477253552300201755ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/attest/README.md000066400000000000000000000036071477253552300214620ustar00rootroot00000000000000# gitsign-attest NOTE: This is an **experimental demo**. This will be added as a subcommand to gitsign if/when we decide to support this. `gitsign-attest` will add attestations to the latest commit SHA in your Git working directory (if using a dirty workspace, the last commit is used). Data is stored as a commit under `refs/attestations/commits` or `refs/attestations/trees` (depending what you're attesting to), separate from the primary source tree. This means that the original commit is **unmodified**. Within this commit, there contains a folder for each commit SHA attested to. gitsign-attest will store the following: - the raw data given by the user - a signed DSSE message attesting to the file For now, only public sigstore is supported. ## Usage ### Commit attestations Commit attestations signs and attaches the given attestation file to the latest commit. Data is stored in `refs/attestations/commits` ```sh $ git log f44de7a (HEAD -> main) commit 2b0ff1e commit 1 760568f initial commit $ gitsign-attest -f test.json $ gitsign-attest -f spdx.sbom --type spdx $ git checkout refs/attestations/commits $ tree . └── f44de7aee552f119f94d70137b3bebb93f6bca5d ├── sbom.spdx ├── sbom.spdx.sig ├── test.json └── test.json.sig ``` ### Tree attestations Tree attestations signs and attaches the given attestation file to the latest commit. Data is stored in `refs/attestations/trees`. This can be used to sign directory content regardless of the commit they came from. This can be useful to preserve attestations for squash commits, or between sub-directories. ```sh $ git log --oneline --format="Commit: %h Tree: %t" -1 Commit: edd19d9 Tree: 853a6ca $ gitsign-attest -f test.json --objtype tree $ git checkout refs/attestations/trees $ tree . . ├── 853a6ca8dd0e1fb84d67c397f6d8daac5926176c │   ├── test.json │   └── test.json.sig ``` gitsign-0.13.0/internal/commands/attest/attest.go000066400000000000000000000061351477253552300220350ustar00rootroot00000000000000// Copyright 2022 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package attest import ( "context" "fmt" "github.com/go-git/go-git/v5" cosignopts "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" "github.com/sigstore/gitsign/internal/attest" "github.com/sigstore/gitsign/internal/config" "github.com/spf13/cobra" ) const ( attCommitRef = "refs/attestations/commits" attTreeRef = "refs/attestations/trees" FlagObjectTypeCommit = "commit" FlagObjectTypeTree = "tree" ) type options struct { Config *config.Config FlagObjectType string FlagPath string FlagAttestationType string } func (o *options) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.FlagObjectType, "objtype", FlagObjectTypeCommit, "[commit | tree] - Git object type to attest") cmd.Flags().StringVarP(&o.FlagPath, "filepath", "f", "", "attestation filepath") cmd.Flags().StringVar(&o.FlagAttestationType, "type", "", `specify a predicate type URI`) } func (o *options) Run(ctx context.Context) error { repo, err := git.PlainOpen(".") if err != nil { return fmt.Errorf("error opening repo: %w", err) } head, err := repo.Head() if err != nil { return fmt.Errorf("error getting repository head: %w", err) } // If we're attaching the attestation to a tree, resolve the tree SHA. sha := head.Hash() refName := attCommitRef digestType := attest.DigestTypeCommit if o.FlagObjectType == FlagObjectTypeTree { commit, err := repo.CommitObject(head.Hash()) if err != nil { return fmt.Errorf("error getting tree: %w", err) } sha = commit.TreeHash refName = attTreeRef digestType = attest.DigestTypeTree } sv, err := sign.SignerFromKeyOpts(ctx, "", "", cosignopts.KeyOpts{ FulcioURL: o.Config.Fulcio, RekorURL: o.Config.Rekor, OIDCIssuer: o.Config.Issuer, OIDCClientID: o.Config.ClientID, }) if err != nil { return fmt.Errorf("getting signer: %w", err) } defer sv.Close() attestor := attest.NewAttestor(repo, sv, cosign.TLogUploadInTotoAttestation, o.Config, digestType) out, err := attestor.WriteFile(ctx, refName, sha, o.FlagPath, o.FlagAttestationType) if err != nil { return err } fmt.Println(out) return nil } func New(cfg *config.Config) *cobra.Command { o := &options{ Config: cfg, } cmd := &cobra.Command{ Use: "attest", Short: "add attestations to Git objects", Args: cobra.ArbitraryArgs, RunE: func(_ *cobra.Command, _ []string) error { ctx := context.Background() return o.Run(ctx) }, } o.AddFlags(cmd) return cmd } gitsign-0.13.0/internal/commands/initialize/000077500000000000000000000000001477253552300210325ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/initialize/initialize.go000066400000000000000000000054671477253552300235360ustar00rootroot00000000000000// // Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package initialize inits the TUF root for the tool. // This is intended to replicate the behavior of `gitsign initialize`. package initialize import ( "github.com/sigstore/cosign/v2/cmd/cosign/cli/initialize" "github.com/sigstore/sigstore/pkg/tuf" "github.com/spf13/cobra" ) type options struct { Mirror string Root string } // AddFlags implements Interface func (o *options) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.Mirror, "mirror", tuf.DefaultRemoteRoot, "GCS bucket to a Sigstore TUF repository, or HTTP(S) base URL, or file:/// for local filestore remote (air-gap)") cmd.Flags().StringVar(&o.Root, "root", "", "path to trusted initial root. defaults to embedded root") _ = cmd.Flags().SetAnnotation("root", cobra.BashCompSubdirsInDir, []string{}) } func New() *cobra.Command { o := &options{} cmd := &cobra.Command{ Use: "initialize", Short: "Initializes Sigstore root to retrieve trusted certificate and key targets for verification.", Long: `Initializes Sigstore root to retrieve trusted certificate and key targets for verification. The following options are used by default: - The current trusted Sigstore TUF root is embedded inside gitsign at the time of release. - Sigstore remote TUF repository is pulled from the CDN mirror at tuf-repo-cdn.sigstore.dev. To provide an out-of-band trusted initial root.json, use the -root flag with a file or URL reference. This will enable you to point gitsign to a separate TUF root. Any updated TUF repository will be written to $HOME/.sigstore/root/. Trusted keys and certificate used in gitsign verification (e.g. verifying Fulcio issued certificates with Fulcio root CA) are pulled form the trusted metadata.`, Example: `gitsign initialize -mirror -out # initialize root with distributed root keys, default mirror, and default out path. gitsign initialize # initialize with an out-of-band root key file, using the default mirror. gitsign initialize -root # initialize with an out-of-band root key file and custom repository mirror. gitsign initialize -mirror -root `, RunE: func(cmd *cobra.Command, _ []string) error { return initialize.DoInitialize(cmd.Context(), o.Root, o.Mirror) }, } o.AddFlags(cmd) return cmd } gitsign-0.13.0/internal/commands/root/000077500000000000000000000000001477253552300176545ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/root/root.go000066400000000000000000000072161477253552300211740ustar00rootroot00000000000000// // 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 root import ( "github.com/spf13/cobra" "github.com/sigstore/gitsign/internal/commands/attest" "github.com/sigstore/gitsign/internal/commands/initialize" "github.com/sigstore/gitsign/internal/commands/show" "github.com/sigstore/gitsign/internal/commands/verify" verifytag "github.com/sigstore/gitsign/internal/commands/verify-tag" "github.com/sigstore/gitsign/internal/commands/version" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/gitsign/internal/io" ) type options struct { Config *config.Config FlagSign bool FlagVerify bool FlagVersion bool FlagLocalUser string FlagDetachedSignature bool FlagArmor bool FlagStatusFD int FlagIncludeCerts int } func (o *options) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVarP(&o.FlagSign, "sign", "s", false, "make a signature") cmd.Flags().BoolVarP(&o.FlagVerify, "verify", "v", false, "verify a signature") cmd.Flags().BoolVar(&o.FlagVersion, "version", false, "print Gitsign version") cmd.Flags().StringVarP(&o.FlagLocalUser, "local-user", "u", "", "use USER-ID to sign") cmd.Flags().BoolVarP(&o.FlagDetachedSignature, "detached-sign", "", false, "make a detached signature") cmd.Flags().BoolVarP(&o.FlagDetachedSignature, "detach-sign", "b", false, "make a detached signature") cmd.Flags().BoolVarP(&o.FlagArmor, "armor", "a", false, "create ascii armored output") cmd.Flags().IntVar(&o.FlagStatusFD, "status-fd", -1, "write special status strings to the file descriptor n.") cmd.Flags().IntVar(&o.FlagIncludeCerts, "include-certs", -2, "-3 is the same as -2, but omits issuer when cert has Authority Information Access extension. -2 includes all certs except root. -1 includes all certs. 0 includes no certs. 1 includes leaf cert. >1 includes n from the leaf. Default -2.") cmd.Flags().MarkDeprecated("detached-sign", "--detached-sign has been deprecated in favor of --detach-sign to match the interface of other signing tools") //nolint:errcheck } func New(cfg *config.Config) *cobra.Command { o := &options{Config: cfg} rootCmd := &cobra.Command{ Use: "gitsign", Short: "Keyless Git signing with Sigstore!", Args: cobra.ArbitraryArgs, DisableAutoGenTag: true, RunE: func(cmd *cobra.Command, args []string) error { s := io.New(o.Config.LogPath) defer s.Close() return s.Wrap(func() error { switch { case o.FlagVersion: // Alias root --version with version subcommand for _, item := range cmd.Commands() { if item.Name() == "version" { return item.RunE(item, cmd.Flags().Args()) } } case o.FlagSign, o.FlagDetachedSignature: return commandSign(o, s, args...) case o.FlagVerify: return commandVerify(o, s, args...) default: return cmd.Help() } return nil }) }, } rootCmd.AddCommand(version.New(cfg)) rootCmd.AddCommand(show.New(cfg)) rootCmd.AddCommand(attest.New(cfg)) rootCmd.AddCommand(verify.New(cfg)) rootCmd.AddCommand(verifytag.New(cfg)) rootCmd.AddCommand(initialize.New()) o.AddFlags(rootCmd) return rootCmd } gitsign-0.13.0/internal/commands/root/sign.go000066400000000000000000000061221477253552300211440ustar00rootroot00000000000000// // 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 root import ( "bytes" "context" "errors" "fmt" "io" "os" "github.com/sigstore/gitsign/internal/fulcio" "github.com/sigstore/gitsign/internal/git" "github.com/sigstore/gitsign/internal/gpg" gsio "github.com/sigstore/gitsign/internal/io" "github.com/sigstore/gitsign/internal/rekor" "github.com/sigstore/gitsign/internal/signature" ) // commandSign implements gitsign commit signing. // This is implemented as a root command so that user can specify the // gitsign binary directly in their gitconfigs. func commandSign(o *options, s *gsio.Streams, args ...string) error { ctx := context.Background() // Flag validation if o.FlagVerify { return errors.New("specify --help, --sign, or --verify") } userIdent, err := fulcio.NewIdentity(ctx, o.Config, s.TTYIn, s.TTYOut) if err != nil { return fmt.Errorf("failed to get identity: %w", err) } // Git is looking for "\n[GNUPG:] SIG_CREATED ", meaning we need to print a // line before SIG_CREATED. BEGIN_SIGNING seems appropriate. GPG emits this, // though GPGSM does not. gpgout := gpg.NewStatusWriterFromFD(uintptr(o.FlagStatusFD)) gpgout.Emit(gpg.StatusBeginSigning) var f io.Reader if len(args) == 1 { f2, err := os.Open(args[0]) if err != nil { return fmt.Errorf("failed to open message file (%s): %w", args[0], err) } defer f2.Close() f = f2 } else { f = s.In } dataBuf := new(bytes.Buffer) if _, err = io.Copy(dataBuf, f); err != nil { return fmt.Errorf("failed to read message from stdin: %w", err) } rekor, err := rekor.NewClientContext(ctx, o.Config.Rekor) if err != nil { return fmt.Errorf("failed to create rekor client: %w", err) } opts := signature.SignOptions{ Detached: o.FlagDetachedSignature, TimestampAuthority: o.Config.TimestampURL, Armor: o.FlagArmor, IncludeCerts: o.FlagIncludeCerts, } if o.Config.MatchCommitter { opts.UserName = o.Config.CommitterName opts.UserEmail = o.Config.CommitterEmail } var fn git.SignFunc = git.LegacySHASign if o.Config.RekorMode == "offline" { fn = git.Sign } resp, err := fn(ctx, rekor, userIdent, dataBuf.Bytes(), opts) if err != nil { return fmt.Errorf("failed to sign message: %w", err) } if tlog := resp.LogEntry; tlog != nil && tlog.LogIndex != nil { fmt.Fprintf(s.TTYOut, "tlog entry created with index: %d\n", *tlog.LogIndex) } gpgout.EmitSigCreated(resp.Cert, o.FlagDetachedSignature) if _, err := s.Out.Write(resp.Signature); err != nil { return errors.New("failed to write signature") } return nil } gitsign-0.13.0/internal/commands/root/verify.go000066400000000000000000000074461477253552300215220ustar00rootroot00000000000000// // 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 root import ( "bytes" "context" "errors" "fmt" "io" "os" "github.com/sigstore/gitsign/internal/commands/verify" "github.com/sigstore/gitsign/internal/gitsign" "github.com/sigstore/gitsign/internal/gpg" gsio "github.com/sigstore/gitsign/internal/io" ) // commandSign implements gitsign commit verification. // This is implemented as a root command so that user can specify the // gitsign binary directly in their gitconfigs. func commandVerify(o *options, s *gsio.Streams, args ...string) error { ctx := context.Background() // Flag validation if o.FlagSign { return errors.New("specify --help, --sign, or --verify") } if o.FlagDetachedSignature { return errors.New("detach-sign cannot be specified for verification") } if o.FlagArmor { return errors.New("armor cannot be specified for verification") } gpgout := gpg.NewStatusWriterFromFD(uintptr(o.FlagStatusFD)) gpgout.Emit(gpg.StatusNewSig) var ( data, sig []byte err error ) detached := len(args) >= 2 if detached { data, sig, err = readDetached(s, args...) } else { sig, err = readAttached(s, args...) } if err != nil { return fmt.Errorf("failed to read signature data (detached: %T): %w", detached, err) } v, err := gitsign.NewVerifierWithCosignOpts(ctx, o.Config, nil) if err != nil { return err } summary, err := v.Verify(ctx, data, sig, true) if err != nil { return err } if err != nil { if summary != nil && summary.Cert != nil { gpgout.EmitBadSig(summary.Cert) } else { // TODO: We're omitting a bunch of arguments here. gpgout.Emit(gpg.StatusErrSig) } return fmt.Errorf("failed to verify signature: %w", err) } verify.PrintSummary(s.Err, summary) fmt.Fprintln(s.Err, "WARNING: git verify-commit does not verify cert claims. Prefer using `gitsign verify` instead.") gpgout.EmitGoodSig(summary.Cert) gpgout.EmitTrustFully() return nil } func readAttached(s *gsio.Streams, args ...string) ([]byte, error) { var ( f io.Reader err error ) // Read in signature if len(args) == 1 { f2, err := os.Open(args[0]) if err != nil { return nil, fmt.Errorf("failed to open signature file (%s): %w", args[0], err) } defer f2.Close() f = f2 } else { f = s.In } sig := new(bytes.Buffer) if _, err = io.Copy(sig, f); err != nil { return nil, fmt.Errorf("failed to read signature: %w", err) } return sig.Bytes(), nil } func readDetached(s *gsio.Streams, args ...string) ([]byte, []byte, error) { // Read in signature sigFile, err := os.Open(args[0]) if err != nil { return nil, nil, fmt.Errorf("failed to open signature file (%s): %w", args[0], err) } defer sigFile.Close() sig := new(bytes.Buffer) if _, err = io.Copy(sig, sigFile); err != nil { return nil, nil, fmt.Errorf("failed to read signature file: %w", err) } var dataFile io.Reader // Read in signed data if args[1] == "-" { dataFile = s.In } else { f2, err := os.Open(args[1]) if err != nil { return nil, nil, fmt.Errorf("failed to open message file (%s): %w", args[1], err) } defer f2.Close() dataFile = f2 } buf := new(bytes.Buffer) if _, err = io.Copy(buf, dataFile); err != nil { return nil, nil, fmt.Errorf("failed to read message file: %w", err) } return buf.Bytes(), sig.Bytes(), nil } gitsign-0.13.0/internal/commands/show/000077500000000000000000000000001477253552300176515ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/show/show.go000066400000000000000000000124541477253552300211660ustar00rootroot00000000000000// 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 show import ( "encoding/base64" "encoding/json" "encoding/pem" "errors" "io" "os" "github.com/github/smimesign/ietf-cms/protocol" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/in-toto/in-toto-golang/in_toto" "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/gitsign/pkg/predicate" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/spf13/cobra" ) const ( predicateType = "https://gitsign.sigstore.dev/predicate/git/v0.1" ) type options struct { FlagRemote string } func (o *options) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&o.FlagRemote, "remote", "r", "origin", "git remote (used to populate subject)") } func (o *options) Run(w io.Writer, args []string) error { repo, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{ DetectDotGit: true, }) if err != nil { return err } revision := "HEAD" if len(args) > 0 { revision = args[0] } out, err := statement(repo, o.FlagRemote, revision) if err != nil { return err } enc := json.NewEncoder(w) enc.SetIndent("", " ") return enc.Encode(out) } func statement(repo *git.Repository, remote, revision string) (*in_toto.Statement, error) { hash, err := repo.ResolveRevision(plumbing.Revision(revision)) if err != nil { return nil, err } commit, err := repo.CommitObject(*hash) if err != nil { return nil, err } // Extract parent hashes parents := make([]string, 0, len(commit.ParentHashes)) for _, p := range commit.ParentHashes { if !p.IsZero() { parents = append(parents, p.String()) } } // Build initial predicate from the commit. predicate := &predicate.GitCommit{ Commit: &predicate.Commit{ Tree: commit.TreeHash.String(), Parents: parents, Author: &predicate.Author{ Name: commit.Author.Name, Email: commit.Author.Email, Date: commit.Author.When, }, Committer: &predicate.Author{ Name: commit.Committer.Name, Email: commit.Committer.Email, Date: commit.Committer.When, }, Message: commit.Message, }, Signature: commit.PGPSignature, } // We have a PEM encoded signature, try and extract certificate details. pem, _ := pem.Decode([]byte(commit.PGPSignature)) if pem != nil { sigs, err := parseSignature(pem.Bytes) if err != nil { return nil, err } predicate.SignerInfo = sigs } // Try and resolve the remote name to use as the subject name. // If the repo does not have a remote configured then this will be left // blank. resolvedRemote, err := repo.Remote(remote) if err != nil && !errors.Is(err, git.ErrRemoteNotFound) { return nil, err } remoteName := "" if resolvedRemote != nil && resolvedRemote.Config() != nil && len(resolvedRemote.Config().URLs) > 0 { remoteName = resolvedRemote.Config().URLs[0] } // Wrap predicate in in-toto Statement. return &in_toto.Statement{ StatementHeader: in_toto.StatementHeader{ Type: in_toto.StatementInTotoV01, Subject: []in_toto.Subject{{ Name: remoteName, Digest: common.DigestSet{ // TODO?: Figure out if/how to support git sha256 - this // will likely depend on upstream support in go-git. // See https://github.com/go-git/go-git/issues/229. "sha1": hash.String(), }, }}, PredicateType: predicateType, }, Predicate: predicate, }, nil } func parseSignature(raw []byte) ([]*predicate.SignerInfo, error) { ci, err := protocol.ParseContentInfo(raw) if err != nil { return nil, err } sd, err := ci.SignedDataContent() if err != nil { return nil, err } certs, err := sd.X509Certificates() if err != nil { return nil, err } // A signature may have multiple signers associated to it - // extract each SignerInfo separately. out := make([]*predicate.SignerInfo, 0, len(sd.SignerInfos)) for _, si := range sd.SignerInfos { cert, err := si.FindCertificate(certs) if err != nil { continue } b, err := cryptoutils.MarshalCertificateToPEM(cert) if err != nil { return nil, err } sa, err := si.SignedAttrs.MarshaledForVerification() if err != nil { return nil, err } out = append(out, &predicate.SignerInfo{ Certificate: string(b), Attributes: base64.StdEncoding.EncodeToString(sa), }) } return out, nil } func New(_ *config.Config) *cobra.Command { o := &options{} cmd := &cobra.Command{ Use: "show [revision]", Short: "Show source predicate information", Long: `Show source predicate information Prints an in-toto style predicate for the specified revision. If no revision is specified, HEAD is used. This command is experimental, and its CLI surface may change.`, RunE: func(_ *cobra.Command, args []string) error { return o.Run(os.Stdout, args) }, } o.AddFlags(cmd) return cmd } gitsign-0.13.0/internal/commands/show/show_test.go000066400000000000000000000053041477253552300222210ustar00rootroot00000000000000// 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 show import ( "encoding/json" "fmt" "os" "testing" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/storage/memory" "github.com/google/go-cmp/cmp" "github.com/in-toto/in-toto-golang/in_toto" "github.com/sigstore/gitsign/pkg/predicate" ) func TestShow(t *testing.T) { storage := memory.NewStorage() repo := &git.Repository{ Storer: storage, } if err := repo.SetConfig(&config.Config{ Remotes: map[string]*config.RemoteConfig{ "origin": { Name: "origin", URLs: []string{"git@github.com:wlynch/gitsign.git"}, }, }, }); err != nil { t.Fatalf("error setting git config: %v", err) } // Expect files in testdata directory: // foo.in.txt -> foo.out.json // IMPORTANT: When generating new test files, use a command like `git cat-file commit main > foo.in.txt`. // If you try and copy/paste the content, you may get burned by file encodings and missing \r characters. for _, tc := range []string{ "fulcio-cert", "gpg", } { t.Run(tc, func(t *testing.T) { raw, err := os.ReadFile(fmt.Sprintf("testdata/%s.in.txt", tc)) if err != nil { t.Fatalf("error reading input: %v", err) } obj := storage.NewEncodedObject() obj.SetType(plumbing.CommitObject) w, err := obj.Writer() if err != nil { t.Fatalf("error getting git object writer: %v", err) } _, err = w.Write(raw) if err != nil { t.Fatalf("error writing git commit: %v", err) } h, err := storage.SetEncodedObject(obj) if err != nil { t.Fatalf("error storing git commit: %v", err) } got, err := statement(repo, "origin", h.String()) if err != nil { t.Fatalf("statement(): %v", err) } wantRaw, err := os.ReadFile(fmt.Sprintf("testdata/%s.out.json", tc)) if err != nil { t.Fatalf("error reading want json: %v", err) } want := &in_toto.Statement{ Predicate: &predicate.GitCommit{}, } if err := json.Unmarshal(wantRaw, want); err != nil { t.Fatalf("error decoding want json: %v", err) } if diff := cmp.Diff(want, got); diff != "" { t.Error(diff) } }) } } gitsign-0.13.0/internal/commands/show/testdata/000077500000000000000000000000001477253552300214625ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/show/testdata/fulcio-cert.in.txt000066400000000000000000000032631477253552300250500ustar00rootroot00000000000000tree 194fca354a2439028e347ce5e19e4db45bd708a6 parent 2eaf8fc6d66505baa90640d018e1131cd8e99334 author Billy Lynch 1668460399 -0500 committer Billy Lynch 1668460399 -0500 gpgsig -----BEGIN SIGNED MESSAGE----- MIIEAwYJKoZIhvcNAQcCoIID9DCCA/ACAQExDTALBglghkgBZQMEAgEwCwYJKoZI hvcNAQcBoIICpDCCAqAwggImoAMCAQICFFTzLmXKAlKX5xTUaYoUE5giCxZvMAoG CCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln c3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIyMTExNDIxMTMyM1oXDTIyMTExNDIxMjMy M1owADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH++UCDlF9MaQCSgDKQ0bWhD eOmTrk1sEHw9Oel1eCyrr3SFhDAghcO3VwO7baYmL16fUwRYwMhj5urowsLVrjKj ggFFMIIBQTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD VR0OBBYEFHMPDOs6IDY/iRnVqacIj/yvJbNpMB8GA1UdIwQYMBaAFN/T6c9WJBGW +ajY6ShVosYuGGQ/MCIGA1UdEQEB/wQYMBaBFGJpbGx5QGNoYWluZ3VhcmQuZGV2 MCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBigYK KwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAv Ke6OAAABhHf9WZAAAAQDAEcwRQIgV8anMDEjbHI/WvGxpJmm44DgBTYf5bkfBJIP 6FJtqXYCIQD/noLzthDKgjrXoiep/BqqnygoTRM9HKim+DRMbwHteDAKBggqhkjO PQQDAwNoADBlAjEAvHvqOAKT34QQx9PSuOswQfquByALdzA1ES0nx4M5i47kqNeE Bl612/hYTD1ydpLIAjBTWiHDtdxM9rriTIyGGJubC0+vNcccsURDTJ+A3XnMAER3 ikl/cJ2wG9c8ZN7AUS8xggElMIIBIQIBATBPMDcxFTATBgNVBAoTDHNpZ3N0b3Jl LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlAhRU8y5lygJSl+cU 1GmKFBOYIgsWbzALBglghkgBZQMEAgGgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN AQcBMBwGCSqGSIb3DQEJBTEPFw0yMjExMTQyMTEzMjNaMC8GCSqGSIb3DQEJBDEi BCC9Yk93XCRKy6FPCb8dAqjdWpjb1NIbFtTo9CP6yYOZQjAKBggqhkjOPQQDAgRH MEUCIQCq+2Zs0bBcAAciePeeRpzmfVJ2gEu7sGngy+TcYpS0ugIgL9Qix3V8taBV +Tb6rMZmt80sfGsYhUqE8KsIF1AEc+8= -----END SIGNED MESSAGE----- add sample gitsign-0.13.0/internal/commands/show/testdata/fulcio-cert.out.json000066400000000000000000000067041477253552300254060ustar00rootroot00000000000000{ "_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://gitsign.sigstore.dev/predicate/git/v0.1", "subject": [ { "name": "git@github.com:wlynch/gitsign.git", "digest": { "sha1": "10a3086104c5331623be85a5e30d709f457b536b" } } ], "predicate": { "source": { "tree": "194fca354a2439028e347ce5e19e4db45bd708a6", "parents": [ "2eaf8fc6d66505baa90640d018e1131cd8e99334" ], "author": { "name": "Billy Lynch", "email": "billy@chainguard.dev", "date": "2022-11-14T16:13:19-05:00" }, "committer": { "name": "Billy Lynch", "email": "billy@chainguard.dev", "date": "2022-11-14T16:13:19-05:00" }, "message": "add sample\n" }, "signature": "-----BEGIN SIGNED MESSAGE-----\nMIIEAwYJKoZIhvcNAQcCoIID9DCCA/ACAQExDTALBglghkgBZQMEAgEwCwYJKoZI\nhvcNAQcBoIICpDCCAqAwggImoAMCAQICFFTzLmXKAlKX5xTUaYoUE5giCxZvMAoG\nCCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln\nc3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIyMTExNDIxMTMyM1oXDTIyMTExNDIxMjMy\nM1owADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH++UCDlF9MaQCSgDKQ0bWhD\neOmTrk1sEHw9Oel1eCyrr3SFhDAghcO3VwO7baYmL16fUwRYwMhj5urowsLVrjKj\nggFFMIIBQTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD\nVR0OBBYEFHMPDOs6IDY/iRnVqacIj/yvJbNpMB8GA1UdIwQYMBaAFN/T6c9WJBGW\n+ajY6ShVosYuGGQ/MCIGA1UdEQEB/wQYMBaBFGJpbGx5QGNoYWluZ3VhcmQuZGV2\nMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBigYK\nKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAv\nKe6OAAABhHf9WZAAAAQDAEcwRQIgV8anMDEjbHI/WvGxpJmm44DgBTYf5bkfBJIP\n6FJtqXYCIQD/noLzthDKgjrXoiep/BqqnygoTRM9HKim+DRMbwHteDAKBggqhkjO\nPQQDAwNoADBlAjEAvHvqOAKT34QQx9PSuOswQfquByALdzA1ES0nx4M5i47kqNeE\nBl612/hYTD1ydpLIAjBTWiHDtdxM9rriTIyGGJubC0+vNcccsURDTJ+A3XnMAER3\nikl/cJ2wG9c8ZN7AUS8xggElMIIBIQIBATBPMDcxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlAhRU8y5lygJSl+cU\n1GmKFBOYIgsWbzALBglghkgBZQMEAgGgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN\nAQcBMBwGCSqGSIb3DQEJBTEPFw0yMjExMTQyMTEzMjNaMC8GCSqGSIb3DQEJBDEi\nBCC9Yk93XCRKy6FPCb8dAqjdWpjb1NIbFtTo9CP6yYOZQjAKBggqhkjOPQQDAgRH\nMEUCIQCq+2Zs0bBcAAciePeeRpzmfVJ2gEu7sGngy+TcYpS0ugIgL9Qix3V8taBV\n+Tb6rMZmt80sfGsYhUqE8KsIF1AEc+8=\n-----END SIGNED MESSAGE-----\n", "signer_info": [ { "attributes": "MWkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjIxMTE0MjExMzIzWjAvBgkqhkiG9w0BCQQxIgQgvWJPd1wkSsuhTwm/HQKo3VqY29TSGxbU6PQj+smDmUI=", "certificate": "-----BEGIN CERTIFICATE-----\nMIICoDCCAiagAwIBAgIUVPMuZcoCUpfnFNRpihQTmCILFm8wCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjIxMTE0MjExMzIzWhcNMjIxMTE0MjEyMzIzWjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAEf75QIOUX0xpAJKAMpDRtaEN46ZOuTWwQfD05\n6XV4LKuvdIWEMCCFw7dXA7ttpiYvXp9TBFjAyGPm6ujCwtWuMqOCAUUwggFBMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUcw8M\n6zogNj+JGdWppwiP/K8ls2kwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wIgYDVR0RAQH/BBgwFoEUYmlsbHlAY2hhaW5ndWFyZC5kZXYwKQYKKwYBBAGD\nvzABAQQbaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tMIGKBgorBgEEAdZ5AgQC\nBHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGEd/1Z\nkAAABAMARzBFAiBXxqcwMSNscj9a8bGkmabjgOAFNh/luR8Ekg/oUm2pdgIhAP+e\ngvO2EMqCOteiJ6n8GqqfKChNEz0cqKb4NExvAe14MAoGCCqGSM49BAMDA2gAMGUC\nMQC8e+o4ApPfhBDH09K46zBB+q4HIAt3MDURLSfHgzmLjuSo14QGXrXb+FhMPXJ2\nksgCMFNaIcO13Ez2uuJMjIYYm5sLT681xxyxRENMn4DdecwARHeKSX9wnbAb1zxk\n3sBRLw==\n-----END CERTIFICATE-----\n" } ] } } gitsign-0.13.0/internal/commands/show/testdata/gpg.in.txt000066400000000000000000000024001477253552300234010ustar00rootroot00000000000000tree 6cd5cc5fbf2bac0aac027e411ec3072699e7028c parent 7bac7137354d99bd876d453e6dc98c6961975d93 author Billy Lynch 1668000929 -0500 committer GitHub 1668000929 -0500 gpgsig -----BEGIN PGP SIGNATURE----- wsBcBAABCAAQBQJja6yhCRBK7hj4Ov3rIwAAYakIAEvzgwrtNu/GLTIgMXOPylQy Bo4FBGRnBshaA/nOVfgjinw8Cixrb9N9/YvkQU+ub/I24MEc+R6YsjbGDXqre/Ny YSakMBYLJeiPAP0y9GMBXoD8HEk3nl1Ae1BXpRMS9dzHzGOwESuv9BEo5D+RhpXw GeeoUFjK8/ISB7Qad5n61brQtinFYwP+3qu+14hwFMJPkQcIqtdHeXd0uFkO0/Th 1vellvL9yTSOspaWD9qG7s4x2ZRPwf9MjgRp1NTkGDfH9xpqcXWqJl5AorHZdSTk bzg98srLKvqcCTBj09WJbWDIu4e/pZc4lFEd3APCSU6kEIJigizXf2uEIfuPZpE= =/vdg -----END PGP SIGNATURE----- Refactor commands with Cobra. (#185) This PR rewrites the commands using Cobra so that it can be easier to add additional subcommands (i.e. gitsign cache, gitsign attest, etc.) This change doesn't add any new functionality, though it does refactor a good chunk of the config, flags, status printing, and other global state to make it more easily consumable by packages. Co-authored-by: Eddie Zaneski Signed-off-by: Billy Lynch Signed-off-by: Billy Lynch Co-authored-by: Eddie Zaneski gitsign-0.13.0/internal/commands/show/testdata/gpg.out.json000066400000000000000000000036021477253552300237410ustar00rootroot00000000000000{ "_type": "https://in-toto.io/Statement/v0.1", "predicateType": "https://gitsign.sigstore.dev/predicate/git/v0.1", "subject": [ { "name": "git@github.com:wlynch/gitsign.git", "digest": { "sha1": "262c05491554c57ee641461f315bf4023d0e93c7" } } ], "predicate": { "source": { "tree": "6cd5cc5fbf2bac0aac027e411ec3072699e7028c", "parents": [ "7bac7137354d99bd876d453e6dc98c6961975d93" ], "author": { "name": "Billy Lynch", "email": "billy@chainguard.dev", "date": "2022-11-09T08:35:29-05:00" }, "committer": { "name": "GitHub", "email": "noreply@github.com", "date": "2022-11-09T08:35:29-05:00" }, "message": "Refactor commands with Cobra. (#185)\n\nThis PR rewrites the commands using Cobra so that it can be easier to\r\nadd additional subcommands (i.e. gitsign cache, gitsign attest, etc.)\r\n\r\nThis change doesn't add any new functionality, though it does refactor a\r\ngood chunk of the config, flags, status printing, and other global state\r\nto make it more easily consumable by packages.\r\n\r\nCo-authored-by: Eddie Zaneski \u003ceddiezane@chainguard.dev\u003e\r\nSigned-off-by: Billy Lynch \u003cbilly@chainguard.dev\u003e\r\n\r\nSigned-off-by: Billy Lynch \u003cbilly@chainguard.dev\u003e\r\nCo-authored-by: Eddie Zaneski \u003ceddiezane@chainguard.dev\u003e" }, "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJja6yhCRBK7hj4Ov3rIwAAYakIAEvzgwrtNu/GLTIgMXOPylQy\nBo4FBGRnBshaA/nOVfgjinw8Cixrb9N9/YvkQU+ub/I24MEc+R6YsjbGDXqre/Ny\nYSakMBYLJeiPAP0y9GMBXoD8HEk3nl1Ae1BXpRMS9dzHzGOwESuv9BEo5D+RhpXw\nGeeoUFjK8/ISB7Qad5n61brQtinFYwP+3qu+14hwFMJPkQcIqtdHeXd0uFkO0/Th\n1vellvL9yTSOspaWD9qG7s4x2ZRPwf9MjgRp1NTkGDfH9xpqcXWqJl5AorHZdSTk\nbzg98srLKvqcCTBj09WJbWDIu4e/pZc4lFEd3APCSU6kEIJigizXf2uEIfuPZpE=\n=/vdg\n-----END PGP SIGNATURE-----\n\n" } } gitsign-0.13.0/internal/commands/verify-tag/000077500000000000000000000000001477253552300207465ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/verify-tag/verify_tag.go000066400000000000000000000074331477253552300234430ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verifytag import ( "context" "encoding/pem" "fmt" "io" "os" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" cosignopts "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/gitsign/internal/commands/verify" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/gitsign/internal/gitsign" "github.com/spf13/cobra" ) type options struct { Config *config.Config cosignopts.CertVerifyOptions } func (o *options) AddFlags(cmd *cobra.Command) { o.CertVerifyOptions.AddFlags(cmd) } func (o *options) Run(_ io.Writer, args []string) error { ctx := context.Background() repo, err := gogit.PlainOpenWithOptions(".", &gogit.PlainOpenOptions{ DetectDotGit: true, }) if err != nil { return err } if len(args) == 0 { return fmt.Errorf("tag reference is required") } tagRef := args[0] // Resolve the tag reference ref, err := repo.Reference(plumbing.ReferenceName(fmt.Sprintf("refs/tags/%s", tagRef)), true) if err != nil { return fmt.Errorf("error resolving tag reference: %w", err) } // Get the tag object tagObj, err := repo.TagObject(ref.Hash()) if err != nil { return fmt.Errorf("error reading tag object: %w", err) } // Extract the signature sig := []byte(tagObj.PGPSignature) p, _ := pem.Decode(sig) if p == nil || p.Type != "SIGNED MESSAGE" { return fmt.Errorf("unsupported signature type") } // Get the tag data without the signature tagData := new(plumbing.MemoryObject) if err := tagObj.EncodeWithoutSignature(tagData); err != nil { return err } r, err := tagData.Reader() if err != nil { return err } defer r.Close() data, err := io.ReadAll(r) if err != nil { return err } // Verify the signature v, err := gitsign.NewVerifierWithCosignOpts(ctx, o.Config, &o.CertVerifyOptions) if err != nil { return err } summary, err := v.Verify(ctx, data, sig, true) if err != nil { return err } // Import the internal package just for the PrintSummary function verify.PrintSummary(os.Stdout, summary) return nil } func New(cfg *config.Config) *cobra.Command { o := &options{Config: cfg} cmd := &cobra.Command{ Use: "verify-tag ", Args: cobra.ExactArgs(1), SilenceUsage: true, Short: "Verify a tag", Long: `Verify a tag. verify-tag verifies a tag against a set of certificate claims. This should generally be used over git verify-tag, since verify-tag will check the identity included in the signature's certificate.`, RunE: func(_ *cobra.Command, args []string) error { // Simulate unknown flag errors. if o.Cert != "" { return fmt.Errorf("unknown flag: --certificate") } if o.CertChain != "" { return fmt.Errorf("unknown flag: --certificate-chain") } return o.Run(os.Stdout, args) }, } o.AddFlags(cmd) // Hide flags we don't implement. // --certificate: The cert should always come from the tag. _ = cmd.Flags().MarkHidden("certificate") // --certificate-chain: We only support reading from a TUF root at the moment. // TODO: add support for this. _ = cmd.Flags().MarkHidden("certificate-chain") // --ca-intermediates and --ca-roots _ = cmd.Flags().MarkHidden("ca-intermediates") _ = cmd.Flags().MarkHidden("ca-roots") return cmd } gitsign-0.13.0/internal/commands/verify/000077500000000000000000000000001477253552300201755ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/verify/verify.go000066400000000000000000000102531477253552300220310ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package verify import ( "context" "encoding/pem" "fmt" "io" "os" gogit "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" cosignopts "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/gitsign/internal" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/gitsign/internal/gitsign" "github.com/sigstore/gitsign/pkg/git" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/spf13/cobra" ) type options struct { Config *config.Config cosignopts.CertVerifyOptions } func (o *options) AddFlags(cmd *cobra.Command) { o.CertVerifyOptions.AddFlags(cmd) } func (o *options) Run(_ io.Writer, args []string) error { ctx := context.Background() repo, err := gogit.PlainOpenWithOptions(".", &gogit.PlainOpenOptions{ DetectDotGit: true, }) if err != nil { return err } revision := "HEAD" if len(args) > 0 { revision = args[0] } h, err := repo.ResolveRevision(plumbing.Revision(revision)) if err != nil { return fmt.Errorf("error resolving commit object: %w", err) } c, err := repo.CommitObject(*h) if err != nil { return fmt.Errorf("error reading commit object: %w", err) } sig := []byte(c.PGPSignature) p, _ := pem.Decode(sig) if p == nil || p.Type != "SIGNED MESSAGE" { return fmt.Errorf("unsupported signature type") } c2 := new(plumbing.MemoryObject) if err := c.EncodeWithoutSignature(c2); err != nil { return err } r, err := c2.Reader() if err != nil { return err } defer r.Close() data, err := io.ReadAll(r) if err != nil { return err } v, err := gitsign.NewVerifierWithCosignOpts(ctx, o.Config, &o.CertVerifyOptions) if err != nil { return err } summary, err := v.Verify(ctx, data, sig, true) if err != nil { return err } PrintSummary(os.Stdout, summary) return nil } func PrintSummary(w io.Writer, summary *git.VerificationSummary) { fpr := internal.CertHexFingerprint(summary.Cert) fmt.Fprintln(w, "tlog index:", *summary.LogEntry.LogIndex) fmt.Fprintf(w, "gitsign: Signature made using certificate ID 0x%s | %v\n", fpr, summary.Cert.Issuer) ce := cosign.CertExtensions{Cert: summary.Cert} fmt.Fprintf(w, "gitsign: Good signature from %v(%s)\n", cryptoutils.GetSubjectAlternateNames(summary.Cert), ce.GetIssuer()) for _, c := range summary.Claims { fmt.Fprintf(w, "%s: %t\n", string(c.Key), c.Value) } } func New(cfg *config.Config) *cobra.Command { o := &options{Config: cfg} cmd := &cobra.Command{ Use: "verify [commit]", Args: cobra.MaximumNArgs(1), SilenceUsage: true, Short: "Verify a commit", Long: `Verify a commit. verify verifies a commit against a set of certificate claims. This should generally be used over git verify-commit, since verify will check the identity included in the signature's certificate. If no revision is specified, HEAD is used.`, RunE: func(_ *cobra.Command, args []string) error { // Simulate unknown flag errors. if o.Cert != "" { return fmt.Errorf("unknown flag: --certificate") } if o.CertChain != "" { return fmt.Errorf("unknown flag: --certificate-chain") } return o.Run(os.Stdout, args) }, } o.AddFlags(cmd) // Hide flags we don't implement. // --certificate: The cert should always come from the commit. _ = cmd.Flags().MarkHidden("certificate") // --certificate-chain: We only support reading from a TUF root at the moment. // TODO: add support for this. _ = cmd.Flags().MarkHidden("certificate-chain") // --ca-intermediates and --ca-roots _ = cmd.Flags().MarkHidden("ca-intermediates") _ = cmd.Flags().MarkHidden("ca-roots") return cmd } gitsign-0.13.0/internal/commands/version/000077500000000000000000000000001477253552300203565ustar00rootroot00000000000000gitsign-0.13.0/internal/commands/version/version.go000066400000000000000000000024161477253552300223750ustar00rootroot00000000000000// // 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 version import ( "encoding/json" "fmt" "os" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/gitsign/pkg/version" "github.com/spf13/cobra" ) func New(cfg *config.Config) *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "print Gitsign version", RunE: func(_ *cobra.Command, _ []string) error { v := version.GetVersionInfo() fmt.Println("gitsign version", v.GitVersion) if len(v.Env) > 0 { fmt.Println("env:") for _, e := range v.Env { fmt.Println("\t", e) } } fmt.Println("parsed config:") enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(cfg) }, } return cmd } gitsign-0.13.0/internal/config/000077500000000000000000000000001477253552300163355ustar00rootroot00000000000000gitsign-0.13.0/internal/config/config.go000066400000000000000000000216471477253552300201430ustar00rootroot00000000000000// 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 config import ( "bufio" "bytes" "fmt" "io" "log" "os" "os/exec" "strconv" "strings" "unicode/utf8" ) type RekorVerificationMode int const ( RekorVerificationOnline RekorVerificationMode = iota RekorVerificationOffline ) var ( // execFn is a function to get the raw git config. // Configurable to allow for overriding for testing. execFn = realExec ) // Config represents configuration options for gitsign. type Config struct { // Address of Fulcio server Fulcio string // Path to PEM encoded certificate root for Fulcio. FulcioRoot string // Address of Rekor server Rekor string // Rekor storage mode to operate in. One of [online, offline] (default: online) // online - Commit SHAs are stored in Rekor, requiring online verification for all commit objects. // offline - Hashed commit content is stored in Rekor, with Rekor attributes // necessary for offline verification being stored in the commit itself. // Note: online verification will be deprecated in favor of offline in the future. RekorMode string // OIDC client ID for application ClientID string // File containing the OIDC Client Secret clientSecretFile string // OIDC Redirect URL RedirectURL string // OIDC provider to be used to issue ID token Issuer string // Optional Connector ID to use when fetching Dex OIDC token. // See https://github.com/sigstore/sigstore/blob/c645ceb9d075499f3a4b3f183d3a6864640fa956/pkg/oauthflow/flow.go#L49-L53 // for more details. ConnectorID string // TokenProviders select a OIDC token provider to use to fetch tokens. If not set, all providers are attempted. // See https://github.com/sigstore/cosign/tree/main/pkg/providers for more details. // Valid values are: [interactive, spiffe, google-workload-identity, google-impersonation, github-actions, filesystem, buildkite-agent] TokenProvider string // Timestamp Authority address to use to get a trusted timestamp TimestampURL string // Timestamp Authority PEM encoded cert(s) to use for verification. TimestampCert string // Path to log status output. Helpful for debugging when no TTY is available in the environment. LogPath string // Committer details CommitterName string CommitterEmail string MatchCommitter bool // Autoclose specifies whether to close window after successful authentication Autoclose bool // AutocloseTimeout specifies the time to wait before closing the window AutocloseTimeout int } // CLientSecret retrieves the OIDC client secret from the file provided func (o *Config) ClientSecret() (string, error) { if o.clientSecretFile != "" { clientSecretBytes, err := os.ReadFile(o.clientSecretFile) if err != nil { return "", fmt.Errorf("reading OIDC client secret: %w", err) } if !utf8.Valid(clientSecretBytes) { return "", fmt.Errorf("OIDC client secret in file %s not valid utf8", o.clientSecretFile) } clientSecretString := string(clientSecretBytes) clientSecretString = strings.TrimSpace(clientSecretString) return clientSecretString, nil } return "", nil } // Get fetches the gitsign config options for the repo in the current working // directory. func Get() (*Config, error) { r, err := execFn() if err != nil { return nil, fmt.Errorf("error reading config: %w", err) } cfg := parseConfig(r) // Start with default config out := &Config{ Fulcio: "https://fulcio.sigstore.dev", Rekor: "https://rekor.sigstore.dev", ClientID: "sigstore", Issuer: "https://oauth2.sigstore.dev/auth", // TODO: default to offline RekorMode: "online", Autoclose: true, AutocloseTimeout: 6, } // Get values from config file. applyGitOptions(out, cfg) // Get values from env vars. // Same as GITSIGN_FULCIO_ROOT, but using legacy cosign value for compatibility. // Long term we're likely going to be moving away from this. // See https://github.com/sigstore/sigstore/pull/759 for more discussion. out.FulcioRoot = envOrValue("SIGSTORE_ROOT_FILE", out.FulcioRoot) // Check for common environment variables that could be shared with other // Sigstore tools. Gitsign envs should take precedence. for _, prefix := range []string{"SIGSTORE", "GITSIGN"} { out.Fulcio = envOrValue(fmt.Sprintf("%s_FULCIO_URL", prefix), out.Fulcio) out.FulcioRoot = envOrValue(fmt.Sprintf("%s_FULCIO_ROOT", prefix), out.FulcioRoot) out.Rekor = envOrValue(fmt.Sprintf("%s_REKOR_URL", prefix), out.Rekor) out.ClientID = envOrValue(fmt.Sprintf("%s_OIDC_CLIENT_ID", prefix), out.ClientID) out.clientSecretFile = envOrValue(fmt.Sprintf("%s_OIDC_CLIENT_SECRET_FILE", prefix), out.clientSecretFile) out.RedirectURL = envOrValue(fmt.Sprintf("%s_OIDC_REDIRECT_URL", prefix), out.RedirectURL) out.Issuer = envOrValue(fmt.Sprintf("%s_OIDC_ISSUER", prefix), out.Issuer) out.ConnectorID = envOrValue(fmt.Sprintf("%s_CONNECTOR_ID", prefix), out.ConnectorID) out.TokenProvider = envOrValue(fmt.Sprintf("%s_TOKEN_PROVIDER", prefix), out.TokenProvider) out.TimestampURL = envOrValue(fmt.Sprintf("%s_TIMESTAMP_SERVER_URL", prefix), out.TimestampURL) out.TimestampCert = envOrValue(fmt.Sprintf("%s_TIMESTAMP_CERT_CHAIN", prefix), out.TimestampCert) out.Autoclose = envOrValue(fmt.Sprintf("%s_AUTOCLOSE", prefix), fmt.Sprintf("%t", out.Autoclose)) == "true" out.AutocloseTimeout, _ = strconv.Atoi(envOrValue(fmt.Sprintf("%s_AUTOCLOSE_TIMEOUT", prefix), fmt.Sprintf("%d", out.AutocloseTimeout))) } out.LogPath = envOrValue("GITSIGN_LOG", out.LogPath) out.RekorMode = envOrValue("GITSIGN_REKOR_MODE", out.RekorMode) return out, nil } // realExec forks out to the git binary to read the git config. // We do this as a hack since go-git has issues parsing global configs // for custom fields (https://github.com/go-git/go-git/issues/508) and // doesn't support deprecated subsection syntaxes // (https://github.com/sigstore/gitsign/issues/142). func realExec() (io.Reader, error) { cmd := exec.Command("git", "config", "--get-regexp", `.*`) stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) cmd.Stdout = stdout cmd.Stderr = stderr if err := cmd.Run(); err != nil { if cmd.ProcessState.ExitCode() == 1 && stderr.Len() == 0 { // git config returning exit code 1 with no stderr message can // happen if there are no gitsign related configs set. Treat this // like an non-error / empty config. return stdout, nil } return nil, fmt.Errorf("%w: %s", err, stderr) } return stdout, nil } func parseConfig(r io.Reader) map[string]string { out := map[string]string{} s := bufio.NewScanner(r) for s.Scan() { raw := s.Text() data := strings.Split(raw, " ") if len(data) < 2 { continue } out[data[0]] = strings.Join(data[1:], " ") } return out } func applyGitOptions(out *Config, cfg map[string]string) { for k, v := range cfg { switch { case strings.EqualFold(k, "user.name"): out.CommitterName = v case strings.EqualFold(k, "user.email"): out.CommitterEmail = v case strings.EqualFold(k, "gitsign.fulcio"): out.Fulcio = v case strings.EqualFold(k, "gitsign.fulcioRoot"): out.FulcioRoot = v case strings.EqualFold(k, "gitsign.rekor"): out.Rekor = v case strings.EqualFold(k, "gitsign.rekorMode"): out.RekorMode = v case strings.EqualFold(k, "gitsign.clientID"): out.ClientID = v case strings.EqualFold(k, "gitsign.clientSecretFile"): out.clientSecretFile = v case strings.EqualFold(k, "gitsign.redirectURL"): out.RedirectURL = v case strings.EqualFold(k, "gitsign.issuer"): out.Issuer = v case strings.EqualFold(k, "gitsign.logPath"): out.LogPath = v case strings.EqualFold(k, "gitsign.connectorID"): out.ConnectorID = v case strings.EqualFold(k, "gitsign.tokenProvider"): out.TokenProvider = v case strings.EqualFold(k, "gitsign.timestampServerURL"): out.TimestampURL = v case strings.EqualFold(k, "gitsign.timestampCertChain"): out.TimestampCert = v case strings.EqualFold(k, "gitsign.matchCommitter"): out.MatchCommitter = strings.EqualFold(v, "true") case strings.EqualFold(k, "gitsign.autoclose"): out.Autoclose = strings.EqualFold(v, "true") case strings.EqualFold(k, "gitsign.autocloseTimeout"): if i, err := strconv.Atoi(v); err == nil && i > 0 { out.AutocloseTimeout = i } else { log.Printf("invalid gitsign.autocloseTimeout value %q, defaulting to 6", v) out.AutocloseTimeout = 6 } } } } func envOrValue(env, value string) string { // Only override values if the environment variable is set. if v, ok := os.LookupEnv(env); ok { return v } return value } gitsign-0.13.0/internal/config/config_test.go000066400000000000000000000050401477253552300211670ustar00rootroot00000000000000// 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 config import ( "io" "os" "testing" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" format "github.com/go-git/go-git/v5/plumbing/format/config" "github.com/go-git/go-git/v5/storage/memory" "github.com/google/go-cmp/cmp" ) func TestGet(t *testing.T) { // Create in-memory repo for testing. repo, err := git.Init(memory.NewStorage(), memfs.New()) if err != nil { t.Fatal(err) } cfg := &format.Config{ Sections: format.Sections{ &format.Section{ Name: "gitsign", Options: format.Options{ // This will be ignored. &format.Option{ Key: "foo", Value: "bar", }, &format.Option{ Key: "fulcio", Value: "example.com", }, &format.Option{ Key: "rekor", Value: "example.com", }, }, }, }, } if err := repo.SetConfig(&config.Config{ Raw: cfg, }); err != nil { t.Fatal(err) } // This should take precedence over config value. t.Setenv("GITSIGN_REKOR_URL", "rekor.example.com") // This just overrides default value. t.Setenv("GITSIGN_OIDC_ISSUER", "tacocat") // Recognize SIGSTORE prefixes. t.Setenv("SIGSTORE_OIDC_REDIRECT_URL", "example.com") // GITSIGN prefix takes priority over SIGSTORE. t.Setenv("SIGSTORE_CONNECTOR_ID", "foo") t.Setenv("GITSIGN_CONNECTOR_ID", "bar") want := &Config{ // Default overridden by config Fulcio: "example.com", // Overridden by config, then by env var Rekor: "rekor.example.com", // Default value ClientID: "sigstore", // Overridden by env var Issuer: "tacocat", RedirectURL: "example.com", ConnectorID: "bar", RekorMode: "online", Autoclose: true, AutocloseTimeout: 6, } execFn = func() (io.Reader, error) { return os.Open("testdata/config.txt") } got, err := Get() if err != nil { t.Fatal(err) } if diff := cmp.Diff(want, got, cmp.AllowUnexported(Config{})); diff != "" { t.Error(diff) } } gitsign-0.13.0/internal/config/testdata/000077500000000000000000000000001477253552300201465ustar00rootroot00000000000000gitsign-0.13.0/internal/config/testdata/config.txt000066400000000000000000000002311477253552300221500ustar00rootroot00000000000000foo bar gitsign.foo bar gitsign.foo.bar baz gitsign gitsign.connectorid https://accounts.google.com gitsign.FULCIO example.com gitsign.Rekor example.comgitsign-0.13.0/internal/e2e/000077500000000000000000000000001477253552300155435ustar00rootroot00000000000000gitsign-0.13.0/internal/e2e/offline_test.go000066400000000000000000000034151477253552300205560ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build e2e // +build e2e package e2e import ( "context" "crypto/x509" "testing" "github.com/sigstore/gitsign/internal/fulcio/fulcioroots" "github.com/sigstore/gitsign/internal/git/gittest" "github.com/sigstore/gitsign/pkg/git" "github.com/sigstore/gitsign/pkg/rekor" "github.com/sigstore/sigstore/pkg/tuf" ) func TestVerifyOffline(t *testing.T) { ctx := context.Background() // Initialize to prod root. tuf.Initialize(ctx, tuf.DefaultRemoteRoot, nil) root, intermediate, err := fulcioroots.New(x509.NewCertPool(), fulcioroots.FromTUF(ctx)) if err != nil { t.Fatalf("error getting certificate root: %v", err) } client, err := rekor.New("https://rekor.sigstore.dev") if err != nil { t.Fatal(err) } commit := gittest.ParseCommit(t, "testdata/offline.commit") body := gittest.MarshalCommitBody(t, commit) sig := []byte(commit.PGPSignature) verifier, err := git.NewCertVerifier(git.WithRootPool(root), git.WithIntermediatePool(intermediate)) if err != nil { t.Fatal(err) } cert, err := verifier.Verify(ctx, body, sig, true) if err != nil { t.Fatal(err) } tlog, err := client.VerifyInclusion(ctx, sig, cert) if err != nil { t.Fatal(err) } t.Log(*tlog.LogIndex) } gitsign-0.13.0/internal/e2e/testdata/000077500000000000000000000000001477253552300173545ustar00rootroot00000000000000gitsign-0.13.0/internal/e2e/testdata/offline.commit000066400000000000000000000054631477253552300222200ustar00rootroot00000000000000tree 045f683042529876c1fe28f97f416c1501ffa433 parent 74f0d242677d95a477f953260a2686712846c619 author Billy Lynch 1685981477 -0400 committer Billy Lynch 1685981477 -0400 gpgsig -----BEGIN SIGNED MESSAGE----- MIIHPQYJKoZIhvcNAQcCoIIHLjCCByoCAQExDTALBglghkgBZQMEAgEwCwYJKoZI hvcNAQcBoIIC0DCCAswwggJSoAMCAQICFAG6/a9VuUO5SlyJA02E3aCq+I7nMAoG CCqGSM49BAMDMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2ln c3RvcmUtaW50ZXJtZWRpYXRlMB4XDTIzMDYwNTE2MTExOVoXDTIzMDYwNTE2MjEx OVowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKMghfIq5VOfOhMrxqHFiu8c 91EbkxLIDLL1kjdhbNS2EfXg7xH3Q789yMKGGWwh3PxMJ3+6tXYi62b2mWVYwOej ggFxMIIBbTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYD VR0OBBYEFLNvhRMNCI6GVLzZjAKPQHfG+H62MB8GA1UdIwQYMBaAFN/T6c9WJBGW +ajY6ShVosYuGGQ/MCIGA1UdEQEB/wQYMBaBFGJpbGx5QGNoYWluZ3VhcmQuZGV2 MCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgor BgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBiQYKKwYB BAHWeQIEAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6O AAABiIxTwqMAAAQDAEYwRAIgHi+WbGvSz/kRzolvZfTodQZWomPXXVzV617mdESS SzsCIG9lfwWn7Mh+m/EbdIIp7OxjA3av+ywcUDw1o/kSzegbMAoGCCqGSM49BAMD A2gAMGUCMQDVOD6f+899Tiy7l+wominmzOPvkB2AZBr0OQ0JeEFeNRPuyBHrKF1l UCupUEg9JsYCMCIR+qJqcKYkvDfrjQAnYXvuwc/lSZeLlQWCzNcZzACv14IcjklM GA7DgiYJ6aL9tTGCBDMwggQvAgEBME8wNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2 MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUCFAG6/a9VuUO5SlyJA02E 3aCq+I7nMAsGCWCGSAFlAwQCAaBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw HAYJKoZIhvcNAQkFMQ8XDTIzMDYwNTE2MTExOVowLwYJKoZIhvcNAQkEMSIEIFVL lVMl0NAe/AWmTBlo1LsuuVEnTnqXvagu9a1+USZ5MAoGCCqGSM49BAMCBEcwRQIg MvJRYH8YjGI6aKjuj2BIvKtXauKmmfnTw6ftFHKHd7ICIQCtvl/bIvRutgXs2aIE nLMhBKbUH7L3yIxCXXioSlMbbaGCAwowggMGBgorBgEEAYO/MAMBMYIC9gSCAvII /9TuChIiCiDA0j1q1AaXP5VZ87otHKAfhBR9j/xbhEXCJPmLlZGAHRoVCgxoYXNo ZWRyZWtvcmQSBTAuMC4xIKiS+KMGKkgKRjBEAiA2urRGPSYHNU7xitI+aOApUJy1 NoYOC+AhkrypnjFpEQIgf8+9b5Dyrut5SiCLZa9XdbyI3TjEIAFtOkpZtVObzrEy 3wQImMbwCBIgIwe35eP4Z02VSBqLyIBE1vGiNv+XuvMirTgfBSZKntsYmcbwCCIg b8pcUOF3RAEn3Ky+i8xIkB35UCyvB9HLrnXBAbeNXSYiIDBOHEdPTzANJeEaYkQs DHM9bIr0t1XLZY7buhOssc5HIiDIVqC3K3WpKXE5SzsayoO0znmjPcRJlswzcNNn AR4KACIglPzBfGEElradbmop8hj8wjnhiC8SQEjpcSKPyiuNbyciIIffDga3d73Q vvvgiTxtfGbEWjsVtYsZDrBsUtbvF7knIiA1tbjz90bJjXQVCOY8Q9SBJ09haNvU gD4uolKhxpw1/SIg4XF+XqpJWXICWkRlRyHWQSx1Fc7Dc7HG4tF9k9nb9EUiIP4p dv5jdzDOGD+/6oOodk2cCq4ukQkrI29bfF/p9UPFIiCtcSyYQk3g8ShNTxRLipW1 0iwYHUwKJGUY56miIL32Qyr+AQr7AXJla29yLnNpZ3N0b3JlLmRldiAtIDI2MDU3 MzY2NzA5NzI3OTQ3NDYKMTg2MjEyMDkKSXdlMzVlUDRaMDJWU0JxTHlJQkUxdkdp TnYrWHV2TWlyVGdmQlNaS250cz0KVGltZXN0YW1wOiAxNjg1OTgxNDgwMTg3Mzc4 MzQ5CgrigJQgcmVrb3Iuc2lnc3RvcmUuZGV2IHdOSTlhakJFQWlBTTE2enROTGtN azRtUzNqWW02TGVKLy9CdFhkV3VXZEpjd1JQMWtLeXYwUUlnSXB1Zm15MWVHNExF TGh5RjJXbVJmVHdXbldXaGJnMzlxeE5IWWpEY0x6dz0K -----END SIGNED MESSAGE----- Mon Jun 5 12:11:17 EDT 2023 gitsign-0.13.0/internal/fork/000077500000000000000000000000001477253552300160315ustar00rootroot00000000000000gitsign-0.13.0/internal/fork/ietf-cms/000077500000000000000000000000001477253552300175405ustar00rootroot00000000000000gitsign-0.13.0/internal/fork/ietf-cms/LICENSE000066400000000000000000000020541477253552300205460ustar00rootroot00000000000000MIT License Copyright (c) 2017 GitHub, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.gitsign-0.13.0/internal/fork/ietf-cms/README.md000066400000000000000000000045661477253552300210320ustar00rootroot00000000000000# CMS This package is forked from [github/smimesign](https://github.com/github/smimesign) with the following changes: - Adds inclusive checking for cert timestamps in timestamp authorities (https://github.com/github/smimesign/pull/121) - Fixes tests for MacOS due to regressions in return types in Go 1.18 crypto libraries (https://github.com/golang/go/issues/52010) - Adds support for separate cert pools for cert validation and TSA validation. [CMS (Cryptographic Message Syntax)](https://tools.ietf.org/html/rfc5652) is a syntax for signing, digesting, and encrypting arbitrary messages. It evolved from PKCS#7 and is the basis for higher level protocols such as S/MIME. This package implements the SignedData CMS content-type, allowing users to digitally sign data as well as verify data signed by others. ## Signing and Verifying Data High level APIs are provided for signing a message with a certificate and key: ```go msg := []byte("some data") cert, _ := x509.ParseCertificate(someCertificateData) key, _ := x509.ParseECPrivateKey(somePrivateKeyData) der, _ := cms.Sign(msg, []*x509.Certificate{cert}, key) //// /// At another time, in another place... // sd, _ := ParseSignedData(der) if err, _ := sd.Verify(x509.VerifyOptions{}); err != nil { panic(err) } ``` By default, CMS SignedData includes the original message. High level APIs are also available for creating and verifying detached signatures: ```go msg := []byte("some data") cert, _ := x509.ParseCertificate(someCertificateData) key, _ := x509.ParseECPrivateKey(somePrivateKeyData) der, _ := cms.SignDetached(msg, cert, key) //// /// At another time, in another place... // sd, _ := ParseSignedData(der) if err, _ := sd.VerifyDetached(msg, x509.VerifyOptions{}); err != nil { panic(err) } ``` ## Timestamping Because certificates expire and can be revoked, it is may be helpful to attach certified timestamps to signatures, proving that they existed at a given time. RFC3161 timestamps can be added to signatures like so: ```go signedData, _ := NewSignedData([]byte("Hello, world!")) signedData.Sign(identity.Chain(), identity.PrivateKey) signedData.AddTimestamps("http://timestamp.digicert.com") derEncoded, _ := signedData.ToDER() io.Copy(os.Stdout, bytes.NewReader(derEncoded)) ``` Verification functions implicitly verify timestamps as well. Without a timestamp, verification will fail if the certificate is no longer valid. gitsign-0.13.0/internal/fork/ietf-cms/main_test.go000066400000000000000000000070221477253552300220530ustar00rootroot00000000000000package cms import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/asn1" "io" "io/ioutil" "math/big" "net/http" "time" "github.com/github/smimesign/fakeca" "github.com/github/smimesign/ietf-cms/oid" "github.com/github/smimesign/ietf-cms/protocol" "github.com/sigstore/gitsign/internal/fork/ietf-cms/timestamp" ) var ( // fake PKI setup root = fakeca.New(fakeca.IsCA) otherRoot = fakeca.New(fakeca.IsCA) intermediateKey, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) intermediate = root.Issue(fakeca.IsCA, fakeca.PrivateKey(intermediateKey)) leaf = intermediate.Issue( fakeca.NotBefore(time.Now().Add(-time.Hour)), fakeca.NotAfter(time.Now().Add(time.Hour)), ) rootOpts = x509.VerifyOptions{Roots: root.ChainPool()} otherRootOpts = x509.VerifyOptions{Roots: otherRoot.ChainPool()} intermediateOpts = x509.VerifyOptions{Roots: intermediate.ChainPool()} // fake timestamp authority setup tsa = &testTSA{ident: intermediate.Issue()} thc = &testHTTPClient{tsa} ) func init() { timestamp.DefaultHTTPClient = thc } type testTSA struct { ident *fakeca.Identity sn int64 hookInfo func(timestamp.Info) timestamp.Info hookToken func(*protocol.SignedData) *protocol.SignedData hookResponse func(timestamp.Response) timestamp.Response } func (tt *testTSA) Clear() { tt.hookInfo = nil tt.hookToken = nil tt.hookResponse = nil } func (tt *testTSA) HookInfo(hook func(timestamp.Info) timestamp.Info) { tt.Clear() tt.hookInfo = hook } func (tt *testTSA) HookToken(hook func(*protocol.SignedData) *protocol.SignedData) { tt.Clear() tt.hookToken = hook } func (tt *testTSA) HookResponse(hook func(timestamp.Response) timestamp.Response) { tt.Clear() tt.hookResponse = hook } func (tt *testTSA) nextSN() *big.Int { defer func() { tt.sn++ }() return big.NewInt(tt.sn) } func (tt *testTSA) Do(req timestamp.Request) (timestamp.Response, error) { info := timestamp.Info{ Version: 1, Policy: asn1.ObjectIdentifier{1, 2, 3}, SerialNumber: tt.nextSN(), GenTime: time.Now(), MessageImprint: req.MessageImprint, Nonce: req.Nonce, } if tt.hookInfo != nil { info = tt.hookInfo(info) } eciDER, err := asn1.Marshal(info) if err != nil { panic(err) } eci, err := protocol.NewEncapsulatedContentInfo(oid.ContentTypeTSTInfo, eciDER) if err != nil { panic(err) } tst, err := protocol.NewSignedData(eci) if err != nil { panic(err) } if err = tst.AddSignerInfo(tsa.ident.Chain(), tsa.ident.PrivateKey); err != nil { panic(err) } if tt.hookToken != nil { tt.hookToken(tst) } ci, err := tst.ContentInfo() if err != nil { panic(err) } resp := timestamp.Response{ Status: timestamp.PKIStatusInfo{Status: 0}, TimeStampToken: ci, } if tt.hookResponse != nil { resp = tt.hookResponse(resp) } return resp, nil } type testHTTPClient struct { tt *testTSA } func (thc *testHTTPClient) Do(httpReq *http.Request) (*http.Response, error) { buf := new(bytes.Buffer) if _, err := io.Copy(buf, httpReq.Body); err != nil { return nil, err } var tsReq timestamp.Request if _, err := asn1.Unmarshal(buf.Bytes(), &tsReq); err != nil { return nil, err } tsResp, err := thc.tt.Do(tsReq) if err != nil { return nil, err } respDER, err := asn1.Marshal(tsResp) if err != nil { return nil, err } return &http.Response{ StatusCode: 200, Header: http.Header{"Content-Type": {"application/timestamp-reply"}}, Body: ioutil.NopCloser(bytes.NewReader(respDER)), }, nil } gitsign-0.13.0/internal/fork/ietf-cms/sign.go000066400000000000000000000026731477253552300210370ustar00rootroot00000000000000package cms import ( "crypto" "crypto/x509" ) // Sign creates a CMS SignedData from the content and signs it with signer. At // minimum, chain must contain the leaf certificate associated with the signer. // Any additional intermediates will also be added to the SignedData. The DER // encoded CMS message is returned. func Sign(data []byte, chain []*x509.Certificate, signer crypto.Signer) ([]byte, error) { sd, err := NewSignedData(data) if err != nil { return nil, err } if err = sd.Sign(chain, signer); err != nil { return nil, err } return sd.ToDER() } // SignDetached creates a detached CMS SignedData from the content and signs it // with signer. At minimum, chain must contain the leaf certificate associated // with the signer. Any additional intermediates will also be added to the // SignedData. The DER encoded CMS message is returned. func SignDetached(data []byte, chain []*x509.Certificate, signer crypto.Signer) ([]byte, error) { sd, err := NewSignedData(data) if err != nil { return nil, err } if err = sd.Sign(chain, signer); err != nil { return nil, err } sd.Detached() return sd.ToDER() } // Sign adds a signature to the SignedData.At minimum, chain must contain the // leaf certificate associated with the signer. Any additional intermediates // will also be added to the SignedData. func (sd *SignedData) Sign(chain []*x509.Certificate, signer crypto.Signer) error { return sd.psd.AddSignerInfo(chain, signer) } gitsign-0.13.0/internal/fork/ietf-cms/sign_test.go000066400000000000000000000111731477253552300220710ustar00rootroot00000000000000package cms import ( "crypto/x509" "encoding/pem" "io/ioutil" "os" "os/exec" "testing" "time" ) var ( examplePrivateKey = leaf.PrivateKey exampleChain = leaf.Chain() ) func TestSign(t *testing.T) { data := []byte("hello, world!") ci, err := Sign(data, leaf.Chain(), leaf.PrivateKey) if err != nil { t.Fatal(err) } sd2, err := ParseSignedData(ci) if err != nil { t.Fatal(err) } if _, err = sd2.Verify(rootOpts, x509.VerifyOptions{}); err != nil { t.Fatal(err) } // test that we're including whole chain in sd sdCerts, err := sd2.psd.X509Certificates() if err != nil { t.Fatal(err) } for _, chainCert := range leaf.Chain() { var found bool for _, sdCert := range sdCerts { if sdCert.Equal(chainCert) { if found == true { t.Fatal("duplicate cert in sd") } found = true } } if !found { t.Fatal("missing cert in sd") } } // check that we're including signing time attribute st, err := sd2.psd.SignerInfos[0].GetSigningTimeAttribute() if st.After(time.Now().Add(time.Second)) || st.Before(time.Now().Add(-time.Second)) { t.Fatal("expected SigningTime to be now. Difference was", st.Sub(time.Now())) } } func TestSignDetached(t *testing.T) { data := []byte("hello, world!") ci, err := SignDetached(data, leaf.Chain(), leaf.PrivateKey) if err != nil { t.Fatal(err) } sd2, err := ParseSignedData(ci) if err != nil { t.Fatal(err) } if _, err = sd2.VerifyDetached(data, rootOpts, x509.VerifyOptions{}); err != nil { t.Fatal(err) } // test that we're including whole chain in sd sdCerts, err := sd2.psd.X509Certificates() if err != nil { t.Fatal(err) } for _, chainCert := range leaf.Chain() { var found bool for _, sdCert := range sdCerts { if sdCert.Equal(chainCert) { if found == true { t.Fatal("duplicate cert in sd") } found = true } } if !found { t.Fatal("missing cert in sd") } } // check that we're including signing time attribute st, err := sd2.psd.SignerInfos[0].GetSigningTimeAttribute() if st.After(time.Now().Add(time.Second)) || st.Before(time.Now().Add(-time.Second)) { t.Fatal("expected SigningTime to be now. Difference was", st.Sub(time.Now())) } } func TestSignDetachedWithOpenSSL(t *testing.T) { // Do not require this test to pass if openssl is not in the path opensslPath, err := exec.LookPath("openssl") if err != nil { t.Skip("could not find openssl in path") } content := []byte("hello, world!") signatureDER, err := SignDetached(content, leaf.Chain(), leaf.PrivateKey) if err != nil { t.Fatal(err) } signatureFile, err := ioutil.TempFile("", "TestSignatureOpenSSL_signatureFile_*") if err != nil { t.Fatal(err) } _, err = signatureFile.Write(signatureDER) if err != nil { t.Fatal(err) } signatureFile.Close() // write content to a temp file contentFile, err := ioutil.TempFile("", "TestSignatureOpenSSL_contentFile_*") if err != nil { t.Fatal(err) } _, err = contentFile.Write(content) if err != nil { t.Fatal(err) } contentFile.Close() // write CA cert to a temp file certsFile, err := ioutil.TempFile("", "TestSignatureOpenSSL_certsFile_*") if err != nil { t.Fatal(err) } for _, cert := range leaf.Chain() { // write leaf as PEM certBlock := &pem.Block{ Type: "CERTIFICATE", Bytes: cert.Raw, } certPEM := pem.EncodeToMemory(certBlock) _, err = certsFile.Write(certPEM) if err != nil { t.Fatal(err) } } certsFile.Close() cmd := exec.Command(opensslPath, "cms", "-verify", "-content", contentFile.Name(), "-binary", "-in", signatureFile.Name(), "-inform", "DER", "-CAfile", certsFile.Name()) _, err = cmd.CombinedOutput() if err != nil { t.Fatal(err) } // // Remove temporary files if test was successful. // Intentionally leave the temp files if test fails. // os.Remove(contentFile.Name()) os.Remove(signatureFile.Name()) os.Remove(certsFile.Name()) } func TestSignRemoveHeaders(t *testing.T) { sd, err := NewSignedData([]byte("hello, world")) if err != nil { t.Fatal(err) } if err = sd.Sign(leaf.Chain(), leaf.PrivateKey); err != nil { t.Fatal(err) } if err = sd.SetCertificates([]*x509.Certificate{}); err != nil { t.Fatal(err) } if certs, err := sd.GetCertificates(); err != nil { t.Fatal(err) } else if len(certs) != 0 { t.Fatal("expected 0 certs") } der, err := sd.ToDER() if err != nil { t.Fatal(err) } if sd, err = ParseSignedData(der); err != nil { t.Fatal(err) } sd.SetCertificates([]*x509.Certificate{leaf.Certificate}) opts := x509.VerifyOptions{ Roots: root.ChainPool(), Intermediates: leaf.ChainPool(), } if _, err := sd.Verify(opts, x509.VerifyOptions{}); err != nil { t.Fatal(err) } } gitsign-0.13.0/internal/fork/ietf-cms/signed_data.go000066400000000000000000000044241477253552300223350ustar00rootroot00000000000000package cms import ( "crypto/x509" "encoding/asn1" "github.com/github/smimesign/ietf-cms/protocol" ) // SignedData represents a signed message or detached signature. type SignedData struct { psd *protocol.SignedData } // NewSignedData creates a new SignedData from the given data. func NewSignedData(data []byte) (*SignedData, error) { eci, err := protocol.NewDataEncapsulatedContentInfo(data) if err != nil { return nil, err } psd, err := protocol.NewSignedData(eci) if err != nil { return nil, err } return &SignedData{psd}, nil } // ParseSignedData parses a SignedData from BER encoded data. func ParseSignedData(ber []byte) (*SignedData, error) { ci, err := protocol.ParseContentInfo(ber) if err != nil { return nil, err } psd, err := ci.SignedDataContent() if err != nil { return nil, err } return &SignedData{psd}, nil } // GetData gets the encapsulated data from the SignedData. Nil will be returned // if this is a detached signature. A protocol.ErrWrongType will be returned if // the SignedData encapsulates something other than data (1.2.840.113549.1.7.1). func (sd *SignedData) GetData() ([]byte, error) { return sd.psd.EncapContentInfo.DataEContent() } // GetCertificates gets all the certificates stored in the SignedData. func (sd *SignedData) GetCertificates() ([]*x509.Certificate, error) { return sd.psd.X509Certificates() } // SetCertificates replaces the certificates stored in the SignedData with new // ones. func (sd *SignedData) SetCertificates(certs []*x509.Certificate) error { sd.psd.ClearCertificates() for _, cert := range certs { if err := sd.psd.AddCertificate(cert); err != nil { return err } } return nil } // Detached removes the data content from this SignedData. No more signatures // can be added after this method has been called. func (sd *SignedData) Detached() { sd.psd.EncapContentInfo.EContent = asn1.RawValue{} } // IsDetached checks if this SignedData has data content. func (sd *SignedData) IsDetached() bool { return sd.psd.EncapContentInfo.EContent.Bytes == nil } // ToDER encodes this SignedData message using DER. func (sd *SignedData) ToDER() ([]byte, error) { return sd.psd.ContentInfoDER() } // Raw returns the underlying CMS SignedData struct. func (sd *SignedData) Raw() *protocol.SignedData { return sd.psd } gitsign-0.13.0/internal/fork/ietf-cms/timestamp.go000066400000000000000000000063251477253552300221000ustar00rootroot00000000000000package cms import ( "bytes" "crypto/x509" "errors" "github.com/github/smimesign/ietf-cms/oid" "github.com/github/smimesign/ietf-cms/protocol" "github.com/sigstore/gitsign/internal/fork/ietf-cms/timestamp" ) // AddTimestamps adds a timestamp to the SignedData using the RFC3161 // timestamping service at the given URL. This timestamp proves that the signed // message existed the time of generation, allowing verifiers to have more trust // in old messages signed with revoked keys. func (sd *SignedData) AddTimestamps(url string) error { var ( attrs = make([]protocol.Attribute, len(sd.psd.SignerInfos)) err error ) // Fetch all timestamp tokens before adding any to sd. This avoids a partial // failure. for i := range attrs { if attrs[i], err = fetchTS(url, sd.psd.SignerInfos[i]); err != nil { return err } } for i := range attrs { sd.psd.SignerInfos[i].UnsignedAttrs = append(sd.psd.SignerInfos[i].UnsignedAttrs, attrs[i]) } return nil } func fetchTS(url string, si protocol.SignerInfo) (protocol.Attribute, error) { nilAttr := protocol.Attribute{} req, err := tsRequest(si) if err != nil { return nilAttr, err } resp, err := req.Do(url) if err != nil { return nilAttr, err } if tsti, err := resp.Info(); err != nil { return nilAttr, err } else if !req.Matches(tsti) { return nilAttr, errors.New("invalid message imprint") } return protocol.NewAttribute(oid.AttributeTimeStampToken, resp.TimeStampToken) } func tsRequest(si protocol.SignerInfo) (timestamp.Request, error) { hash, err := si.Hash() if err != nil { return timestamp.Request{}, err } mi, err := timestamp.NewMessageImprint(hash, bytes.NewReader(si.Signature)) if err != nil { return timestamp.Request{}, err } return timestamp.Request{ Version: 1, CertReq: true, Nonce: timestamp.GenerateNonce(), MessageImprint: mi, }, nil } // getTimestamp verifies and returns the timestamp.Info from the SignerInfo. func getTimestamp(si protocol.SignerInfo, opts x509.VerifyOptions) (timestamp.Info, error) { rawValue, err := si.UnsignedAttrs.GetOnlyAttributeValueBytes(oid.AttributeTimeStampToken) if err != nil { return timestamp.Info{}, err } tst, err := ParseSignedData(rawValue.FullBytes) if err != nil { return timestamp.Info{}, err } tsti, err := timestamp.ParseInfo(tst.psd.EncapContentInfo) if err != nil { return timestamp.Info{}, err } if tsti.Version != 1 { return timestamp.Info{}, protocol.ErrUnsupported } // verify timestamp signature and certificate chain.. if _, err = tst.Verify(opts, opts); err != nil { return timestamp.Info{}, err } // verify timestamp token matches SignerInfo. hash, err := tsti.MessageImprint.Hash() if err != nil { return timestamp.Info{}, err } mi, err := timestamp.NewMessageImprint(hash, bytes.NewReader(si.Signature)) if err != nil { return timestamp.Info{}, err } if !mi.Equal(tsti.MessageImprint) { return timestamp.Info{}, errors.New("invalid message imprint") } return tsti, nil } // hasTimestamp checks if si has a timestamp. func hasTimestamp(si protocol.SignerInfo) (bool, error) { vals, err := si.UnsignedAttrs.GetValues(oid.AttributeTimeStampToken) if err != nil { return false, err } return len(vals) > 0, nil } gitsign-0.13.0/internal/fork/ietf-cms/timestamp/000077500000000000000000000000001477253552300215435ustar00rootroot00000000000000gitsign-0.13.0/internal/fork/ietf-cms/timestamp/timestamp.go000066400000000000000000000311121477253552300240730ustar00rootroot00000000000000package timestamp import ( "bytes" "crypto" "crypto/rand" "crypto/x509/pkix" "encoding/asn1" "fmt" "io" "math/big" "net/http" "strings" "time" "github.com/github/smimesign/ietf-cms/oid" "github.com/github/smimesign/ietf-cms/protocol" ) // HTTPClient is an interface for *http.Client, allowing callers to customize // HTTP behavior. type HTTPClient interface { Do(*http.Request) (*http.Response, error) } // DefaultHTTPClient is the HTTP client used for fetching timestamps. This // variable may be changed to modify HTTP behavior (eg. add timeouts). var DefaultHTTPClient = HTTPClient(http.DefaultClient) const ( contentTypeTSQuery = "application/timestamp-query" contentTypeTSReply = "application/timestamp-reply" nonceBytes = 16 ) // GenerateNonce generates a new nonce for this TSR. func GenerateNonce() *big.Int { buf := make([]byte, nonceBytes) if _, err := rand.Read(buf); err != nil { panic(err) } return new(big.Int).SetBytes(buf[:]) } // Request is a TimeStampReq // // TimeStampReq ::= SEQUENCE { // version INTEGER { v1(1) }, // messageImprint MessageImprint, // --a hash algorithm OID and the hash value of the data to be // --time-stamped // reqPolicy TSAPolicyId OPTIONAL, // nonce INTEGER OPTIONAL, // certReq BOOLEAN DEFAULT FALSE, // extensions [0] IMPLICIT Extensions OPTIONAL } type Request struct { Version int MessageImprint MessageImprint ReqPolicy asn1.ObjectIdentifier `asn1:"optional"` Nonce *big.Int `asn1:"optional"` CertReq bool `asn1:"optional,default:false"` Extensions []pkix.Extension `asn1:"tag:1,optional"` } // Matches checks if the MessageImprint and Nonce from a responsee match those // of the request. func (req Request) Matches(tsti Info) bool { if !req.MessageImprint.Equal(tsti.MessageImprint) { return false } if req.Nonce != nil && tsti.Nonce == nil || req.Nonce.Cmp(tsti.Nonce) != 0 { return false } return true } // Do sends this timestamp request to the specified timestamp service, returning // the parsed response. The timestamp.HTTPClient is used to make the request and // HTTP behavior can be modified by changing that variable. func (req Request) Do(url string) (Response, error) { var nilResp Response reqDER, err := asn1.Marshal(req) if err != nil { return nilResp, err } httpReq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(reqDER)) if err != nil { return nilResp, err } httpReq.Header.Add("Content-Type", contentTypeTSQuery) httpResp, err := DefaultHTTPClient.Do(httpReq) if err != nil { return nilResp, err } if ct := httpResp.Header.Get("Content-Type"); ct != contentTypeTSReply { return nilResp, fmt.Errorf("Bad content-type: %s", ct) } buf := bytes.NewBuffer(make([]byte, 0, httpResp.ContentLength)) if _, err = io.Copy(buf, httpResp.Body); err != nil { return nilResp, err } return ParseResponse(buf.Bytes()) } // Response is a TimeStampResp // // TimeStampResp ::= SEQUENCE { // status PKIStatusInfo, // timeStampToken TimeStampToken OPTIONAL } // // TimeStampToken ::= ContentInfo type Response struct { Status PKIStatusInfo TimeStampToken protocol.ContentInfo `asn1:"optional"` } // ParseResponse parses a BER encoded TimeStampResp. func ParseResponse(ber []byte) (Response, error) { var resp Response der, err := protocol.BER2DER(ber) if err != nil { return resp, err } rest, err := asn1.Unmarshal(der, &resp) if err != nil { return resp, err } if len(rest) > 0 { return resp, protocol.ErrTrailingData } return resp, nil } // Info gets an Info from the response, doing no validation of the SignedData. func (r Response) Info() (Info, error) { var nilInfo Info if err := r.Status.GetError(); err != nil { return nilInfo, err } sd, err := r.TimeStampToken.SignedDataContent() if err != nil { return nilInfo, err } return ParseInfo(sd.EncapContentInfo) } // PKIStatusInfo ::= SEQUENCE { // status PKIStatus, // statusString PKIFreeText OPTIONAL, // failInfo PKIFailureInfo OPTIONAL } // // PKIStatus ::= INTEGER { // granted (0), // -- when the PKIStatus contains the value zero a TimeStampToken, as // requested, is present. // grantedWithMods (1), // -- when the PKIStatus contains the value one a TimeStampToken, // with modifications, is present. // rejection (2), // waiting (3), // revocationWarning (4), // -- this message contains a warning that a revocation is // -- imminent // revocationNotification (5) // -- notification that a revocation has occurred } // // -- When the TimeStampToken is not present // -- failInfo indicates the reason why the // -- time-stamp request was rejected and // -- may be one of the following values. // // PKIFailureInfo ::= BIT STRING { // badAlg (0), // -- unrecognized or unsupported Algorithm Identifier // badRequest (2), // -- transaction not permitted or supported // badDataFormat (5), // -- the data submitted has the wrong format // timeNotAvailable (14), // -- the TSA's time source is not available // unacceptedPolicy (15), // -- the requested TSA policy is not supported by the TSA. // unacceptedExtension (16), // -- the requested extension is not supported by the TSA. // addInfoNotAvailable (17) // -- the additional information requested could not be understood // -- or is not available // systemFailure (25) // -- the request cannot be handled due to system failure } type PKIStatusInfo struct { Status int StatusString PKIFreeText `asn1:"optional"` FailInfo asn1.BitString `asn1:"optional"` } // Error represents an unsuccessful PKIStatusInfo as an error. func (si PKIStatusInfo) GetError() error { if si.Status == 0 { return nil } return si } // Error implements the error interface. func (si PKIStatusInfo) Error() string { fiStr := "" if si.FailInfo.BitLength > 0 { fibin := make([]byte, si.FailInfo.BitLength) for i := range fibin { if si.FailInfo.At(i) == 1 { fibin[i] = byte('1') } else { fibin[i] = byte('0') } } fiStr = fmt.Sprintf(" FailInfo(0b%s)", string(fibin)) } statusStr := "" if len(si.StatusString) > 0 { if strs, err := si.StatusString.Strings(); err == nil { statusStr = fmt.Sprintf(" StatusString(%s)", strings.Join(strs, ",")) } } return fmt.Sprintf("Bad TimeStampResp: Status(%d)%s%s", si.Status, statusStr, fiStr) } // PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String type PKIFreeText []asn1.RawValue // Append returns a new copy of the PKIFreeText with the provided string // appended. func (ft PKIFreeText) Append(t string) PKIFreeText { return append(ft, asn1.RawValue{ Class: asn1.ClassUniversal, Tag: asn1.TagUTF8String, Bytes: []byte(t), }) } // Strings decodes the PKIFreeText into a []string. func (ft PKIFreeText) Strings() ([]string, error) { strs := make([]string, len(ft)) for i := range ft { if rest, err := asn1.Unmarshal(ft[i].FullBytes, &strs[i]); err != nil { return nil, err } else if len(rest) != 0 { return nil, protocol.ErrTrailingData } } return strs, nil } // Info is a TSTInfo // // TSTInfo ::= SEQUENCE { // version INTEGER { v1(1) }, // policy TSAPolicyId, // messageImprint MessageImprint, // -- MUST have the same value as the similar field in // -- TimeStampReq // serialNumber INTEGER, // -- Time-Stamping users MUST be ready to accommodate integers // -- up to 160 bits. // genTime GeneralizedTime, // accuracy Accuracy OPTIONAL, // ordering BOOLEAN DEFAULT FALSE, // nonce INTEGER OPTIONAL, // -- MUST be present if the similar field was present // -- in TimeStampReq. In that case it MUST have the same value. // tsa [0] GeneralName OPTIONAL, // extensions [1] IMPLICIT Extensions OPTIONAL } // // TSAPolicyId ::= OBJECT IDENTIFIER type Info struct { Version int Policy asn1.ObjectIdentifier MessageImprint MessageImprint SerialNumber *big.Int GenTime time.Time `asn1:"generalized"` Accuracy Accuracy `asn1:"optional"` Ordering bool `asn1:"optional,default:false"` Nonce *big.Int `asn1:"optional"` TSA asn1.RawValue `asn1:"tag:0,optional"` Extensions []pkix.Extension `asn1:"tag:1,optional"` } // ParseInfo parses an Info out of a CMS EncapsulatedContentInfo. func ParseInfo(eci protocol.EncapsulatedContentInfo) (Info, error) { i := Info{} if !eci.EContentType.Equal(oid.ContentTypeTSTInfo) { return i, protocol.ErrWrongType } ecval, err := eci.EContentValue() if err != nil { return i, err } if ecval == nil { return i, protocol.ASN1Error{Message: "missing EContent for non data type"} } if rest, err := asn1.Unmarshal(ecval, &i); err != nil { return i, err } else if len(rest) > 0 { return i, protocol.ErrTrailingData } return i, nil } // Before checks if the latest time the signature could have been generated at // is before the specified time. For example, you might check that a signature // was made *before* a certificate's not-after date. func (i *Info) Before(t time.Time) bool { return i.genTimeMax().Before(t) || i.genTimeMax().Equal(t) } // After checks if the earlier time the signature could have been generated at // is before the specified time. For example, you might check that a signature // was made *after* a certificate's not-before date. func (i *Info) After(t time.Time) bool { return i.genTimeMin().After(t) || i.genTimeMin().Equal(t) } // genTimeMax is the latest time at which the token could have been generated // based on the included GenTime and Accuracy attributes. func (i *Info) genTimeMax() time.Time { return i.GenTime.Add(i.Accuracy.Duration()) } // genTimeMin is the earliest time at which the token could have been generated // based on the included GenTime and Accuracy attributes. func (i *Info) genTimeMin() time.Time { return i.GenTime.Add(-i.Accuracy.Duration()) } // MessageImprint ::= SEQUENCE { // hashAlgorithm AlgorithmIdentifier, // hashedMessage OCTET STRING } type MessageImprint struct { HashAlgorithm pkix.AlgorithmIdentifier HashedMessage []byte } // NewMessageImprint creates a new MessageImprint, digesting all bytes from the // provided reader using the specified hash. func NewMessageImprint(hash crypto.Hash, r io.Reader) (MessageImprint, error) { digestAlgorithm := oid.CryptoHashToDigestAlgorithm[hash] if len(digestAlgorithm) == 0 { return MessageImprint{}, protocol.ErrUnsupported } if !hash.Available() { return MessageImprint{}, protocol.ErrUnsupported } h := hash.New() if _, err := io.Copy(h, r); err != nil { return MessageImprint{}, err } return MessageImprint{ HashAlgorithm: pkix.AlgorithmIdentifier{Algorithm: digestAlgorithm}, HashedMessage: h.Sum(nil), }, nil } // Hash gets the crypto.Hash associated with this SignerInfo's DigestAlgorithm. // 0 is returned for unrecognized algorithms. func (mi MessageImprint) Hash() (crypto.Hash, error) { algo := mi.HashAlgorithm.Algorithm.String() hash := oid.DigestAlgorithmToCryptoHash[algo] if hash == 0 || !hash.Available() { return 0, protocol.ErrUnsupported } return hash, nil } // Equal checks if this MessageImprint is identical to another MessageImprint. func (mi MessageImprint) Equal(other MessageImprint) bool { if !mi.HashAlgorithm.Algorithm.Equal(other.HashAlgorithm.Algorithm) { return false } if len(mi.HashAlgorithm.Parameters.Bytes) > 0 || len(other.HashAlgorithm.Parameters.Bytes) > 0 { if !bytes.Equal(mi.HashAlgorithm.Parameters.FullBytes, other.HashAlgorithm.Parameters.FullBytes) { return false } } if !bytes.Equal(mi.HashedMessage, other.HashedMessage) { return false } return true } // Accuracy ::= SEQUENCE { // seconds INTEGER OPTIONAL, // millis [0] INTEGER (1..999) OPTIONAL, // micros [1] INTEGER (1..999) OPTIONAL } type Accuracy struct { Seconds int `asn1:"optional"` Millis int `asn1:"tag:0,optional"` Micros int `asn1:"tag:1,optional"` } // Duration returns this Accuracy as a time.Duration. func (a Accuracy) Duration() time.Duration { return 0 + time.Duration(a.Seconds)*time.Second + time.Duration(a.Millis)*time.Millisecond + time.Duration(a.Micros)*time.Microsecond } gitsign-0.13.0/internal/fork/ietf-cms/timestamp/timestamp_test.go000066400000000000000000000643631477253552300251500ustar00rootroot00000000000000package timestamp import ( "bytes" "crypto" "crypto/x509" "encoding/asn1" "encoding/base64" "encoding/hex" "errors" "io" "math/big" "net/http" "strings" "testing" "time" "github.com/github/smimesign/ietf-cms/protocol" ) var ( errFakeClient = errors.New("fake client") lastRequest *http.Request ) type testHTTPClient struct{} func (c testHTTPClient) Do(req *http.Request) (*http.Response, error) { lastRequest = req return nil, errFakeClient } func TestRequestDo(t *testing.T) { DefaultHTTPClient = testHTTPClient{} var ( req = Request{Version: 1} err error ) req.CertReq = true req.Nonce = GenerateNonce() if req.MessageImprint, err = NewMessageImprint(crypto.SHA256, bytes.NewReader([]byte("hello"))); err != nil { t.Fatal(err) } if _, err = req.Do("https://google.com"); err != errFakeClient { t.Fatalf("expected errFakeClient, got %v", err) } if lastRequest == nil { t.Fatal("expected lastRequest") } if ct := lastRequest.Header.Get("Content-Type"); ct != contentTypeTSQuery { t.Fatalf("expected ts content-type, got %s", ct) } body, err := lastRequest.GetBody() if err != nil { t.Fatal(err) } buf := bytes.NewBuffer(nil) if _, err := io.Copy(buf, body); err != nil { t.Fatal(err) } var req2 Request if rest, err := asn1.Unmarshal(buf.Bytes(), &req2); err != nil { t.Fatal(err) } else if len(rest) > 0 { t.Fatal("unexpected trailing data") } } func TestRequestMatches(t *testing.T) { var err error req := Request{Version: 1} req.Nonce = GenerateNonce() if req.MessageImprint, err = NewMessageImprint(crypto.SHA256, bytes.NewReader([]byte("hello"))); err != nil { t.Fatal(err) } tsti := Info{ MessageImprint: req.MessageImprint, Nonce: new(big.Int).Set(req.Nonce), } if !req.Matches(tsti) { t.Fatal("req doesn't match tsti") } tsti.Nonce.SetInt64(123) if req.Matches(tsti) { t.Fatal("req matches tsti") } tsti.Nonce.Set(req.Nonce) tsti.MessageImprint, _ = NewMessageImprint(crypto.SHA256, bytes.NewReader([]byte("asdf"))) if req.Matches(tsti) { t.Fatal("req matches tsti") } } func TestGenerateNonce(t *testing.T) { nonce := GenerateNonce() if nonce == nil { t.Fatal("expected non-nil nonce") } // don't check for exact bitlength match, since leading 0's don't count // towards length. if nonce.BitLen() < nonceBytes*8/2 { t.Fatalf("expected %d bit nonce, got %d", nonceBytes*8, nonce.BitLen()) } if nonce.Cmp(new(big.Int)) == 0 { t.Fatal("expected non-zero nonce") } } func TestMessageImprint(t *testing.T) { m := []byte("hello, world!") mi1, err := NewMessageImprint(crypto.SHA256, bytes.NewReader(m)) if err != nil { panic(err) } // same mi2, err := NewMessageImprint(crypto.SHA256, bytes.NewReader(m)) if err != nil { panic(err) } if !mi1.Equal(mi2) { t.Fatal("expected m1==m2") } // round trip der, err := asn1.Marshal(mi1) if err != nil { t.Fatal(err) } if _, err = asn1.Unmarshal(der, &mi2); err != nil { t.Fatal(err) } if !mi1.Equal(mi2) { t.Fatal("expected m1==m2") } // null value for hash alrogithm parameters (as opposed to being absent entirely) mi2, _ = NewMessageImprint(crypto.SHA256, bytes.NewReader(m)) mi2.HashAlgorithm.Parameters = asn1.NullRawValue if !mi1.Equal(mi2) { t.Fatal("expected m1==m2") } // different digest mi2, err = NewMessageImprint(crypto.SHA1, bytes.NewReader(m)) if err != nil { panic(err) } if mi1.Equal(mi2) { t.Fatal("expected m1!=m2") } // different message mi2, err = NewMessageImprint(crypto.SHA256, bytes.NewReader([]byte("wrong"))) if err != nil { panic(err) } if mi1.Equal(mi2) { t.Fatal("expected m1!=m2") } // bad digest mi2, _ = NewMessageImprint(crypto.SHA256, bytes.NewReader(m)) mi2.HashedMessage = mi2.HashedMessage[0 : len(mi2.HashedMessage)-1] if mi1.Equal(mi2) { t.Fatal("expected m1!=m2") } } func TestErrorResponse(t *testing.T) { // Error response from request with missing message digest. respDER, _ := protocol.BER2DER(mustBase64Decode("MDQwMgIBAjApDCd0aGUgZGF0YSBzdWJtaXR0ZWQgaGFzIHRoZSB3cm9uZyBmb3JtYXQDAgIE")) resp, err := ParseResponse(respDER) if err != nil { t.Fatal(err) } rt, err := asn1.Marshal(resp) if err != nil { t.Fatal(err) } if !bytes.Equal(respDER, rt) { t.Fatal("expected round-tripping error TimeStampResp to equal") } expectedStatus := 2 if resp.Status.Status != expectedStatus { t.Fatalf("expected status %d, got %d", expectedStatus, resp.Status.Status) } if numStrings := len(resp.Status.StatusString); numStrings != 1 { t.Fatalf("expected single status string, got %d", numStrings) } expectedString := "the data submitted has the wrong format" actualStrings, err := resp.Status.StatusString.Strings() if err != nil { t.Fatal(err) } if actualStrings[0] != expectedString { t.Fatalf("expected status string %s, got %s", expectedString, actualStrings[0]) } expectedFailInfoLen := 6 if resp.Status.FailInfo.BitLength != expectedFailInfoLen { t.Fatalf("expected len(failinfo) %d, got %d", expectedFailInfoLen, resp.Status.FailInfo.BitLength) } expectedFailInfo := []int{0, 0, 0, 0, 0, 1} for i, v := range expectedFailInfo { if actual := resp.Status.FailInfo.At(i); actual != v { t.Fatalf("expected failinfo[%d] to be %d, got %d", i, v, actual) } } } func TestPKIFreeText(t *testing.T) { der := mustBase64Decode("MBUME0JhZCBtZXNzYWdlIGRpZ2VzdC4=") var ft PKIFreeText if _, err := asn1.Unmarshal(der, &ft); err != nil { t.Fatal(err) } rt, err := asn1.Marshal(ft) if err != nil { t.Fatal(err) } if !bytes.Equal(der, rt) { t.Fatal("expected round-tripped PKIFreeText to match") } ft = PKIFreeText{}.Append("Bad message digest.") if err != nil { t.Fatal(err) } rt, err = asn1.Marshal(ft) if err != nil { t.Fatal(err) } if !bytes.Equal(der, rt) { t.Fatal("expected newly made PKIFreeText to match original DER") } } func TestTSTInfo(t *testing.T) { resp, err := ParseResponse(fixtureTimestampSymantecWithCerts) if err != nil { t.Fatal(err) } sd, err := resp.TimeStampToken.SignedDataContent() if err != nil { t.Fatal(err) } inf, err := ParseInfo(sd.EncapContentInfo) if err != nil { t.Fatal(err) } expectedVersion := 1 if inf.Version != expectedVersion { t.Fatalf("expected version %d, got %d", expectedVersion, inf.Version) } expectedPolicy := asn1.ObjectIdentifier{2, 16, 840, 1, 113733, 1, 7, 23, 3} if !inf.Policy.Equal(expectedPolicy) { t.Fatalf("expected policy %s, got %s", expectedPolicy.String(), inf.Policy.String()) } expectedHash := crypto.SHA256 if hash, err := inf.MessageImprint.Hash(); err != nil { t.Fatal(err) } else if hash != expectedHash { t.Fatalf("expected hash %d, got %d", expectedHash, hash) } expectedMI, _ := NewMessageImprint(crypto.SHA256, bytes.NewReader([]byte("hello\n"))) if !inf.MessageImprint.Equal(expectedMI) { t.Fatalf("expected hash %s, got %s", hex.EncodeToString(expectedMI.HashedMessage), hex.EncodeToString(inf.MessageImprint.HashedMessage)) } expectedSN := new(big.Int).SetBytes([]byte{0x34, 0x99, 0xB7, 0x2E, 0xCE, 0x6F, 0xB6, 0x6B, 0x68, 0x2D, 0x35, 0x25, 0xC6, 0xE5, 0x6A, 0x07, 0x77, 0x3D, 0xC9, 0xD8}) if inf.SerialNumber.Cmp(expectedSN) != 0 { t.Fatalf("expected SN %s, got %s", expectedSN.String(), inf.SerialNumber.String()) } timeFmt := "2006-01-02 15:04:05 MST" expectedGenTime, _ := time.Parse(timeFmt, "2018-05-09 18:25:22 UTC") if !inf.GenTime.Equal(expectedGenTime) { t.Fatalf("expected gentime %s, got %s", expectedGenTime.String(), inf.GenTime.String()) } expectedAccuracy := 30 * time.Second if accuracy := inf.Accuracy.Duration(); accuracy != expectedAccuracy { t.Fatalf("expected accurracy %s, got %s", expectedAccuracy.String(), accuracy.String()) } expectedGenTimeMax := expectedGenTime.Add(expectedAccuracy) if inf.genTimeMax() != expectedGenTimeMax { t.Fatalf("expected gentimemax %s, got %s", expectedGenTimeMax.String(), inf.genTimeMax().String()) } expectedGenTimeMin := expectedGenTime.Add(-expectedAccuracy) if inf.genTimeMin() != expectedGenTimeMin { t.Fatalf("expected gentimemax %s, got %s", expectedGenTimeMin.String(), inf.genTimeMin().String()) } expectedOrdering := false if inf.Ordering != expectedOrdering { t.Fatalf("expected ordering %t, got %t", expectedOrdering, inf.Ordering) } if inf.Nonce != nil { t.Fatal("expected nil nonce") } // don't bother with TSA, since we don't want to mess with parsing GeneralNames. if inf.Extensions != nil { t.Fatal("expected nil extensions") } } func TestParseTimestampSymantec(t *testing.T) { testParseInfo(t, fixtureTimestampSymantec) } func TestParseTimestampSymantecWithCerts(t *testing.T) { testParseInfo(t, fixtureTimestampSymantecWithCerts) } func TestParseTimestampDigicert(t *testing.T) { testParseInfo(t, fixtureTimestampDigicert) } func TestParseTimestampComodo(t *testing.T) { testParseInfo(t, fixtureTimestampComodo) } func TestParseTimestampGlobalSign(t *testing.T) { testParseInfo(t, fixtureTimestampGlobalSign) } func testParseInfo(t *testing.T, ber []byte) { resp, err := ParseResponse(ber) if err != nil { t.Fatal(err) } if err = resp.Status.GetError(); err != nil { t.Fatal(err) } sd, err := resp.TimeStampToken.SignedDataContent() if err != nil { t.Fatal(err) } certs, err := sd.X509Certificates() if err != nil { t.Fatal(err) } inf, err := ParseInfo(sd.EncapContentInfo) if err != nil { t.Fatal(err) } hash, err := inf.MessageImprint.Hash() if err != nil { t.Fatal(err) } if hash != crypto.SHA256 { t.Fatalf("expected SHA256 hash, found %s", inf.MessageImprint.HashAlgorithm.Algorithm.String()) } if inf.SerialNumber == nil { t.Fatal("expected non-nill SN") } if inf.SerialNumber.Cmp(big.NewInt(0)) <= 0 { t.Fatal("expected SN>0") } if inf.Version != 1 { t.Fatalf("expected tst v1, found %d", inf.Version) } for _, si := range sd.SignerInfos { if _, err = si.FindCertificate(certs); err != nil && len(certs) > 0 { t.Fatal(err) } if ct, errr := si.GetContentTypeAttribute(); errr != nil { t.Fatal(errr) } else { // signerInfo contentType attribute must match signedData // encapsulatedContentInfo content type. if !ct.Equal(sd.EncapContentInfo.EContentType) { t.Fatalf("expected %s content, got %s", sd.EncapContentInfo.EContentType.String(), ct.String()) } } if md, errr := si.GetMessageDigestAttribute(); errr != nil { t.Fatal(errr) } else if len(md) == 0 { t.Fatal("nil/empty message digest attribute") } if algo := si.X509SignatureAlgorithm(); algo == x509.UnknownSignatureAlgorithm { t.Fatalf("unknown signature algorithm") } var nilTime time.Time if st, errr := si.GetSigningTimeAttribute(); errr != nil { t.Fatal(errr) } else if st == nilTime { t.Fatal("0 value signing time") } } // round trip resp der, err := protocol.BER2DER(ber) if err != nil { t.Fatal(err) } der2, err := asn1.Marshal(resp) if err != nil { t.Fatal(err) } if !bytes.Equal(der, der2) { t.Fatal("re-encoded contentInfo doesn't match original") } // round trip signedData der = resp.TimeStampToken.Content.Bytes der2, err = asn1.Marshal(*sd) if err != nil { t.Fatal(err) } if !bytes.Equal(der, der2) { t.Fatal("re-encoded signedData doesn't match original") } } var fixtureTimestampSymantec = mustBase64Decode("" + "MIIDnjADAgEAMIIDlQYJKoZIhvcNAQcCoIIDhjCCA4ICAQMxDTALBglghkgBZQMEAgEwggEOBgsqhkiG" + "9w0BCRABBKCB/gSB+zCB+AIBAQYLYIZIAYb4RQEHFwMwMTANBglghkgBZQMEAgEFAAQgWJG1tSLV3wht" + "D/CxEPvZ0hu0/HFjrzTQgoai6Eb2vgMCFHERZNISITpb8tPCqDQtcNGcWhhSGA8yMDE4MDUwOTE0NTQy" + "MlowAwIBHqCBhqSBgzCBgDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9u" + "MR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMTEwLwYDVQQDEyhTeW1hbnRlYyBTSEEyNTYg" + "VGltZVN0YW1waW5nIFNpZ25lciAtIEcyMYICWjCCAlYCAQEwgYswdzELMAkGA1UEBhMCVVMxHTAbBgNV" + "BAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSgw" + "JgYDVQQDEx9TeW1hbnRlYyBTSEEyNTYgVGltZVN0YW1waW5nIENBAhBUWPKq10HWRLyEqXugllLmMAsG" + "CWCGSAFlAwQCAaCBpDAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTE4" + "MDUwOTE0NTQyMlowLwYJKoZIhvcNAQkEMSIEIF/3JTU7CB+pzL3Mf+8BKgIRZQlDbovL5WzNhyeTSCn6" + "MDcGCyqGSIb3DQEJEAIvMSgwJjAkMCIEIM96wXrQR+zV/cNoIgMbEtTvB4tvK0xea6Qfj/LPS61nMAsG" + "CSqGSIb3DQEBAQSCAQCRxSB9MLAzK4YnNoFqIK9i71b011Q4pcyF6FEffC3ihOHjdmaHf/rFCeuv4roh" + "yGxW9cRTshE8UohcghMEuSbkSyaFtVt37o31NC1IvW0vouJVQ0j0rg6nQjlsO9rMGW7cJOS2lVnREqk5" + "+WfBMKJVnuYSXrnUdxcjSG++4eBCEF5L1fdCVjm4s1hagEORimvUoKuStibW0lwE8rdOEBjusZjRPDV6" + "hudDhI+2SJPCAFhnNaDDT73y+Ux4x5cVdxHV+tME8kUrr6Hm/l6EyPxu/jwrV/EdJFVsJfkemdJz/ACa" + "EbbTXfP8KuOwEyUwbFbRCXqO+Z6Gg0RqpiAZWCSM", ) var fixtureTimestampSymantecWithCerts = mustBase64Decode("" + "MIIOLTADAgEAMIIOJAYJKoZIhvcNAQcCoIIOFTCCDhECAQMxDTALBglghkgBZQMEAgEwggEOBgsqhkiG" + "9w0BCRABBKCB/gSB+zCB+AIBAQYLYIZIAYb4RQEHFwMwMTANBglghkgBZQMEAgEFAAQgWJG1tSLV3wht" + "D/CxEPvZ0hu0/HFjrzTQgoai6Eb2vgMCFDSZty7Ob7ZraC01Jcblagd3PcnYGA8yMDE4MDUwOTE4MjUy" + "MlowAwIBHqCBhqSBgzCBgDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9u" + "MR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMTEwLwYDVQQDEyhTeW1hbnRlYyBTSEEyNTYg" + "VGltZVN0YW1waW5nIFNpZ25lciAtIEczoIIKizCCBTgwggQgoAMCAQICEHsFsdRJaFFE98mJ0pwZnRIw" + "DQYJKoZIhvcNAQELBQAwgb0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0G" + "A1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDIwMDggVmVyaVNpZ24sIElu" + "Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE4MDYGA1UEAxMvVmVyaVNpZ24gVW5pdmVyc2FsIFJv" + "b3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTYwMTEyMDAwMDAwWhcNMzEwMTExMjM1OTU5WjB3" + "MQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFu" + "dGVjIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMTH1N5bWFudGVjIFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0Ew" + "ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7WZ1ZVU+djHJdGoGi61XzsAGtPHGsMo8Fa4aa" + "JwAyl2pNyWQUSym7wtkpuS7sY7Phzz8LVpD4Yht+66YH4t5/Xm1AONSRBudBfHkcy8utG7/YlZHz8O5s" + "+K2WOS5/wSe4eDnFhKXt7a+Hjs6Nx23q0pi1Oh8eOZ3D9Jqo9IThxNF8ccYGKbQ/5IMNJsN7CD5N+Qq3" + "M0n/yjvU9bKbS+GImRr1wOkzFNbfx4Dbke7+vJJXcnf0zajM/gn1kze+lYhqxdz0sUvUzugJkV+1hHk1" + "inisGTKPI8EyQRtZDqk+scz51ivvt9jk1R1tETqS9pPJnONI7rtTDtQ2l4Z4xaE3AgMBAAGjggF3MIIB" + "czAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADBmBgNVHSAEXzBdMFsGC2CGSAGG+EUB" + "BxcDMEwwIwYIKwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3BzMCUGCCsGAQUFBwICMBkaF2h0" + "dHBzOi8vZC5zeW1jYi5jb20vcnBhMC4GCCsGAQUFBwEBBCIwIDAeBggrBgEFBQcwAYYSaHR0cDovL3Mu" + "c3ltY2QuY29tMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9zLnN5bWNiLmNvbS91bml2ZXJzYWwtcm9v" + "dC5jcmwwEwYDVR0lBAwwCgYIKwYBBQUHAwgwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFt" + "cC0yMDQ4LTMwHQYDVR0OBBYEFK9j1sqjToVy4Ke8QfMpojh/gHViMB8GA1UdIwQYMBaAFLZ3+mlIR59T" + "EtXC6gcydgfRlwcZMA0GCSqGSIb3DQEBCwUAA4IBAQB16rAt1TQZXDJF/g7h1E+meMFv1+rd3E/zociB" + "iPenjxXmQCmt5l30otlWZIRxMCrdHmEXZiBWBpgZjV1x8viXvAn9HJFHyeLojQP7zJAv1gpsTjPs1rST" + "yEyQY0g5QCHE3dZuiZg8tZiX6KkGtwnJj1NXQZAv4R5NTtzKEHhsQm7wtsX4YVxS9U72a433Snq+8839" + "A9fZ9gOoD+NT9wp17MZ1LqpmhQSZt/gGV+HGDvbor9rsmxgfqrnjOgC/zoqUywHbnsc4uw9Sq9HjlANg" + "Ck2g/idtFDL8P5dA4b+ZidvkORS92uTTw+orWrOVWFUEfcea7CMDjYUq0v+uqWGBMIIFSzCCBDOgAwIB" + "AgIQe9Tlr7rMBz+hASMEIkFNEjANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJVUzEdMBsGA1UEChMU" + "U3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxKDAmBgNV" + "BAMTH1N5bWFudGVjIFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMTcxMjIzMDAwMDAwWhcNMjkwMzIy" + "MjM1OTU5WjCBgDELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYD" + "VQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMTEwLwYDVQQDEyhTeW1hbnRlYyBTSEEyNTYgVGltZVN0" + "YW1waW5nIFNpZ25lciAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArw6Kqvjcv2l7" + "VBdxRwm9jTyB+HQVd2eQnP3eTgKeS3b25TY+ZdUkIG0w+d0dg+k/J0ozTm0WiuSNQI0iqr6nCxvSB7Y8" + "tRokKPgbclE9yAmIJgg6+fpDI3VHcAyzX1uPCB1ySFdlTa8CPED39N0yOJM/5Sym81kjy4DeE035EMmq" + "ChhsVWFX0fECLMS1q/JsI9KfDQ8ZbK2FYmn9ToXBilIxq1vYyXRS41dsIr9Vf2/KBqs/SrcidmXs7Dby" + "lpWBJiz9u5iqATjTryVAmwlT8ClXhVhe6oVIQSGH5d600yaye0BTWHmOUjEGTZQDRcTOPAPstwDyOiLF" + "tG/l77CKmwIDAQABo4IBxzCCAcMwDAYDVR0TAQH/BAIwADBmBgNVHSAEXzBdMFsGC2CGSAGG+EUBBxcD" + "MEwwIwYIKwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3BzMCUGCCsGAQUFBwICMBkaF2h0dHBz" + "Oi8vZC5zeW1jYi5jb20vcnBhMEAGA1UdHwQ5MDcwNaAzoDGGL2h0dHA6Ly90cy1jcmwud3Muc3ltYW50" + "ZWMuY29tL3NoYTI1Ni10c3MtY2EuY3JsMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQE" + "AwIHgDB3BggrBgEFBQcBAQRrMGkwKgYIKwYBBQUHMAGGHmh0dHA6Ly90cy1vY3NwLndzLnN5bWFudGVj" + "LmNvbTA7BggrBgEFBQcwAoYvaHR0cDovL3RzLWFpYS53cy5zeW1hbnRlYy5jb20vc2hhMjU2LXRzcy1j" + "YS5jZXIwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0yMDQ4LTYwHQYDVR0OBBYEFKUT" + "AamfhcwbbhYeXzsxqnk2AHsdMB8GA1UdIwQYMBaAFK9j1sqjToVy4Ke8QfMpojh/gHViMA0GCSqGSIb3" + "DQEBCwUAA4IBAQBGnq/wuKJfoplIz6gnSyHNsrmmcnBjL+NVKXs5Rk7nfmUGWIu8V4qSDQjYELo2JPoK" + "e/s702K/SpQV5oLbilRt/yj+Z89xP+YzCdmiWRD0Hkr+Zcze1GvjUil1AEorpczLm+ipTfe0F1mSQcO3" + "P4bm9sB/RDxGXBda46Q71Wkm1SF94YBnfmKst04uFZrlnCOvWxHqcalB+Q15OKmhDc+0sdo+mnrHIsV0" + "zd9HCYbE/JElshuW6YUI6N3qdGBuYKVWeg3IRFjc5vlIFJ7lv94AvXexmBRyFCTfxxEsHwA/w0sUxmcc" + "zB4Go5BfXFSLPuMzW4IPxbeGAk5xn+lmRT92MYICWjCCAlYCAQEwgYswdzELMAkGA1UEBhMCVVMxHTAb" + "BgNVBAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3Jr" + "MSgwJgYDVQQDEx9TeW1hbnRlYyBTSEEyNTYgVGltZVN0YW1waW5nIENBAhB71OWvuswHP6EBIwQiQU0S" + "MAsGCWCGSAFlAwQCAaCBpDAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8X" + "DTE4MDUwOTE4MjUyMlowLwYJKoZIhvcNAQkEMSIEIF5EOTCml8PvDOxSGeQnbCv+jXprtZlEut7wcOx/" + "xjfvMDcGCyqGSIb3DQEJEAIvMSgwJjAkMCIEIMR0znYAfQI5Tg2l5N58FMaA+eKCATz+9lPvXbcf32H4" + "MAsGCSqGSIb3DQEBAQSCAQBD1SGuMSSNtmwg38x/1d8v+uvX/2aPIJQS//p5Q54Y8moIEeezRhG0tK3N" + "81tfKdLeYTVE6VL8D7ZaCpbKzNJeD6DQM4S87bzH88H5RQOb2JTCvBPF3C/ytcl7ylezx6xsFNtftbW3" + "IOXETaWLgIBpeL7jUZQDhgQ4Xb9HeFl4vA6Wk2kR88h+8Tv2ci0AI9hZgHhH9c/OwPvd8TKbhSjK9qXK" + "DjaJr0BeVuYHPSWxfsxWVCOjNIOg7moWpPLSYQpqM2gdg5ppjQWffWYC4rywmM6XsBKs+EKFb++4GSOc" + "wc6JJCugxm8Ba1a6nDAAAQYf/pQyBRRlh/qCHZ0rIoFq", ) var fixtureTimestampDigicert = mustBase64Decode("" + "MIIOuTADAgEAMIIOsAYJKoZIhvcNAQcCoIIOoTCCDp0CAQMxDzANBglghkgBZQMEAgEFADB3BgsqhkiG" + "9w0BCRABBKBoBGYwZAIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIFiRtbUi1d8IbQ/wsRD7" + "2dIbtPxxY6800IKGouhG9r4DAhAvZIfDsFuq0GRqVn9Wu2I8GA8yMDE4MDUwOTE4NDgxOFqgggu7MIIF" + "MTCCBBmgAwIBAgIQCqEl1tYyG35B5AXaNpfCFTANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEV" + "MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtE" + "aWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTYwMTA3MTIwMDAwWhcNMzEwMTA3MTIwMDAwWjBy" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu" + "Y29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBMIIBIjAN" + "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvdAy7kvNj3/dqbqCmcU5VChXtiNKxA4HRTNREH3Q+X1N" + "aH7ntqD0jbOI5Je/YyGQmL8TvFfTw+F+CNZqFAA49y4eO+7MpvYyWf5fZT/gm+vjRkcGGlV+Cyd+wKL1" + "oODeIj8O/36V+/OjuiI+GKwR5PCZA207hXwJ0+5dyJoLVOOoCXFr4M8iEA91z3FyTgqt30A6XLdR4aF5" + "FMZNJCMwXbzsPGBqrC8HzP3w6kfZiFBe/WZuVmEnKYmEUeaC50ZQ/ZQqLKfkdT66mA+Ef58xFNat1fJk" + "y3seBdCEGXIX8RcG7z3N1k3vBkL9olMqT4UdxB08r8/arBD13ays6Vb/kwIDAQABo4IBzjCCAcowHQYD" + "VR0OBBYEFPS24SAd/imu0uRhpbKiJbLIFzVuMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgP" + "MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHkG" + "CCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF" + "BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0" + "MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk" + "SURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk" + "SURSb290Q0EuY3JsMFAGA1UdIARJMEcwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczov" + "L3d3dy5kaWdpY2VydC5jb20vQ1BTMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAQEAcZUS6VGH" + "VmnN793afKpjerN4zwY3QITvS4S/ys8DAv3Fp8MOIEIsr3fzKx8MIVoqtwU0HWqumfgnoma/Capg33ak" + "OpMP+LLR2HwZYuhegiUexLoceywh4tZbLBQ1QwRostt1AuByx5jWPGTlH0gQGF+JOGFNYkYkh2OMkVIs" + "rymJ5Xgf1gsUpYDXEkdws3XVk4WTfraSZ/tTYYmo9WuWwPRYaQ18yAGxuSh1t5ljhSKMYcp5lH5Z/IwP" + "42+1ASa2bKXuh1Eh5Fhgm7oMLSttosR+u8QlK0cCCHxJrhO24XxCQijGGFbPQTS2Zl22dHv1VjMiLyI2" + "skuiSpXY9aaOUjCCBoIwggVqoAMCAQICEAnA/EbIBEITtVmLryhPTkEwDQYJKoZIhvcNAQELBQAwcjEL" + "MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv" + "bTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTAeFw0xNzAx" + "MDQwMDAwMDBaFw0yODAxMTgwMDAwMDBaMEwxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEq" + "MCgGA1UEAxMhRGlnaUNlcnQgU0hBMiBUaW1lc3RhbXAgUmVzcG9uZGVyMIIBIjANBgkqhkiG9w0BAQEF" + "AAOCAQ8AMIIBCgKCAQEAnpWYajQ7cxuofvzHvilpicdoJkZfPY1ic4eBo6Gc8LdbJDdaktT0Wdd2ieTc" + "1Sfw1Wa8Cu60KzFnrFjFSpFZK0UeCQHWZLNZ7o1mTfsjXswQDQuKZ+9SrqAIkMJS9/WotW6bLHud57U+" + "+3jNMlAYv0C1TIy7V/SgTxFFbEJCueWv1t/0p3wKaJYP0l8pV877HTL/9BGhEyL7Esvv11PS65fLoqwb" + "HZ1YIVGCwsLe6is/LCKE0EPsOzs/R8T2VtxFN5i0a3S1Wa94V2nIDwkCeN3YU8GZ22DEnequr+B+hkpc" + "qVhhqF50igEoaHJOp4adtQJSh3BmSNOO74EkzNzYZQIDAQABo4IDODCCAzQwDgYDVR0PAQH/BAQDAgeA" + "MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwggG/BgNVHSAEggG2MIIBsjCCAaEG" + "CWCGSAGG/WwHATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwggFk" + "BggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABp" + "AGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAg" + "AG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABl" + "ACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBo" + "ACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwBy" + "AHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJ" + "YIZIAYb9bAMVMB8GA1UdIwQYMBaAFPS24SAd/imu0uRhpbKiJbLIFzVuMB0GA1UdDgQWBBThpzJK7gEh" + "KH1U1fIHkm60Bw89hzBxBgNVHR8EajBoMDKgMKAuhixodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hh" + "Mi1hc3N1cmVkLXRzLmNybDAyoDCgLoYsaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJl" + "ZC10cy5jcmwwgYUGCCsGAQUFBwEBBHkwdzAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu" + "Y29tME8GCCsGAQUFBzAChkNodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNz" + "dXJlZElEVGltZXN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4IBAQAe8EGCMq7t8bQ1E9xQwtWX" + "riIinQ4OrzPTTP18v28BEaeUZSJcxiKhyIlSa5qMc1zZXj8y3hZgTIs2/TGZCr3BhLeNHe+JJhMFVvNH" + "zUdbrYSyOK9qI7VF4x6IMkaA0remmSL9wXjP9YvYDIwFCe5E5oDVbXDMn1MeJ90qSN7ak2WtbmWjmafC" + "QA5zzFhPj0Uo5byciOYozmBdLSVdi3MupQ1bUdqaTv9QBYko2vJ4u9JYeI1Ep6w6AJF4aYlkBNNdlt8q" + "v/mlTCyT/+aK3YKs8dKzooaawVWJVmpHP/rWM5VDNYkFeFo6adoiuARD029oNTZ6FD5F6Zhkhg8TDCZK" + "MYICTTCCAkkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE" + "CxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVz" + "dGFtcGluZyBDQQIQCcD8RsgEQhO1WYuvKE9OQTANBglghkgBZQMEAgEFAKCBmDAaBgkqhkiG9w0BCQMx" + "DQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTE4MDUwOTE4NDgxOFowLwYJKoZIhvcNAQkEMSIE" + "IDpdtczqob9pSfKx5ZEHQZSSHM3P+8uGHy1rXmrK9iUjMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFEAB" + "kUdcmIkd66EEr0cJG1621MvLMA0GCSqGSIb3DQEBAQUABIIBAIlFY+12XT6zvj4/0LVL5//VunTmYTKg" + "Z6eSrafFT9zOvGbDzm/8XnDLrUQq9Y4kQpE+eKfHWJOBQQZ0ze0wftUml+iRsvqEVlax7G03SzHyPIYH" + "HzEH/IKRlryHR5LgzzeFqS6IdVg18FBLvrs2fvPJlsj0ZGmAbwn6ntHDromtnkwZV6Cir5gH+wSKuA+Z" + "3Qj5odgrTQ9gmbmNlFgwp4BwH/vFbBB1eIt7EUD1KfZzThfdFYHnyl8eRcE5p5+MxvyAC78fPzlSlJJP" + "OES5LDDTx/Qvhet0PjJv70Z7kKgMmAA0BMTRuTnGfiVfEoFm2bzoKmwprU38EPz+PVnrbUA=", ) var fixtureTimestampComodo = mustBase64Decode("" + "MIIDuDADAgEAMIIDrwYJKoZIhvcNAQcCoIIDoDCCA5wCAQMxDzANBglghkgBZQMEAgEFADCCAQ8GCyqG" + "SIb3DQEJEAEEoIH/BIH8MIH5AgEBBgorBgEEAbIxAgEBMDEwDQYJYIZIAWUDBAIBBQAEIFiRtbUi1d8I" + "bQ/wsRD72dIbtPxxY6800IKGouhG9r4DAhUA4Fc3zQPRFgrg3c8/sksclhBco7QYDzIwMTgwNTA5MTg0" + "NzQyWqCBjKSBiTCBhjELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G" + "A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxLDAqBgNVBAMTI0NPTU9ETyBT" + "SEEtMjU2IFRpbWUgU3RhbXBpbmcgU2lnbmVyMYICcTCCAm0CAQEwgaowgZUxCzAJBgNVBAYTAlVTMQsw" + "CQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1Qg" + "TmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNF" + "UkZpcnN0LU9iamVjdAIQTrCHj8wkNTay2Mn3vzlVdzANBglghkgBZQMEAgEFAKCBmDAaBgkqhkiG9w0B" + "CQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTE4MDUwOTE4NDc0MlowKwYLKoZIhvcNAQkQ" + "AgwxHDAaMBgwFgQUNlJ9T6JqaPnrRZbx2Zq7LA6nbfowLwYJKoZIhvcNAQkEMSIEIJeVWgArDRySkAZc" + "F6na8PZrsUBoQs2jUzy94iOFYfM6MA0GCSqGSIb3DQEBAQUABIIBAKKV56NeTuFn4VdoNv15X0bUWG3p" + "JSMRVbp1CWktnraj7E5m3BUmFlb4Dwrf3IMmE4QJrGrzDUWtUmpnHR4VuGAUmyajEcmDICc2gpBBG+aV" + "0Ng/lXQ1xAotKkU7/4wNQY1nOBsquZykYRHWbzJaVxaq8VEc0nVZY2o1TVDgWtLF7BHAd96vw4iVuG3O" + "Pb8izdFMwQ0t/TMNq0FD0hEFQDSTvVkayeaficblGbhf/p1xuCxSMoFBmnfO56aRX01E3SDNAgo3/hFl" + "na2g8ESpdWHRMqG3+8ehvgMwljUnhj5+iYT1YF7Rm6KcV2TCIh6QyokN42ji4BMqTlBA7vzSx5A=", ) var fixtureTimestampGlobalSign = mustBase64Decode("" + "MIIDoTADAgEAMIIDmAYJKoZIhvcNAQcCoIIDiTCCA4UCAQMxCzAJBgUrDgMCGgUAMIHdBgsqhkiG9w0B" + "CRABBKCBzQSByjCBxwIBAQYJKwYBBAGgMgICMDEwDQYJYIZIAWUDBAIBBQAEIFiRtbUi1d8IbQ/wsRD7" + "2dIbtPxxY6800IKGouhG9r4DAhRYZmxGjSg8ojY0mWZG3dUdVW0mAxgPMjAxODA1MDkxODQ2MjRaoF2k" + "WzBZMQswCQYDVQQGEwJTRzEfMB0GA1UEChMWR01PIEdsb2JhbFNpZ24gUHRlIEx0ZDEpMCcGA1UEAxMg" + "R2xvYmFsU2lnbiBUU0EgZm9yIFN0YW5kYXJkIC0gRzIxggKRMIICjQIBATBoMFIxCzAJBgNVBAYTAkJF" + "MRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSgwJgYDVQQDEx9HbG9iYWxTaWduIFRpbWVzdGFtcGlu" + "ZyBDQSAtIEcyAhIRIbRVNR67GrJPl+8H/iqzC4owCQYFKw4DAhoFAKCB/zAaBgkqhkiG9w0BCQMxDQYL" + "KoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTE4MDUwOTE4NDYyNFowIwYJKoZIhvcNAQkEMRYEFOmL" + "BqSyLEaL7tN+hDwnk6fha6wfMIGdBgsqhkiG9w0BCRACDDGBjTCBijCBhzCBhAQUg/3hunb+9VKRtQ1o" + "YZBtqkW1jLUwbDBWpFQwUjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExKDAm" + "BgNVBAMTH0dsb2JhbFNpZ24gVGltZXN0YW1waW5nIENBIC0gRzICEhEhtFU1Hrsask+X7wf+KrMLijAN" + "BgkqhkiG9w0BAQEFAASCAQBhWhjTagaTyATim1IHw0tF0wb22rlj6qXki86lclB/2uxBC8/3uLVd259z" + "iz7aaTmxSj3ksMBq9A75beQW5Be9vK00B/mj/p1dLrtgCcYZtV4uhoBkBx0YbriumEnvQoQL1bI1EiXh" + "TDbdTrGs2wXn3Xzw/qwqc7w+IjW1BjqzLf6BB9jw2raxMuWBA3EGMwGTumRx5x6a7j2Jx/9Uhs+3ce+9" + "ZRDtiWAFCkTQVvNLrAuHLTFK6lLOqfucrru76adpJMlTJ+VRut0adpwviS1Cb2ifIX1iUHjtGssihk6v" + "/tt7Yo4J341G5pC4JDXXhJvxHImNew3l0BWM0LROEgLM", ) func mustBase64Decode(b64 string) []byte { decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(b64)) buf := new(bytes.Buffer) if _, err := io.Copy(buf, decoder); err != nil { panic(err) } return buf.Bytes() } // The fixtures above are complete TimeStampResp's, but most of our tests only // care about the TimeStampToken (CMS ContentInfo) part of it. func mustExtractTimeStampToken(ber []byte) []byte { resp, err := ParseResponse(ber) if err != nil { panic(err) } tstDER, err := asn1.Marshal(resp.TimeStampToken) if err != nil { panic(err) } return tstDER } gitsign-0.13.0/internal/fork/ietf-cms/timestamp_test.go000066400000000000000000000144241477253552300231360ustar00rootroot00000000000000package cms import ( "crypto/rsa" "crypto/x509" "strings" "testing" "time" "github.com/github/smimesign/fakeca" "github.com/github/smimesign/ietf-cms/oid" "github.com/github/smimesign/ietf-cms/protocol" "github.com/sigstore/gitsign/internal/fork/ietf-cms/timestamp" ) func TestAddTimestamps(t *testing.T) { // Good response tsa.Clear() sd, _ := NewSignedData([]byte("hi")) sd.Sign(leaf.Chain(), leaf.PrivateKey) if err := sd.AddTimestamps("https://google.com"); err != nil { t.Fatal(err) } if _, err := sd.Verify(intermediateOpts, intermediateOpts); err != nil { t.Fatal(err) } if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err != nil { t.Fatal(err) } // Error status in response tsa.HookResponse(func(resp timestamp.Response) timestamp.Response { resp.Status.Status = 1 return resp }) sd, _ = NewSignedData([]byte("hi")) sd.Sign(leaf.Chain(), leaf.PrivateKey) if err := sd.AddTimestamps("https://google.com"); err != nil { if _, isStatusErr := err.(timestamp.PKIStatusInfo); !isStatusErr { t.Fatalf("expected timestamp.PKIStatusInfo error, got %v", err) } } // Bad nonce tsa.HookInfo(func(info timestamp.Info) timestamp.Info { info.Nonce.SetInt64(123123) return info }) sd, _ = NewSignedData([]byte("hi")) sd.Sign(leaf.Chain(), leaf.PrivateKey) if err := sd.AddTimestamps("https://google.com"); err == nil || err.Error() != "invalid message imprint" { t.Fatalf("expected 'invalid message imprint', got %v", err) } // Bad message imprint tsa.HookInfo(func(info timestamp.Info) timestamp.Info { info.MessageImprint.HashedMessage[0] ^= 0xFF return info }) sd, _ = NewSignedData([]byte("hi")) sd.Sign(leaf.Chain(), leaf.PrivateKey) if err := sd.AddTimestamps("https://google.com"); err == nil || err.Error() != "invalid message imprint" { t.Fatalf("expected 'invalid message imprint', got %v", err) } } func TestTimestampsVerifications(t *testing.T) { getTimestampedSignedData := func() *SignedData { sd, _ := NewSignedData([]byte("hi")) sd.Sign(leaf.Chain(), leaf.PrivateKey) tsReq, _ := tsRequest(sd.psd.SignerInfos[0]) tsResp, _ := tsa.Do(tsReq) tsAttr, _ := protocol.NewAttribute(oid.AttributeTimeStampToken, tsResp.TimeStampToken) sd.psd.SignerInfos[0].UnsignedAttrs = append(sd.psd.SignerInfos[0].UnsignedAttrs, tsAttr) return sd } // Good timestamp tsa.Clear() sd := getTimestampedSignedData() if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err != nil { t.Fatal(err) } if _, err := sd.Verify(intermediateOpts, intermediateOpts); err != nil { t.Fatal(err) } // Timestamped maybe before not-before // // Not-Before Not-After // |--------------------------------| // |--------| // sig-min sig-max tsa.HookInfo(func(info timestamp.Info) timestamp.Info { info.Accuracy.Seconds = 30 info.GenTime = leaf.Certificate.NotBefore return info }) sd = getTimestampedSignedData() if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err != nil { t.Fatal(err) } if _, err := sd.Verify(intermediateOpts, intermediateOpts); err == nil || !strings.HasPrefix(err.Error(), "x509: certificate has expired") { t.Fatalf("expected expired error, got %v", err) } // Timestamped after not-before // // Not-Before Not-After // |--------------------------------| // |--------| // sig-min sig-max tsa.HookInfo(func(info timestamp.Info) timestamp.Info { info.Accuracy.Seconds = 30 info.GenTime = leaf.Certificate.NotBefore.Add(31 * time.Second) return info }) sd = getTimestampedSignedData() if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err != nil { t.Fatal(err) } if _, err := sd.Verify(intermediateOpts, intermediateOpts); err != nil { t.Fatal(err) } // Timestamped maybe after not-after // // Not-Before Not-After // |--------------------------------| // |--------| // sig-min sig-max tsa.HookInfo(func(info timestamp.Info) timestamp.Info { info.Accuracy.Seconds = 30 info.GenTime = leaf.Certificate.NotAfter return info }) sd = getTimestampedSignedData() if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err != nil { t.Fatal(err) } if _, err := sd.Verify(intermediateOpts, intermediateOpts); err == nil || !strings.HasPrefix(err.Error(), "x509: certificate has expired") { t.Fatalf("expected expired error, got %v", err) } // Timestamped before not-after // // Not-Before Not-After // |--------------------------------| // |--------| // sig-min sig-max tsa.HookInfo(func(info timestamp.Info) timestamp.Info { info.Accuracy.Seconds = 30 info.GenTime = leaf.Certificate.NotAfter.Add(-31 * time.Second) return info }) sd = getTimestampedSignedData() if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err != nil { t.Fatal(err) } if _, err := sd.Verify(intermediateOpts, intermediateOpts); err != nil { t.Fatal(err) } // Bad message imprint tsa.HookInfo(func(info timestamp.Info) timestamp.Info { info.MessageImprint.HashedMessage[0] ^= 0xFF return info }) sd = getTimestampedSignedData() if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err == nil || err.Error() != "invalid message imprint" { t.Fatalf("expected 'invalid message imprint', got %v", err) } // Untrusted signature tsa.HookToken(func(tst *protocol.SignedData) *protocol.SignedData { badIdent := fakeca.New() tst.SignerInfos = nil tst.AddSignerInfo(badIdent.Chain(), badIdent.PrivateKey) return tst }) sd = getTimestampedSignedData() if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err == nil { t.Fatal("expected error") } else if _, ok := err.(x509.UnknownAuthorityError); !ok { t.Fatalf("expected x509.UnknownAuthorityError, got %v", err) } // Bad signature tsa.HookToken(func(tst *protocol.SignedData) *protocol.SignedData { tst.SignerInfos[0].Signature[0] ^= 0xFF return tst }) sd = getTimestampedSignedData() if _, err := getTimestamp(sd.psd.SignerInfos[0], intermediateOpts); err != rsa.ErrVerification { t.Fatalf("expected %v, got %v", rsa.ErrVerification, err) } } gitsign-0.13.0/internal/fork/ietf-cms/verify.go000066400000000000000000000145531477253552300214030ustar00rootroot00000000000000package cms import ( "bytes" "crypto/x509" "encoding/asn1" "errors" "github.com/github/smimesign/ietf-cms/protocol" "github.com/sigstore/sigstore/pkg/cryptoutils" ) // Verify verifies the SingerInfos' signatures. Each signature's associated // certificate is verified using the provided roots. UnsafeNoVerify may be // specified to skip this verification. Nil may be provided to use system roots. // The full chains for the certificates whose keys made the signatures are // returned. // // WARNING: this function doesn't do any revocation checking. func (sd *SignedData) Verify(opts x509.VerifyOptions, tsaOpts x509.VerifyOptions) ([][][]*x509.Certificate, error) { econtent, err := sd.psd.EncapContentInfo.EContentValue() if err != nil { return nil, err } if econtent == nil { return nil, errors.New("detached signature") } return sd.verify(econtent, opts, tsaOpts) } // VerifyDetached verifies the SingerInfos' detached signatures over the // provided data message. Each signature's associated certificate is verified // using the provided roots. UnsafeNoVerify may be specified to skip this // verification. Nil may be provided to use system roots. The full chains for // the certificates whose keys made the signatures are returned. // // WARNING: this function doesn't do any revocation checking. func (sd *SignedData) VerifyDetached(message []byte, opts x509.VerifyOptions, tsaOpts x509.VerifyOptions) ([][][]*x509.Certificate, error) { if sd.psd.EncapContentInfo.EContent.Bytes != nil { return nil, errors.New("signature not detached") } return sd.verify(message, opts, tsaOpts) } func (sd *SignedData) verify(econtent []byte, opts x509.VerifyOptions, tsOpts x509.VerifyOptions) ([][][]*x509.Certificate, error) { if len(sd.psd.SignerInfos) == 0 { return nil, protocol.ASN1Error{Message: "no signatures found"} } certs, err := sd.psd.X509Certificates() if err != nil { return nil, err } if opts.Intermediates == nil { opts.Intermediates = x509.NewCertPool() } for _, cert := range certs { opts.Intermediates.AddCert(cert) } chains := make([][][]*x509.Certificate, 0, len(sd.psd.SignerInfos)) for _, si := range sd.psd.SignerInfos { var signedMessage []byte // SignedAttrs is optional if EncapContentInfo eContentType isn't id-data. if si.SignedAttrs == nil { // SignedAttrs may only be absent if EncapContentInfo eContentType is // id-data. if !sd.psd.EncapContentInfo.IsTypeData() { return nil, protocol.ASN1Error{Message: "missing SignedAttrs"} } // If SignedAttrs is absent, the signature is over the original // encapsulated content itself. signedMessage = econtent } else { // If SignedAttrs is present, we validate the mandatory ContentType and // MessageDigest attributes. siContentType, err := si.GetContentTypeAttribute() if err != nil { return nil, err } if !siContentType.Equal(sd.psd.EncapContentInfo.EContentType) { return nil, protocol.ASN1Error{Message: "invalid SignerInfo ContentType attribute"} } // Calculate the digest over the actual message. hash, err := si.Hash() if err != nil { return nil, err } actualMessageDigest := hash.New() if _, err = actualMessageDigest.Write(econtent); err != nil { return nil, err } // Get the digest from the SignerInfo. messageDigestAttr, err := si.GetMessageDigestAttribute() if err != nil { return nil, err } // Make sure message digests match. if !bytes.Equal(messageDigestAttr, actualMessageDigest.Sum(nil)) { return nil, errors.New("invalid message digest") } // The signature is over the DER encoded signed attributes, minus the // leading class/tag/length bytes. This includes the digest of the // original message, so it is implicitly signed too. if signedMessage, err = si.SignedAttrs.MarshaledForVerification(); err != nil { return nil, err } } cert, err := si.FindCertificate(certs) if err != nil { return nil, err } // Handle certificates where the Subject Alternative Name is not set to // a supported GeneralName (RFC 5280 4.2.1.6). Go only supports DNS, IP // addresses, email addresses, or URIs as SANs. Fulcio can issue a // certificate with an OtherName GeneralName, so remove the unhandled // critical SAN extension before verifying. // This matches https://github.com/sigstore/cosign/blob/a0752eb40b500316ac417baf4926a2c2d99b39b8/pkg/cosign/verify.go#L236-L248 if len(cert.UnhandledCriticalExtensions) > 0 { var unhandledExts []asn1.ObjectIdentifier for _, oid := range cert.UnhandledCriticalExtensions { if !oid.Equal(cryptoutils.SANOID) { unhandledExts = append(unhandledExts, oid) } } cert.UnhandledCriticalExtensions = unhandledExts } algo := si.X509SignatureAlgorithm() if algo == x509.UnknownSignatureAlgorithm { return nil, protocol.ErrUnsupported } if err := cert.CheckSignature(algo, signedMessage, si.Signature); err != nil { return nil, err } // If the caller didn't specify the signature time, we'll use the verified // timestamp. If there's no timestamp we use the current time when checking // the cert validity window. This isn't perfect because the signature may // have been created before the cert's not-before date, but this is the best // we can do. We update a copy of opts because we are verifying multiple // signatures in a loop and only want the timestamp to affect this one. optsCopy := opts if hasTS, err := hasTimestamp(si); err != nil { return nil, err } else if hasTS { // Use provided verification options for timestamp verification also, but // explicitly ask for key-usage=timestamping. tsOpts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageTimeStamping} tsti, err := getTimestamp(si, tsOpts) if err != nil { return nil, err } // This check is slightly redundant, given that the cert validity times // are checked by cert.Verify. We take the timestamp accuracy into account // here though, whereas cert.Verify will not. if !tsti.Before(cert.NotAfter) || !tsti.After(cert.NotBefore) { return nil, x509.CertificateInvalidError{Cert: cert, Reason: x509.Expired, Detail: "timestamp authority verification failed"} } if optsCopy.CurrentTime.IsZero() { optsCopy.CurrentTime = tsti.GenTime } } if chain, err := cert.Verify(optsCopy); err != nil { return nil, err } else { chains = append(chains, chain) } } // OK return chains, nil } gitsign-0.13.0/internal/fork/ietf-cms/verify_test.go000066400000000000000000001224131477253552300224350ustar00rootroot00000000000000package cms import ( "bytes" "crypto/x509" "encoding/base64" "encoding/pem" "io" "strings" "testing" "github.com/github/smimesign/ietf-cms/protocol" "golang.org/x/xerrors" ) func ExampleSignedData() { data := []byte("hello, world!") // Wrap the data in a CMS SignedData structure and sign it with our key. signedDataDER, err := Sign(data, exampleChain, examplePrivateKey) if err != nil { panic(err) } // Re-parse the encoded SignedData structure. signedData, err := ParseSignedData(signedDataDER) if err != nil { panic(err) } // Verify the SignedData's signature. if _, err = signedData.Verify(x509.VerifyOptions{Roots: root.ChainPool()}, x509.VerifyOptions{}); err != nil { panic(err) } } func verifyOptionsForSignedData(sd *SignedData) (opts x509.VerifyOptions) { // add self-signed cert as trusted root certs, err := sd.psd.X509Certificates() if err != nil { panic(err) } if len(certs) == 1 { opts.Roots = x509.NewCertPool() opts.Roots.AddCert(certs[0]) } // trust signing time signingTime, err := sd.psd.SignerInfos[0].GetSigningTimeAttribute() if err != nil { panic(err) } opts.CurrentTime = signingTime // Any key usage opts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageAny} return } func TestVerify(t *testing.T) { sd, err := ParseSignedData(fixtureSignatureOne) if err != nil { t.Fatal(err) } opts := verifyOptionsForSignedData(sd) if _, err = sd.Verify(opts, opts); err != nil { t.Fatal(err) } } func TestVerifyGPGSMAttached(t *testing.T) { sd, err := ParseSignedData(fixtureSignatureGPGSMAttached) if err != nil { t.Fatal(err) } opts := verifyOptionsForSignedData(sd) if _, err = sd.Verify(opts, opts); err != nil { t.Fatal(err) } data, err := sd.GetData() if err != nil { t.Fatal(err) } if !bytes.Equal(data, []byte("hello\n")) { t.Fatal("bad msg") } } func TestVerifyGPGSMDetached(t *testing.T) { sd, err := ParseSignedData(fixtureSignatureGPGSM) if err != nil { t.Fatal(err) } opts := verifyOptionsForSignedData(sd) if _, err := sd.VerifyDetached([]byte("hello, world!\n"), opts, opts); err != nil { t.Fatal(err) } } func TestVerifyGPGSMNoCerts(t *testing.T) { sd, err := ParseSignedData(fixtureSignatureNoCertsGPGSM) if err != nil { t.Fatal(err) } if _, err := sd.VerifyDetached([]byte("hello, world!\n"), x509.VerifyOptions{}, x509.VerifyOptions{}); err != protocol.ErrNoCertificate { t.Fatalf("expected %v, got %v", protocol.ErrNoCertificate, err) } } func TestVerifyOpenSSLAttached(t *testing.T) { sd, err := ParseSignedData(fixtureSignatureOpenSSLAttached) if err != nil { t.Fatal(err) } if _, err := sd.Verify(verifyOptionsForSignedData(sd), x509.VerifyOptions{}); err != nil { t.Fatal(err) } } func TestVerifyOpenSSLDetached(t *testing.T) { sd, err := ParseSignedData(fixtureSignatureOpenSSLDetached) if err != nil { t.Fatal(err) } if _, err := sd.VerifyDetached([]byte("hello, world!"), verifyOptionsForSignedData(sd), x509.VerifyOptions{}); err != nil { t.Fatal(err) } } func TestVerifyChain(t *testing.T) { signerChain := leaf.Chain() ber, _ := Sign([]byte("hi"), leaf.Chain(), leaf.PrivateKey) sd, _ := ParseSignedData(ber) // good root chains, err := sd.Verify(rootOpts, x509.VerifyOptions{}) if err != nil { t.Fatal(err) } if len(chains) != 1 || len(chains[0]) != 1 || len(chains[0][0]) != len(signerChain) { t.Fatal("bad chain") } for i, c := range signerChain { if !chains[0][0][i].Equal(c) { t.Fatalf("bad cert: %d", i) } } // bad root if _, err = sd.Verify(otherRootOpts, otherRootOpts); err != nil { if _, isX509Err := err.(x509.UnknownAuthorityError); !isX509Err { t.Fatalf("expected x509.UnknownAuthorityError, got %v", err) } } // system root if _, err = sd.Verify(x509.VerifyOptions{}, x509.VerifyOptions{}); err != nil { // Need to check error string due to a regression in Go 1.18 // See https://github.com/golang/go/issues/52010 if _, isX509Err := err.(x509.UnknownAuthorityError); !isX509Err && !strings.Contains(err.Error(), "certificate is not trusted") { t.Fatalf("expected x509.UnknownAuthorityError, got %v", err) } } // no root if _, err = sd.Verify(x509.VerifyOptions{Roots: x509.NewCertPool()}, x509.VerifyOptions{}); err != nil { if _, isX509Err := err.(x509.UnknownAuthorityError); !isX509Err { t.Fatalf("expected x509.UnknownAuthorityError, got %v", err) } } } func TestVerifyDSAWithSHA1(t *testing.T) { // Created with the following openssl commands: // openssl dsaparam -out dsakey.pem -genkey 1024 // openssl req -key dsakey.pem -new -x509 -days 365000 -out dsa.crt -sha1 -subj "/CN=foo.com" // This creates a cert which is valid for 1000 years const publicCert string = ` -----BEGIN CERTIFICATE----- MIICWjCCAhoCCQCNGgCUlB6gszAJBgcqhkjOOAQDMBIxEDAOBgNVBAMMB2Zvby5j b20wIBcNMTkwNTI5MDMzNzUyWhgPMzAxODA5MjkwMzM3NTJaMBIxEDAOBgNVBAMM B2Zvby5jb20wggG2MIIBKwYHKoZIzjgEATCCAR4CgYEAyoyaU2206Zuu9MDfQ1gM Uba4Iu3j9EBWSSYiFjHS93Y2RVGqkNGHqNtLJ1nXANINqjnTP8RxnsccRejhX7C5 xVAlfsKSvJpRO1idp0SA8tVItpyHNjY175SYFYcg6elr1KQxfd41o/brruo915fs BXxl0S3261INjJJ64Ybn+CkCFQDa9pKFl6/S1OObPF3XeemwQVSW8QKBgByeV3hw YGzpdIu+/6iMYAvkNAYVBTfwuYd5Oa1Le2m9detLgcHg/0/q8kD5YafNPKYVAg1N aD+lLYEkbFuOJo00Pk1zrTQtKkrfbU9EVxd/6/XCrsFAVLl+39Q3vDEEX3tLjf+m r860lPYoC0+HRj+RGJiYmMqeydsV4N8gtRZbA4GEAAKBgGp8JeErPlZ7l56NG+mL XJxpVB7Vb0rqM/B0r5kMX/a0Nw7oa0Nehy2BJyvI3zREz2BYJd4RGsIq8cCts2yO zh8PgBUSNAnEEivfxRV+LivovAjVXqsr53WolzvkCOlxeX15a9SINSDNkphqsZrW zYTE+BOvUEbM0lM0273nAa4KMAkGByqGSM44BAMDLwAwLAIUURvdNGP0kzOJh79x ZFtQRP+6EQoCFAmd0+Tig4/yNQ0eSnQEFwEMQiD1 -----END CERTIFICATE----- ` // Signed pkcs7 doc created using above cert and the following commands: // printf 'Hello, World!' > hello.txt // openssl smime -sign -in hello.txt -nodetach -nocerts -outform pem -md sha1 -signer dsa.crt -inkey dsakey.pem const pkcs7Doc string = ` -----BEGIN PKCS7----- MIIBvgYJKoZIhvcNAQcCoIIBrzCCAasCAQExCzAJBgUrDgMCGgUAMBwGCSqGSIb3 DQEHAaAPBA1IZWxsbywgV29ybGQhMYIBeTCCAXUCAQEwHzASMRAwDgYDVQQDDAdm b28uY29tAgkAjRoAlJQeoLMwCQYFKw4DAhoFAKCCAQcwGAYJKoZIhvcNAQkDMQsG CSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwNTI5MDM1OTU4WjAjBgkqhkiG 9w0BCQQxFgQUCgqfKmdylCVXq1NV12r0Qvj2XgEwgacGCSqGSIb3DQEJDzGBmTCB ljALBglghkgBZQMEASowCAYGKoUDAgIJMAoGCCqFAwcBAQICMAoGCCqFAwcBAQID MAgGBiqFAwICFTALBglghkgBZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMH MA4GCCqGSIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG 9w0DAgIBKDAJBgcqhkjOOAQDBC4wLAIUXZ2aaGVyPDzpb1svc0ruE3qCUzsCFCNw F1Al5pA+giJh15T7Uu+p5O0J -----END PKCS7----- ` pkcs7CertPEM, _ := pem.Decode([]byte(publicCert)) if pkcs7CertPEM == nil { t.Fatal("failed to parse certificate PEM") } pkcs7Cert, err := x509.ParseCertificate(pkcs7CertPEM.Bytes) if err != nil { t.Fatalf("failed to parse certificate: %v", err) } pkcs7Certs := []*x509.Certificate{pkcs7Cert} pkcs7VerifyOptions := x509.VerifyOptions{ Roots: x509.NewCertPool(), KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, } pkcs7VerifyOptions.Roots.AddCert(pkcs7Cert) derRequest, _ := pem.Decode([]byte(pkcs7Doc)) if derRequest == nil { t.Fatalf("failed to parse id doc PEM: %s", pkcs7Doc) } sd, err := ParseSignedData([]byte(derRequest.Bytes)) if err != nil { t.Fatalf("Error parsing pkcs7 document: %s, err: %v", pkcs7Doc, err) } sd.SetCertificates(pkcs7Certs) _, err = sd.Verify(pkcs7VerifyOptions, x509.VerifyOptions{}) if err != nil { if xerrors.Is(err, x509.ErrUnsupportedAlgorithm) { return } t.Fatalf("Error verifying signing request: %v, err %v", *sd, err) } data, err := sd.GetData() if err != nil { t.Fatalf("Error getting data from pkcs7 document %s, err %v", pkcs7Doc, err) } expectedData := "Hello, World!" if string(data) != expectedData { t.Fatalf("Expected data: %s Actual Data: %s", expectedData, data) } } var fixtureSignatureOne = mustBase64Decode("" + "MIIDVgYJKoZIhvcNAQcCoIIDRzCCA0MCAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B" + "BwGgDwQNV2UgdGhlIFBlb3BsZaCCAdkwggHVMIIBQKADAgECAgRpuDctMAsGCSqG" + "SIb3DQEBCzApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxFZGRhcmQgU3Rh" + "cmswHhcNMTUwNTA2MDQyNDQ4WhcNMTYwNTA2MDQyNDQ4WjAlMRAwDgYDVQQKEwdB" + "Y21lIENvMREwDwYDVQQDEwhKb24gU25vdzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw" + "gYkCgYEAqr+tTF4mZP5rMwlXp1y+crRtFpuLXF1zvBZiYMfIvAHwo1ta8E1IcyEP" + "J1jIiKMcwbzeo6kAmZzIJRCTezq9jwXUsKbQTvcfOH9HmjUmXBRWFXZYoQs/OaaF" + "a45deHmwEeMQkuSWEtYiVKKZXtJOtflKIT3MryJEDiiItMkdybUCAwEAAaMSMBAw" + "DgYDVR0PAQH/BAQDAgCgMAsGCSqGSIb3DQEBCwOBgQDK1EweZWRL+f7Z+J0kVzY8" + "zXptcBaV4Lf5wGZJLJVUgp33bpLNpT3yadS++XQJ+cvtW3wADQzBSTMduyOF8Zf+" + "L7TjjrQ2+F2HbNbKUhBQKudxTfv9dJHdKbD+ngCCdQJYkIy2YexsoNG0C8nQkggy" + "axZd/J69xDVx6pui3Sj8sDGCATYwggEyAgEBMDEwKTEQMA4GA1UEChMHQWNtZSBD" + "bzEVMBMGA1UEAxMMRWRkYXJkIFN0YXJrAgRpuDctMAcGBSsOAwIaoGEwGAYJKoZI" + "hvcNAQkDMQsGCSqGSIb3DQEHATAgBgkqhkiG9w0BCQUxExcRMTUwNTA2MDAyNDQ4" + "LTA0MDAwIwYJKoZIhvcNAQkEMRYEFG9D7gcTh9zfKiYNJ1lgB0yTh4sZMAsGCSqG" + "SIb3DQEBAQSBgFF3sGDU9PtXty/QMtpcFa35vvIOqmWQAIZt93XAskQOnBq4OloX" + "iL9Ct7t1m4pzjRm0o9nDkbaSLZe7HKASHdCqijroScGlI8M+alJ8drHSFv6ZIjnM" + "FIwIf0B2Lko6nh9/6mUXq7tbbIHa3Gd1JUVire/QFFtmgRXMbXYk8SIS", ) var fixtureSignatureGPGSMAttached = mustBase64Decode("" + "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0B" + "BwGggCSABAZoZWxsbwoAAAAAAACgggNYMIIDVDCCAjygAwIBAgIIFnTa5+xvrkgw" + "DQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAxMJQmVuIFRvZXdzMCAXDTE3MTExNjE3" + "NTAzMloYDzIwNjMwNDA1MTcwMDAwWjAUMRIwEAYDVQQDEwlCZW4gVG9ld3MwggEi" + "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdcejAkkPekPH6VuFbDcbkf5XD" + "jCAYW3JWlc+tyVpBXoOtDdETKFUQqXxxm2ukLZlRuz/+AugtaijRmgr2boPYzL6v" + "rHuPQVlNl327QkIqaia67HEWmy/9puil+d05gzg3Y5H2VrkIqzlZieTzIbFAfnyR" + "1KAwvC5yF0Oa60AH6rWg67JAjxzE37j/bBAsUhvNtWPbZ+mSHrAgYE6tQYts9V5x" + "82rlOP8d6V49CRSQ59HgMsJK7P6mrhkp1TAbAU4fIIZoyKBi3JZsCMTExz+xAM+g" + "2dT+W5JPom9izbdzF4Zj8PH95nf2Dlvf9dtlvAXVkePVozeyAmxNMo5kJbAJAgMB" + "AAGjgacwgaQwbgYDVR0RBGcwZYEUbWFzdGFoeWV0aUBnbWFpbC5jb22BFW1hc3Rh" + "aHlldGlAZ2l0aHViLmNvbYERYnRvZXdzQGdpdGh1Yi5jb22BI21hc3RhaHlldGlA" + "dXNlcnMubm9yZXBseS5naXRodWIuY29tMBEGCisGAQQB2kcCAgEEAwEB/zAPBgNV" + "HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIE8DANBgkqhkiG9w0BAQsFAAOCAQEA" + "iurKpC6lhIEEsqkpN65zqUhnWijgf6jai1TlM59PYhYNduGoscoMZsvgI22ONLVu" + "DguY0zQdGOI31TugdkCvd0728Eu1rwZVzJx4z6vM0CjCb1FluDMqGXJt7PSXz92T" + "CeybmkkgQqiR9eoJUJPi9C+Lrwi4aOfFiwutvsGw9HB+n5EOVCj+tE0jbnraY323" + "nj2Ibfo/ZGPzXpwSJMimma0Qa9IF5CKBGkbZWPRCi/l5vfDEcqy7od9KmIW7WKAu" + "aNjW5c0Zgu4ZufRYpiN8IEkvnAXH5WAFWSKlQslu5zVgqSoB7T8pu211OTWBdDgu" + "LGuzzactHfA/HTr9d5LNrzGCAeEwggHdAgEBMCAwFDESMBAGA1UEAxMJQmVuIFRv" + "ZXdzAggWdNrn7G+uSDANBglghkgBZQMEAgEFAKCBkzAYBgkqhkiG9w0BCQMxCwYJ" + "KoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xNzExMjIxNzU3NTZaMCgGCSqGSIb3" + "DQEJDzEbMBkwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMC8GCSqGSIb3DQEJBDEi" + "BCBYkbW1ItXfCG0P8LEQ+9nSG7T8cWOvNNCChqLoRva+AzANBgkqhkiG9w0BAQEF" + "AASCAQBbKSOFVXnWuRADFW1M9mZApLKjU2jtzN22aaVTlvSDoHE7yzj53EVorfm4" + "br1JWJMeOJcfAiV5oiJiuIqiXOec5bTgR9EzkCZ8yA+R89y6M538XXp8sLMxNkO/" + "EhoLXdQV8UhoF2mXktbbe/blTODvupTBonUXQhVAeJpWi0q8Qaz5StpzuXu6UFWK" + "nTCTsl8gg1x/Wf0zLOUVWtLLPLeQB5usv1fQker0e+kCthv/q+QyLxw9J3e5rJ9a" + "Dekeh5WkaS8yHCCvnOyOLI9/o2rHwUII36XjvK6VF+UHG+OcoL29BnUb01+vwxPk" + "SDXMwnexRO3w39tu4ChUFbsX8l5CAAAAAAAA", ) var fixtureSignatureGPGSM = mustBase64Decode("" + "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0B" + "BwEAAKCCA1gwggNUMIICPKADAgECAggWdNrn7G+uSDANBgkqhkiG9w0BAQsFADAU" + "MRIwEAYDVQQDEwlCZW4gVG9ld3MwIBcNMTcxMTE2MTc1MDMyWhgPMjA2MzA0MDUx" + "NzAwMDBaMBQxEjAQBgNVBAMTCUJlbiBUb2V3czCCASIwDQYJKoZIhvcNAQEBBQAD" + "ggEPADCCAQoCggEBAJ1x6MCSQ96Q8fpW4VsNxuR/lcOMIBhbclaVz63JWkFeg60N" + "0RMoVRCpfHGba6QtmVG7P/4C6C1qKNGaCvZug9jMvq+se49BWU2XfbtCQipqJrrs" + "cRabL/2m6KX53TmDODdjkfZWuQirOVmJ5PMhsUB+fJHUoDC8LnIXQ5rrQAfqtaDr" + "skCPHMTfuP9sECxSG821Y9tn6ZIesCBgTq1Bi2z1XnHzauU4/x3pXj0JFJDn0eAy" + "wkrs/qauGSnVMBsBTh8ghmjIoGLclmwIxMTHP7EAz6DZ1P5bkk+ib2LNt3MXhmPw" + "8f3md/YOW9/122W8BdWR49WjN7ICbE0yjmQlsAkCAwEAAaOBpzCBpDBuBgNVHREE" + "ZzBlgRRtYXN0YWh5ZXRpQGdtYWlsLmNvbYEVbWFzdGFoeWV0aUBnaXRodWIuY29t" + "gRFidG9ld3NAZ2l0aHViLmNvbYEjbWFzdGFoeWV0aUB1c2Vycy5ub3JlcGx5Lmdp" + "dGh1Yi5jb20wEQYKKwYBBAHaRwICAQQDAQH/MA8GA1UdEwEB/wQFMAMBAf8wDgYD" + "VR0PAQH/BAQDAgTwMA0GCSqGSIb3DQEBCwUAA4IBAQCK6sqkLqWEgQSyqSk3rnOp" + "SGdaKOB/qNqLVOUzn09iFg124aixygxmy+AjbY40tW4OC5jTNB0Y4jfVO6B2QK93" + "TvbwS7WvBlXMnHjPq8zQKMJvUWW4MyoZcm3s9JfP3ZMJ7JuaSSBCqJH16glQk+L0" + "L4uvCLho58WLC62+wbD0cH6fkQ5UKP60TSNuetpjfbeePYht+j9kY/NenBIkyKaZ" + "rRBr0gXkIoEaRtlY9EKL+Xm98MRyrLuh30qYhbtYoC5o2NblzRmC7hm59FimI3wg" + "SS+cBcflYAVZIqVCyW7nNWCpKgHtPym7bXU5NYF0OC4sa7PNpy0d8D8dOv13ks2v" + "MYIB4TCCAd0CAQEwIDAUMRIwEAYDVQQDEwlCZW4gVG9ld3MCCBZ02ufsb65IMA0G" + "CWCGSAFlAwQCAQUAoIGTMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZI" + "hvcNAQkFMQ8XDTE3MTExNzAwNDcyNFowKAYJKoZIhvcNAQkPMRswGTALBglghkgB" + "ZQMEAQIwCgYIKoZIhvcNAwcwLwYJKoZIhvcNAQkEMSIEIE3KD9X0JKMbA6uAfLrn" + "frMr8tCJ7tHO4VSzr+1FjeDcMA0GCSqGSIb3DQEBAQUABIIBAGH7rQRx3IPuJbPr" + "FjErvUWvgh8fS9s0mKI3/NPgUhx2gu1TpPdTp68La8KUDbN4jRVZ8o59WnzN9/So" + "5mpc0AcpVlolIb4B/qQMkBALx6O5nHE/lr7orXQWUPM3iSUHAscNZbNr98k8YBdl" + "hfarrderC+7n3dLOhNwpz3+STVr6l5czuXOqggcbwOMDbg4o/fiI2hm6eG79rDsd" + "MJ3NoMYnEURUtsK0OffSMpnbsifEyRviKQG0LC4neqMJGylm6uYOXfzNsCbP12MM" + "VovtxgUEskE2aU9UfPPqtm6H69QgcusUxxoECxWifydVObY/di5m5FGOCzP4b+QG" + "SX+du6QAAAAAAAA=", ) var fixtureSignatureNoCertsGPGSM = mustBase64Decode("" + "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0B" + "BwEAADGCAeEwggHdAgEBMCAwFDESMBAGA1UEAxMJQmVuIFRvZXdzAggWdNrn7G+u" + "SDANBglghkgBZQMEAgEFAKCBkzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG" + "CSqGSIb3DQEJBTEPFw0xNzExMTcwMDQxNDhaMCgGCSqGSIb3DQEJDzEbMBkwCwYJ" + "YIZIAWUDBAECMAoGCCqGSIb3DQMHMC8GCSqGSIb3DQEJBDEiBCBNyg/V9CSjGwOr" + "gHy6536zK/LQie7RzuFUs6/tRY3g3DANBgkqhkiG9w0BAQEFAASCAQAvGAGPMaH3" + "oRiNDU0AGIVyjXUrZ8g2VRazGCTuuO0CPGWBDbBuuvCePuWTddcv5KHHyrYO0yUD" + "xergVhh1EXIsOItHbJ6QeMstmY8Ub7HGm4Srdtm3MMSEe24zRmKK5yvPfeaaXeb6" + "MASKXvViU/j9VDwUZ2CFPUzPq8DlS6j4w6dapfphFGN1wJV3ADLUzUkTXfXQ57HE" + "WUKdbxgcuyBH7eLhZpKAXP31iRKm2b7dV50SruRCqNYZOp8bUQ57bC2jels0dzQd" + "EQS76O/DH6eQ3/OgvpmR8BjlujA82tgjqP7fj0S7Cw2VlPqcey0iqRmAmiO2qzOI" + "KAYzMkxWr7iUAAAAAAAA", ) var fixtureSignatureOpenSSLAttached = mustBase64Decode("" + "MIIFGgYJKoZIhvcNAQcCoIIFCzCCBQcCAQExDzANBglghkgBZQMEAgEFADAcBgkq" + "hkiG9w0BBwGgDwQNaGVsbG8sIHdvcmxkIaCCAqMwggKfMIIBh6ADAgECAgEAMA0G" + "CSqGSIb3DQEBBQUAMBMxETAPBgNVBAMMCGNtcy10ZXN0MB4XDTE3MTEyMDIwNTM0" + "M1oXDTI3MTExODIwNTM0M1owEzERMA8GA1UEAwwIY21zLXRlc3QwggEiMA0GCSqG" + "SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWMRnJdRQxw8j8Yn3jh/rcZyeALStl+MmM" + "TEtr6XsmMOWQhnP6nCAIOw5EIAXGpKl4Yg3F2gDKmJCVl279Q+G9nLtvmWvCzu19" + "BJUG7jVLWzO8KSuJa83iiilZUP2adVZujdGB6dxekIBu7vkYi9XxZJm4edhj0bkd" + "EtkxLCNUGDQKsywnKOTWzfefT9UCQJyLwt74ThJtNX7uoYrfAHNfBARk3Kx+wf4U" + "Grd2GmSe8Lnr3FNcZ/uMJffsYvBk3fbDwYsVC6rd4BuJvvri3K1dti3rnvDEnuMI" + "Ve7a2n7NE7yV0cietIjKeeY8bO25lwrTtBzgP5y1G9spjzAtiRLZAgMBAAEwDQYJ" + "KoZIhvcNAQEFBQADggEBAMkYPFmsHYlyO+KZMKEWUWOdw1rwrIVhLQOKqLz8Wbe8" + "lIQ5pdsd4S1DqvMEzYyMtpZckZ9mOBZh/SQsmdb8sZnQwiMvlPSO6IWp/MpuP+VK" + "v8IBAr1aaLlMaelV086uIFc9coE6XAdWFrGlUT9FYM00JwoSfi51vbcqbIh6P8y9" + "uwHqlt2vkVYujto+p0UMBnBZkfKBgzMG7ILWpJbVszmpesVzI2XUgq8BxlO0fvw5" + "m/R4bAtHqXTK0xVrTBXUg6izFbdA3pVlFMiuv8Kq2cyBg+VkXGYmZ37BGhApe5Le" + "Dabe4iGcXQMW4lunjRSv8gDu/ODA/20OMNVDOx92MTIxggIqMIICJgIBATAYMBMx" + "ETAPBgNVBAMMCGNtcy10ZXN0AgEAMA0GCWCGSAFlAwQCAQUAoIHkMBgGCSqGSIb3" + "DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE3MTEyMDIwNTM0M1ow" + "LwYJKoZIhvcNAQkEMSIEIGjmVrJR5n6DWL74SDqw1RxmGfPnoanw51g41B/zaPco" + "MHkGCSqGSIb3DQEJDzFsMGowCwYJYIZIAWUDBAEqMAsGCWCGSAFlAwQBFjALBglg" + "hkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMC" + "AgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIIBAJHB" + "kfH1hZ4Y0TI6PdW7DNFnb++KQJiu4NmzE7SyTJOCxC2W44uAKUdJw7c8cdn/lcb/" + "y1kvwNbi2kysuZSTpywBIjHSTw3BTwdaNJFd6HUV1mX2IQRfaJIPW5fqkhLfQtZ6" + "LZka/HWQ5fwA51g6lVNTMbStjsPlBef6qEDcCLMp/4CNEqC5+fUx8Jb7Q5mvyCHQ" + "3IZrIEMLBYhrgrm61qh/MXKnAqlEo6XxN1fL0CXDxy9dYPSKr2G66o9+BjmYktF5" + "3MfxrT4JDizd2S/8BVEv+H+uHmrpyRxMceREPJVrVHOdd922hyKALbAGcoyMdXpj" + "ZdMtHnR5z07z9wxvwiw=", ) var fixtureSignatureOpenSSLDetached = mustBase64Decode("" + "MIIFCQYJKoZIhvcNAQcCoIIE+jCCBPYCAQExDzANBglghkgBZQMEAgEFADALBgkq" + "hkiG9w0BBwGgggKjMIICnzCCAYegAwIBAgIBADANBgkqhkiG9w0BAQUFADATMREw" + "DwYDVQQDDAhjbXMtdGVzdDAeFw0xNzExMjAyMTE0NDdaFw0yNzExMTgyMTE0NDda" + "MBMxETAPBgNVBAMMCGNtcy10ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB" + "CgKCAQEA5VQ0FRvQRA9F+6nss77yUcm3x8IOoJV/icQrtrkR/BHGgeepcLIcHkWh" + "s/cap69xR5TCtONy0I4tqKf/vXnKXvMjsGGrecFMi8NVTbEoNg9m47nbdO7BY1+f" + "waLfwAX5vf17BRSqA0wRIoNIzJc07mNrI84EbKfVmDtPrqzwnT0sIKqj5p2PQdWi" + "sPwOocLYJBdAPglnLuFk6WTZalJRgV7h50nl1GBDKJVo1Yc7zqPdqWzHzFqK759g" + "CHBZMYJdqIx/wev/l66oEcJZr6gnnKzq8lsWljpjVWD96z/W/fehWZsWlWkvmrus" + "qizMbL0vCx8HrReo7+hszMIHR5bwTwIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAD" + "ZjPxm/JHc4KoQUaVOSAU97lO60MD21Ud0LtaebbiSJnaMH9a/rb3kuxJAKVSBhDp" + "wyRK19KNtaSXHEAD48aJeT7J4wsDJFNfKGx/9R2iYB5xjc/POpK13A/o4fDrpLWL" + "1doIc0KjVA63BXaYOwsEj2iKzUKNFZ2kS3bXMkEBhUDUXtSo08WFI7UkgYTuIfM2" + "LS/wyORcwZIEIvq+ndkch/nAyQZ8U0/85dgwpOQcyZ0UDiu8Ti9z9IUlhxSq2T13" + "JhIfiMa4m27y71JmsFy12uN3fGBckkyNkKkxVMy0H4Ukr1hq/ZkvH3HdrEnWmNEu" + "WdU7WvIBsbe3U2idyhBSMYICKjCCAiYCAQEwGDATMREwDwYDVQQDDAhjbXMtdGVz" + "dAIBADANBglghkgBZQMEAgEFAKCB5DAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB" + "MBwGCSqGSIb3DQEJBTEPFw0xNzExMjAyMTE0NDdaMC8GCSqGSIb3DQEJBDEiBCBo" + "5layUeZ+g1i++Eg6sNUcZhnz56Gp8OdYONQf82j3KDB5BgkqhkiG9w0BCQ8xbDBq" + "MAsGCWCGSAFlAwQBKjALBglghkgBZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3" + "DQMHMA4GCCqGSIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggq" + "hkiG9w0DAgIBKDANBgkqhkiG9w0BAQEFAASCAQAcLsBbjvlhz+HAy7m5cvh8tRav" + "xT05fFK1hwBC287z+D/UaCrvrd2vR4bdUV8jfS5iTyUfX/BikOljxRwUMgtBLPKq" + "gdNokoxUoQiqVOdgCER0isNLF/8+O29reI6N/9Mp+IpfE41o2xcRrggfncuPX00K" + "MB2K4/ZF35HddfblHIgQ+9gWfHE52KMur4XeI5sc/izMNuPyR8VVB7St5JLMepHj" + "UtbPYBJ0bRSwDX1JAoB+Ze/mPvCmo/pS5QyYfNvXg3Jw4TVoud5+oUH9r6MwSxzN" + "BSws5SM9d0GAafR+Hj19x9s8ypUjLJmGIAjeTrlgcYUTJjnfEtZBL5Je2FuK", ) var fixtureSignatureOutlookDetached = mustBase64Decode("" + "MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwEAAKCCD0Yw" + "ggO3MIICn6ADAgECAhAM5+DlF9hG/o/lYPwb8DA5MA0GCSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYT" + "AlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAi" + "BgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTEx" + "MTAwMDAwMDBaMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT" + "EHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTCC" + "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK0OFc7kQ4BcsYfzt2D5cRKlrtwmlIiq9M71" + "IDkoWGAM+IDaqRWVMmE8tbEohIqK3J8KDIMXeo+QrIrneVNcMYQq9g+YMjZ2zN7dPKii72r7IfJS" + "Yd+fINcf4rHZ/hhk0hJbX/lYGDW8R82hNvlrf9SwOD7BG8OMM9nYLxj+KA+zp4PWw25EwGE1lhb+" + "WZyLdm3X8aJLDSv/C3LanmDQjpA1xnhVhyChz+VtCshJfDGYM2wi6YfQMlqiuhOCEe05F52ZOnKh" + "5vqk2dUXMXWuhX0irj8BRob2KHnIsdrkVxfEfhwOsLSSplazvbKX7aqn8LfFqD+VFtD/oZbrCF8Y" + "d08CAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEXr" + "oq/0ksuCMS1Ri6enIZ3zbcgPMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqG" + "SIb3DQEBBQUAA4IBAQCiDrzf4u3w43JzemSUv/dyZtgy5EJ1Yq6H6/LV2d5Ws5/MzhQouQ2XYFwS" + "TFjk0z2DSUVYlzVpGqhH6lbGeasS2GeBhN9/CTyU5rgmLCC9PbMoifdf/yLil4Qf6WXvh+DfwWdJ" + "s13rsgkq6ybteL59PyvztyY1bV+JAbZJW58BBZurPSXBzLZ/wvFvhsb6ZGjrgS2U60K3+owe3WLx" + "vlBnt2y98/Efaww2BxZ/N3ypW2168RJGYIPXJwS+S86XvsNnKmgR34DnDDNmvxMNFG7zfx9jEB76" + "jRslbWyPpbdhAbHSoyahEHGdreLD+cOZUbcrBwjOLuZQsqf6CkUvovDyMIIFNTCCBB2gAwIBAgIQ" + "BaTO8JYvDXElKlIYlJMocDANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM" + "RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2Vy" + "dCBTSEEyIEFzc3VyZWQgSUQgQ0EwHhcNMTcwMzAxMDAwMDAwWhcNMjAwMjI4MTIwMDAwWjBbMQsw" + "CQYDVQQGEwJVUzELMAkGA1UECBMCTlkxETAPBgNVBAcTCE5ldyBZb3JrMRUwEwYDVQQKEwxPcmVu" + "IE5vdm90bnkxFTATBgNVBAMTDE9yZW4gTm92b3RueTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC" + "AQoCggEBAMKWbckomlzHxi8o34oOv8FxVIPI2wyhVmW0VdcgFyLlr10D50h3f4jlFOqiWI60c35A" + "3be77ykVbX7dlijMUa1xgBAxSmMFiRYWy1OqsgciGO/VXEwTmPjcxgwYGEBCcVXBAzbmYQtlvr1U" + "FBJc3CwSQknznLPWLPmOSntPfexwQYcHOinQ3HvdenKFnfGH+BtBsaBSYGokpjH1RQCPxKruuVOa" + "YdHeG8g+vp96w1rsCK9r0RAJp7w1gCoMePxlFQr/1r7kJhcclcNU6hodEouF9OJOeahsD9vbM9Bt" + "DafC1RMAo5+cYbrECHgx5M3JLh/BACh5JRaLQHg3QkWrZ9kCAwEAAaOCAekwggHlMB8GA1UdIwQY" + "MBaAFOcCI4AAT9jXvJQL2T90OUkyPIp5MB0GA1UdDgQWBBQOAAryJTOprIAZzEnY28ajByUJ6TAM" + "BgNVHRMBAf8EAjAAMBsGA1UdEQQUMBKBEG9yZW5Abm92b3RueS5vcmcwDgYDVR0PAQH/BAQDAgWg" + "MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDBDBgNVHSAEPDA6MDgGCmCGSAGG/WwEAQIw" + "KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCBiAYDVR0fBIGAMH4w" + "PaA7oDmGN2h0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURDQS1n" + "Mi5jcmwwPaA7oDmGN2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVk" + "SURDQS1nMi5jcmwweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp" + "Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy" + "dFNIQTJBc3N1cmVkSURDQS5jcnQwDQYJKoZIhvcNAQELBQADggEBADh2DYGfn+1eg21dTa34iZlu" + "IyActG/S23bCLnJSThPbiCfZgGkKr9Bq6TSJ4qQfsquIB7cO46mJ+tzHL570xAsJ4pC7z3RhBdzK" + "j9uT6ZUExdHQs2FoPjU5uT1UhqHv7T9qYp689XpZ2xPLH59SwLASIVnoQFIS0MKT8AN6ZgKxDWDY" + "EUyRfGQxxDbfqWhncH0qxT20mv8TnvIMo2ngsCBZfpJcv9u3LijnD7uVCZ2qRIJkmJ7s1eoGc05c" + "Z+7NeA8vC28BgGe2svMUlRInaNsMDUBmizI4x6DnS8uVlX22KAdPML9NvPOfCGCohDevZgCSMx/o" + "nH+foA+rOCngkR8wggZOMIIFNqADAgECAhAErnlgZmaQGrnFf6ZsW9zNMA0GCSqGSIb3DQEBCwUA" + "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdp" + "Y2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzExMDUx" + "MjAwMDBaFw0yODExMDUxMjAwMDBaMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ" + "bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IFNIQTIgQXNz" + "dXJlZCBJRCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANz4ESM/arXvwCd5Gy0F" + "h6IQQzHfDtQVG093pCLOPoxw8L4Hjt0nKrwBHbYsCsrdaVgfQe1qBR/aY3hZHiIsK/i6fsk1O1bx" + "H3xCfiWwIxnGRTjXPUT5IHxgrhywWhgEvo8796nwlJqmDGNJtkEXU0AyvU/mUHpQHyVF6PGJr83/" + "Xv9Q8/AXEf+9xYn1vWK52PuORQSFbZnNxUhN/SarAjZF6jbXX2riGoJBCtzp2fWRF47GIa04PBPm" + "Hn9mnNVN2Uba9s9Sp307JMO0wVE1xpvr1O9+5HsD4US9egs34E/LgooNcRjkpuCJLBvzsnM8wbCS" + "nhh9vat9xX0IoSzCn3MCAwEAAaOCAvgwggL0MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/" + "BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu" + "Y29tMIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRB" + "c3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNl" + "cnRBc3N1cmVkSURSb290Q0EuY3JsMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDCCAbMG" + "A1UdIASCAaowggGmMIIBogYKYIZIAYb9bAACBDCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3" + "LmRpZ2ljZXJ0LmNvbS9DUFMwggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABv" + "AGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQA" + "ZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0" + "ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQA" + "eQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBp" + "AGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUA" + "cgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wHQYDVR0OBBYEFOcCI4AAT9jXvJQL" + "2T90OUkyPIp5MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUA" + "A4IBAQBO1Iknuf0dh3d+DygFkPEKL8k7Pr2TnJDGr/qRUYcyVGvoysFxUVyZjrX64GIZmaYHmnwT" + "J9vlAqKEEtkV9gpEV8Q0j21zHzrWoAE93uOC5EVrsusl/YBeHTmQvltC9s6RYOP5oFYMSBDOM2h7" + "zZOr8GrLT1gPuXtdGwSBnqci4ldJJ+6Skwi+aQhTAjouXcgZ9FCATgLZsF2RtJOH+ZaWgVVAjmbt" + "gti7KF/tTGHtBlgoGVMRRLxHICmyBGzYiVSZO3XbZ3gsHpJ4xlU9WBIRMm69QwxNNNt7xkLb7L6r" + "m2FMBpLjjt8hKlBXBMBgojXVJJ5mNwlJz9X4ZbPg4m7CMYIDvzCCA7sCAQEweTBlMQswCQYDVQQG" + "EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw" + "IgYDVQQDExtEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ0ECEAWkzvCWLw1xJSpSGJSTKHAwDQYJ" + "YIZIAWUDBAIBBQCgggIXMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X" + "DTE3MTEyOTE0NDMxOVowLwYJKoZIhvcNAQkEMSIEIEgBjCiMhZLBevfHienSec11YNE+P7PSd4JD" + "wfCQCrwWMIGIBgkrBgEEAYI3EAQxezB5MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy" + "dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IFNIQTIg" + "QXNzdXJlZCBJRCBDQQIQBaTO8JYvDXElKlIYlJMocDCBigYLKoZIhvcNAQkQAgsxe6B5MGUxCzAJ" + "BgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j" + "b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDQQIQBaTO8JYvDXElKlIYlJMo" + "cDCBkwYJKoZIhvcNAQkPMYGFMIGCMAsGCWCGSAFlAwQBKjALBglghkgBZQMEARYwCgYIKoZIhvcN" + "AwcwCwYJYIZIAWUDBAECMA4GCCqGSIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDALBglghkgBZQME" + "AgEwCwYJYIZIAWUDBAIDMAsGCWCGSAFlAwQCAjAHBgUrDgMCGjANBgkqhkiG9w0BAQEFAASCAQBh" + "AjkUd98Si7LKxELWdwY8yrqrfK61JxVSxSY/BkF3xS/0QbQMU9Y+0V23nJX5ymamgCd9yNTdNapV" + "D4OzoVXfmTqd1/AD30M1a1CdBVoNGV8X4Uv8Z1fAl5MN+6Yt1CeIun39gvkutAgUmvCVrjFN+gD6" + "GH+VTQNGHr3wxdmtL9F8WeNECvpVgYEMqnYRrYHw4B6euJRsy4UnB4Sy/ogV1elkipxCbqRovPU1" + "pVeKhkfYuRlsLwbBwQPKvzcfUU3ZJua4I3AKKPxlqdY8uP72A5iObDTL8kHhSRMtVVHoruVzgJPZ" + "+9Mfsz41eM4pJSPDKZPYD9rH6cUKJI8xEnmCAAAAAAAA", ) var fixtureMessageOutlookDetached = mustBase64Decode("" + "Q29udGVudC1UeXBlOiBtdWx0aXBhcnQvbWl4ZWQ7DQoJYm91bmRhcnk9Ii0t" + "LS09X05leHRQYXJ0XzAwMV8wMDFEXzAxRDM2OEY2LjdDRTk2MzcwIg0KDQoN" + "Ci0tLS0tLT1fTmV4dFBhcnRfMDAxXzAwMURfMDFEMzY4RjYuN0NFOTYzNzAN" + "CkNvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L2FsdGVybmF0aXZlOw0KCWJvdW5k" + "YXJ5PSItLS0tPV9OZXh0UGFydF8wMDJfMDAxRV8wMUQzNjhGNi43Q0U5NjM3" + "MCINCg0KDQotLS0tLS09X05leHRQYXJ0XzAwMl8wMDFFXzAxRDM2OEY2LjdD" + "RTk2MzcwDQpDb250ZW50LVR5cGU6IHRleHQvcGxhaW47DQoJY2hhcnNldD0i" + "dXMtYXNjaWkiDQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA3Yml0DQoN" + "CkhlcmUncyBhIG1lc3NhZ2Ugd2l0aCBhbiBTL01JTUUgc2lnbmF0dXJlIGFu" + "ZCBhbiBhdHRhY2htZW50Lg0KDQogDQoNCkp1c3QgY3VyaW91cyB3aGF0IHlv" + "dSdyZSBsb29raW5nIGF0IHNpZ25hdHVyZXMgZm9yPw0KDQogDQoNCkhvcGUg" + "dGhpcyBoZWxwcyENCg0KT3Jlbg0KDQoNCi0tLS0tLT1fTmV4dFBhcnRfMDAy" + "XzAwMUVfMDFEMzY4RjYuN0NFOTYzNzANCkNvbnRlbnQtVHlwZTogdGV4dC9o" + "dG1sOw0KCWNoYXJzZXQ9InVzLWFzY2lpIg0KQ29udGVudC1UcmFuc2Zlci1F" + "bmNvZGluZzogcXVvdGVkLXByaW50YWJsZQ0KDQo8aHRtbCB4bWxuczp2PTNE" + "InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206dm1sIiA9DQp4bWxuczpvPTNE" + "InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgPQ0K" + "eG1sbnM6dz0zRCJ1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOm9mZmljZTp3" + "b3JkIiA9DQp4bWxuczptPTNEImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5j" + "b20vb2ZmaWNlLzIwMDQvMTIvb21tbCIgPQ0KeG1sbnM9M0QiaHR0cDovL3d3" + "dy53My5vcmcvVFIvUkVDLWh0bWw0MCI+PGhlYWQ+PE1FVEEgPQ0KSFRUUC1F" + "UVVJVj0zRCJDb250ZW50LVR5cGUiIENPTlRFTlQ9M0QidGV4dC9odG1sOyA9" + "DQpjaGFyc2V0PTNEdXMtYXNjaWkiPjxtZXRhIG5hbWU9M0RHZW5lcmF0b3Ig" + "Y29udGVudD0zRCJNaWNyb3NvZnQgV29yZCAxNSA9DQooZmlsdGVyZWQgbWVk" + "aXVtKSI+PHN0eWxlPjwhLS0NCi8qIEZvbnQgRGVmaW5pdGlvbnMgKi8NCkBm" + "b250LWZhY2UNCgl7Zm9udC1mYW1pbHk6IkNhbWJyaWEgTWF0aCI7DQoJcGFu" + "b3NlLTE6MiA0IDUgMyA1IDQgNiAzIDIgNDt9DQpAZm9udC1mYWNlDQoJe2Zv" + "bnQtZmFtaWx5OkNhbGlicmk7DQoJcGFub3NlLTE6MiAxNSA1IDIgMiAyIDQg" + "MyAyIDQ7fQ0KLyogU3R5bGUgRGVmaW5pdGlvbnMgKi8NCnAuTXNvTm9ybWFs" + "LCBsaS5Nc29Ob3JtYWwsIGRpdi5Nc29Ob3JtYWwNCgl7bWFyZ2luOjBpbjsN" + "CgltYXJnaW4tYm90dG9tOi4wMDAxcHQ7DQoJZm9udC1zaXplOjExLjBwdDsN" + "Cglmb250LWZhbWlseToiQ2FsaWJyaSIsc2Fucy1zZXJpZjt9DQphOmxpbmss" + "IHNwYW4uTXNvSHlwZXJsaW5rDQoJe21zby1zdHlsZS1wcmlvcml0eTo5OTsN" + "Cgljb2xvcjojMDU2M0MxOw0KCXRleHQtZGVjb3JhdGlvbjp1bmRlcmxpbmU7" + "fQ0KYTp2aXNpdGVkLCBzcGFuLk1zb0h5cGVybGlua0ZvbGxvd2VkDQoJe21z" + "by1zdHlsZS1wcmlvcml0eTo5OTsNCgljb2xvcjojOTU0RjcyOw0KCXRleHQt" + "ZGVjb3JhdGlvbjp1bmRlcmxpbmU7fQ0Kc3Bhbi5FbWFpbFN0eWxlMTcNCgl7" + "bXNvLXN0eWxlLXR5cGU6cGVyc29uYWwtY29tcG9zZTsNCglmb250LWZhbWls" + "eToiQ2FsaWJyaSIsc2Fucy1zZXJpZjsNCgljb2xvcjp3aW5kb3d0ZXh0O30N" + "Ci5Nc29DaHBEZWZhdWx0DQoJe21zby1zdHlsZS10eXBlOmV4cG9ydC1vbmx5" + "Ow0KCWZvbnQtZmFtaWx5OiJDYWxpYnJpIixzYW5zLXNlcmlmO30NCkBwYWdl" + "IFdvcmRTZWN0aW9uMQ0KCXtzaXplOjguNWluIDExLjBpbjsNCgltYXJnaW46" + "MS4waW4gMS4waW4gMS4waW4gMS4waW47fQ0KZGl2LldvcmRTZWN0aW9uMQ0K" + "CXtwYWdlOldvcmRTZWN0aW9uMTt9DQotLT48L3N0eWxlPjwhLS1baWYgZ3Rl" + "IG1zbyA5XT48eG1sPg0KPG86c2hhcGVkZWZhdWx0cyB2OmV4dD0zRCJlZGl0" + "IiBzcGlkbWF4PTNEIjEwMjYiIC8+DQo8L3htbD48IVtlbmRpZl0tLT48IS0t" + "W2lmIGd0ZSBtc28gOV0+PHhtbD4NCjxvOnNoYXBlbGF5b3V0IHY6ZXh0PTNE" + "ImVkaXQiPg0KPG86aWRtYXAgdjpleHQ9M0QiZWRpdCIgZGF0YT0zRCIxIiAv" + "Pg0KPC9vOnNoYXBlbGF5b3V0PjwveG1sPjwhW2VuZGlmXS0tPjwvaGVhZD48" + "Ym9keSBsYW5nPTNERU4tVVMgPQ0KbGluaz0zRCIjMDU2M0MxIiB2bGluaz0z" + "RCIjOTU0RjcyIj48ZGl2IGNsYXNzPTNEV29yZFNlY3Rpb24xPjxwID0NCmNs" + "YXNzPTNETXNvTm9ybWFsPkhlcmUmIzgyMTc7cyBhIG1lc3NhZ2Ugd2l0aCBh" + "biBTL01JTUUgc2lnbmF0dXJlIGFuZCBhbiA9DQphdHRhY2htZW50LjxvOnA+" + "PC9vOnA+PC9wPjxwIGNsYXNzPTNETXNvTm9ybWFsPjxvOnA+Jm5ic3A7PC9v" + "OnA+PC9wPjxwID0NCmNsYXNzPTNETXNvTm9ybWFsPkp1c3QgY3VyaW91cyB3" + "aGF0IHlvdSYjODIxNztyZSBsb29raW5nIGF0IHNpZ25hdHVyZXMgPQ0KZm9y" + "PzxvOnA+PC9vOnA+PC9wPjxwIGNsYXNzPTNETXNvTm9ybWFsPjxvOnA+Jm5i" + "c3A7PC9vOnA+PC9wPjxwID0NCmNsYXNzPTNETXNvTm9ybWFsPkhvcGUgdGhp" + "cyBoZWxwcyE8bzpwPjwvbzpwPjwvcD48cCA9DQpjbGFzcz0zRE1zb05vcm1h" + "bD5PcmVuPG86cD48L286cD48L3A+PHAgY2xhc3M9M0RNc29Ob3JtYWw+ID0N" + "CjxvOnA+PC9vOnA+PC9wPjwvZGl2PjwvYm9keT48L2h0bWw+DQotLS0tLS09" + "X05leHRQYXJ0XzAwMl8wMDFFXzAxRDM2OEY2LjdDRTk2MzcwLS0NCg0KLS0t" + "LS0tPV9OZXh0UGFydF8wMDFfMDAxRF8wMUQzNjhGNi43Q0U5NjM3MA0KQ29u" + "dGVudC1UeXBlOiB0ZXh0L3BsYWluOw0KCW5hbWU9InRlc3QudHh0Ig0KQ29u" + "dGVudC1UcmFuc2Zlci1FbmNvZGluZzogN2JpdA0KQ29udGVudC1EaXNwb3Np" + "dGlvbjogYXR0YWNobWVudDsNCglmaWxlbmFtZT0idGVzdC50eHQiDQoNCnRl" + "c3QNCi0tLS0tLT1fTmV4dFBhcnRfMDAxXzAwMURfMDFEMzY4RjYuN0NFOTYz" + "NzAtLQ0K", ) var fixtureSmimesignAttachedWithTimestamp = mustBase64Decode("" + "MIIgZQYJKoZIhvcNAQcCoIIgVjCCIFICAQExDTALBglghkgBZQMEAgEwFQYJ" + "KoZIhvcNAQcBoAgEBmhlbGxvCqCCD1UwggVEMIIELKADAgECAhAMLfp+jIxN" + "FhbxAJyYi7cNMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYD" + "VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x" + "JDAiBgNVBAMTG0RpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDQTAeFw0xNzEx" + "MjIwMDAwMDBaFw0yMDExMjIxMjAwMDBaMGUxCzAJBgNVBAYTAlVTMRMwEQYD" + "VQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRUwEwYD" + "VQQKEwxHaXRIdWIsIEluYy4xEjAQBgNVBAMTCUJlbiBUb2V3czCCASIwDQYJ" + "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO9ZN/PY8mMHV/fGVQvEJqlNqQKY" + "keUjZljjsOodUq1g6CjelFgqVjfib+B6yDfUESpi4gD0PK9jODyQ7vC221sS" + "scihYl5BMsBn93bQwy2zFIfyW0lOFuhtpPT6DZHCrqSI+NWbQ4+Wf+braXRv" + "re7nYB7LkbC9Y9n2wq8n3hMxggAI1GcgWi6OqV8FrJKLBgmkYvlBkKOROHSq" + "UsHKx/FPZ9U3B4KvVSIwPR5fcR1M+zvWQ6vpY3iGWbZlklqAjCFX+s6gdwwO" + "Xh5PcW+kRpM2oNTRtohR6xh+pQ631KzS4d3RKKMiJaBVpasVUH206+mtaSxa" + "2Mw9Sm0UBZTnTG0CAwEAAaOCAe4wggHqMB8GA1UdIwQYMBaAFOcCI4AAT9jX" + "vJQL2T90OUkyPIp5MB0GA1UdDgQWBBSRnqSdQlLKpx5J3ytAbMevm7xdbjAM" + "BgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeBFW1hc3RhaHlldGlAZ2l0aHViLmNv" + "bTAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUF" + "BwMEMEMGA1UdIAQ8MDowOAYKYIZIAYb9bAQBAjAqMCgGCCsGAQUFBwIBFhxo" + "dHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGIBgNVHR8EgYAwfjA9oDug" + "OYY3aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3Vy" + "ZWRJRENBLWcyLmNybDA9oDugOYY3aHR0cDovL2NybDQuZGlnaWNlcnQuY29t" + "L0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRENBLWcyLmNybDB5BggrBgEFBQcBAQRt" + "MGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggr" + "BgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0" + "U0hBMkFzc3VyZWRJRENBLmNydDANBgkqhkiG9w0BAQsFAAOCAQEAVouNNiSm" + "K9tUIk6pN5MbwGVTqNcLYzPJAXk1ufZynmlP0mJsyLrdlwLRrhWQUkRiAAAp" + "EWBycg8hAF4h29ZuaLzp4zPL4L/nSjN7wGRwCzQZhkrazfRf24wLpNDWQuYh" + "rot/AsfN56/aUXUZDrLIkTQID+u9qlWVAH/+sb096oTjDULRDlahEzGnNYna" + "gi9X+o1r3zn4drbksjYL1Jb4XBNx3pFXcb3/sFCDYLYgP0k1VdZ7SVWqam7x" + "LD3XCR6hcCACVCIvH1fa/LjNgCCy2M1xa92DTh1SBBzeiMoAGSvcEvA0DPVu" + "Eco2fr8+PANEg55NvpBoacqyIhnsvn9qJTCCBk4wggU2oAMCAQICEASueWBm" + "ZpAaucV/pmxb3M0wDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCVVMxFTAT" + "BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv" + "bTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTEz" + "MTEwNTEyMDAwMFoXDTI4MTEwNTEyMDAwMFowZTELMAkGA1UEBhMCVVMxFTAT" + "BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv" + "bTEkMCIGA1UEAxMbRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENBMIIBIjAN" + "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3PgRIz9qte/AJ3kbLQWHohBD" + "Md8O1BUbT3ekIs4+jHDwvgeO3ScqvAEdtiwKyt1pWB9B7WoFH9pjeFkeIiwr" + "+Lp+yTU7VvEffEJ+JbAjGcZFONc9RPkgfGCuHLBaGAS+jzv3qfCUmqYMY0m2" + "QRdTQDK9T+ZQelAfJUXo8Ymvzf9e/1Dz8BcR/73FifW9YrnY+45FBIVtmc3F" + "SE39JqsCNkXqNtdfauIagkEK3OnZ9ZEXjsYhrTg8E+Yef2ac1U3ZRtr2z1Kn" + "fTskw7TBUTXGm+vU737kewPhRL16CzfgT8uCig1xGOSm4IksG/OyczzBsJKe" + "GH29q33FfQihLMKfcwIDAQABo4IC+DCCAvQwEgYDVR0TAQH/BAgwBgEB/wIB" + "ADAOBgNVHQ8BAf8EBAMCAYYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzAB" + "hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgYEGA1UdHwR6MHgwOqA4oDaG" + "NGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJv" + "b3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp" + "Q2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0lBBYwFAYIKwYBBQUHAwIG" + "CCsGAQUFBwMEMIIBswYDVR0gBIIBqjCCAaYwggGiBgpghkgBhv1sAAIEMIIB" + "kjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCC" + "AWQGCCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQA" + "aABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0" + "AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUA" + "IABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABo" + "AGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0A" + "ZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABp" + "AHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0AGUA" + "ZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjAd" + "BgNVHQ4EFgQU5wIjgABP2Ne8lAvZP3Q5STI8inkwHwYDVR0jBBgwFoAUReui" + "r/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQELBQADggEBAE7UiSe5/R2H" + "d34PKAWQ8QovyTs+vZOckMav+pFRhzJUa+jKwXFRXJmOtfrgYhmZpgeafBMn" + "2+UCooQS2RX2CkRXxDSPbXMfOtagAT3e44LkRWuy6yX9gF4dOZC+W0L2zpFg" + "4/mgVgxIEM4zaHvNk6vwastPWA+5e10bBIGepyLiV0kn7pKTCL5pCFMCOi5d" + "yBn0UIBOAtmwXZG0k4f5lpaBVUCOZu2C2LsoX+1MYe0GWCgZUxFEvEcgKbIE" + "bNiJVJk7ddtneCweknjGVT1YEhEybr1DDE0023vGQtvsvqubYUwGkuOO3yEq" + "UFcEwGCiNdUknmY3CUnP1fhls+DibsIwggO3MIICn6ADAgECAhAM5+DlF9hG" + "/o/lYPwb8DA5MA0GCSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYTAlVTMRUwEwYD" + "VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x" + "JDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0wNjEx" + "MTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGUxCzAJBgNVBAYTAlVTMRUwEwYD" + "VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x" + "JDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTCCASIwDQYJ" + "KoZIhvcNAQEBBQADggEPADCCAQoCggEBAK0OFc7kQ4BcsYfzt2D5cRKlrtwm" + "lIiq9M71IDkoWGAM+IDaqRWVMmE8tbEohIqK3J8KDIMXeo+QrIrneVNcMYQq" + "9g+YMjZ2zN7dPKii72r7IfJSYd+fINcf4rHZ/hhk0hJbX/lYGDW8R82hNvlr" + "f9SwOD7BG8OMM9nYLxj+KA+zp4PWw25EwGE1lhb+WZyLdm3X8aJLDSv/C3La" + "nmDQjpA1xnhVhyChz+VtCshJfDGYM2wi6YfQMlqiuhOCEe05F52ZOnKh5vqk" + "2dUXMXWuhX0irj8BRob2KHnIsdrkVxfEfhwOsLSSplazvbKX7aqn8LfFqD+V" + "FtD/oZbrCF8Yd08CAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB" + "/wQFMAMBAf8wHQYDVR0OBBYEFEXroq/0ksuCMS1Ri6enIZ3zbcgPMB8GA1Ud" + "IwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUAA4IB" + "AQCiDrzf4u3w43JzemSUv/dyZtgy5EJ1Yq6H6/LV2d5Ws5/MzhQouQ2XYFwS" + "TFjk0z2DSUVYlzVpGqhH6lbGeasS2GeBhN9/CTyU5rgmLCC9PbMoifdf/yLi" + "l4Qf6WXvh+DfwWdJs13rsgkq6ybteL59PyvztyY1bV+JAbZJW58BBZurPSXB" + "zLZ/wvFvhsb6ZGjrgS2U60K3+owe3WLxvlBnt2y98/Efaww2BxZ/N3ypW216" + "8RJGYIPXJwS+S86XvsNnKmgR34DnDDNmvxMNFG7zfx9jEB76jRslbWyPpbdh" + "AbHSoyahEHGdreLD+cOZUbcrBwjOLuZQsqf6CkUvovDyMYIQzDCCEMgCAQEw" + "eTBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD" + "VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBTSEEy" + "IEFzc3VyZWQgSUQgQ0ECEAwt+n6MjE0WFvEAnJiLtw0wCwYJYIZIAWUDBAIB" + "oEswLwYJKoZIhvcNAQkEMSIEIFiRtbUi1d8IbQ/wsRD72dIbtPxxY6800IKG" + "ouhG9r4DMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwCwYJKoZIhvcNAQEB" + "BIIBAMQdkVhH1gK8cI2x6BzGW0Vu6IhQcvNqgUacjuzebIBDozpkOKVGYv1/" + "qpmuDLyXEyweI307U5tArvRiWAaZnvjOpmQbdNipbCkjzzUu4tfHtLwTVjLV" + "c6qW9THJWszaqU9rvWAgritkX3mN5AOR5X2Up/hsjMC8SMdwZRHHniRaGB7M" + "IUwVrnnHgxfWUn2FaO85XN6vqsBz0ykI3NIDQGFAIMISX1lKYSBKHOwODvX+" + "Z6aWr2JFVbdcc/hcLi/rbC5x0dlWVdDhREW0HTkZxW0Y37HEyz56d1qpj5II" + "3wJLeoKdGckI7NTwGhY4lcY0lTCd1stsaoGDe9miVAlQfFahgg7bMIIO1wYL" + "KoZIhvcNAQkQAg4xgg7GMIIOwgYJKoZIhvcNAQcCoIIOszCCDq8CAQMxDzAN" + "BglghkgBZQMEAgEFADCBiAYLKoZIhvcNAQkQAQSgeQR3MHUCAQEGCWCGSAGG" + "/WwHATAvMAsGCWCGSAFlAwQCAQQg3oBfclnOJ5THSQ6G1dMX7mo8WnWhN27z" + "2w8+uXPmAHsCEHG548OQys1qPcVJD4482vQYDzIwMTgwOTEzMTQ1OTAyWgIR" + "AN8rYymxrG3grObgUvx8VVygggu7MIIGgjCCBWqgAwIBAgIQCcD8RsgEQhO1" + "WYuvKE9OQTANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UE" + "ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEw" + "LwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5n" + "IENBMB4XDTE3MDEwNDAwMDAwMFoXDTI4MDExODAwMDAwMFowTDELMAkGA1UE" + "BhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSowKAYDVQQDEyFEaWdpQ2VydCBT" + "SEEyIFRpbWVzdGFtcCBSZXNwb25kZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IB" + "DwAwggEKAoIBAQCelZhqNDtzG6h+/Me+KWmJx2gmRl89jWJzh4GjoZzwt1sk" + "N1qS1PRZ13aJ5NzVJ/DVZrwK7rQrMWesWMVKkVkrRR4JAdZks1nujWZN+yNe" + "zBANC4pn71KuoAiQwlL39ai1bpsse53ntT77eM0yUBi/QLVMjLtX9KBPEUVs" + "QkK55a/W3/SnfApolg/SXylXzvsdMv/0EaETIvsSy+/XU9Lrl8uirBsdnVgh" + "UYLCwt7qKz8sIoTQQ+w7Oz9HxPZW3EU3mLRrdLVZr3hXacgPCQJ43dhTwZnb" + "YMSd6q6v4H6GSlypWGGoXnSKAShock6nhp21AlKHcGZI047vgSTM3NhlAgMB" + "AAGjggM4MIIDNDAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNV" + "HSUBAf8EDDAKBggrBgEFBQcDCDCCAb8GA1UdIASCAbYwggGyMIIBoQYJYIZI" + "AYb9bAcBMIIBkjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu" + "Y29tL0NQUzCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAg" + "AG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8A" + "bgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBm" + "ACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAgAGEA" + "bgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBn" + "AHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkA" + "YQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABv" + "AHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUA" + "bgBjAGUALjALBglghkgBhv1sAxUwHwYDVR0jBBgwFoAU9LbhIB3+Ka7S5GGl" + "sqIlssgXNW4wHQYDVR0OBBYEFOGnMkruASEofVTV8geSbrQHDz2HMHEGA1Ud" + "HwRqMGgwMqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFz" + "c3VyZWQtdHMuY3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20v" + "c2hhMi1hc3N1cmVkLXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUF" + "BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0" + "dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVk" + "SURUaW1lc3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggEBAB7wQYIy" + "ru3xtDUT3FDC1ZeuIiKdDg6vM9NM/Xy/bwERp5RlIlzGIqHIiVJrmoxzXNle" + "PzLeFmBMizb9MZkKvcGEt40d74kmEwVW80fNR1uthLI4r2ojtUXjHogyRoDS" + "t6aZIv3BeM/1i9gMjAUJ7kTmgNVtcMyfUx4n3SpI3tqTZa1uZaOZp8JADnPM" + "WE+PRSjlvJyI5ijOYF0tJV2Lcy6lDVtR2ppO/1AFiSja8ni70lh4jUSnrDoA" + "kXhpiWQE012W3yq/+aVMLJP/5ordgqzx0rOihprBVYlWakc/+tYzlUM1iQV4" + "Wjpp2iK4BEPTb2g1NnoUPkXpmGSGDxMMJkowggUxMIIEGaADAgECAhAKoSXW" + "1jIbfkHkBdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUw" + "EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j" + "b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0x" + "NjAxMDcxMjAwMDBaFw0zMTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUw" + "EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j" + "b20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3Rh" + "bXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC90DLu" + "S82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5fU1ofue2oPSNs4jkl79jIZCY" + "vxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb6+NGRwYaVX4LJ37AovWg" + "4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU46gJcWvgzyIQD3XP" + "cXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mIUF79Zm5WYScp" + "iYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfxFwbvPc3W" + "Te8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAdBgNV" + "HQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SS" + "y4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E" + "BAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQG" + "CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUH" + "MAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy" + "ZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0" + "LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4" + "oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJ" + "RFJvb3RDQS5jcmwwUAYDVR0gBEkwRzA4BgpghkgBhv1sAAIEMCowKAYIKwYB" + "BQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJYIZIAYb9" + "bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLpUYdWac3v3dp8qmN6s3jPBjdA" + "hO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQdaq6Z+CeiZr8JqmDfdqQ6" + "kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC4HLHmNY8ZOUfSBAY" + "X4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+tpJn+1Nhiaj1" + "a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6HUSHkWGCb" + "ugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIvIjay" + "S6JKldj1po5SMYICTTCCAkkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV" + "BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEx" + "MC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGlu" + "ZyBDQQIQCcD8RsgEQhO1WYuvKE9OQTANBglghkgBZQMEAgEFAKCBmDAaBgkq" + "hkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTE4MDkx" + "MzE0NTkwMlowLwYJKoZIhvcNAQkEMSIEIFyJ+5SjrJKFITSeJofXvLgWWxPb" + "6ggwDfbdg+klERNxMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFEABkUdcmIkd" + "66EEr0cJG1621MvLMA0GCSqGSIb3DQEBAQUABIIBAJwA/28RMum8YR9Cvx1O" + "N5pk7SlyC1OAA4f+RECrRzrV4TBLkqOeFU+LgCZ4sl9KdyrG+qvEmuy13iAP" + "IAiJC5VY8+WYnmaWLvuO5lt147X5psNAx7xS8ehBywOW3otMqMuy1DaqSCQe" + "oLkUAO/kkVB+X5k2HEUudno3w7pHiNkYWxJ9idgvTPo1E9120fI/pptuvtiK" + "yV7MXWgWWTdZFdyQ9Ig6Ntwt1YvWLNLIw52AmiZp7xPqxj08+8MIruHaUN0u" + "9nEUK+2UxorVSK1IrZkUEObFHVp7lmeINW6tN37esXU8BQzVF+zHd9hbBPIT" + "PWw6BOUQ5LQYHqlrGwfbnlk=", ) func mustBase64Decode(b64 string) []byte { decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(b64)) buf := new(bytes.Buffer) if _, err := io.Copy(buf, decoder); err != nil { panic(err) } return buf.Bytes() } gitsign-0.13.0/internal/fulcio/000077500000000000000000000000001477253552300163515ustar00rootroot00000000000000gitsign-0.13.0/internal/fulcio/fulcioroots/000077500000000000000000000000001477253552300207215ustar00rootroot00000000000000gitsign-0.13.0/internal/fulcio/fulcioroots/fulcioroots.go000066400000000000000000000067611477253552300236320ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcioroots import ( "bytes" "context" "crypto/x509" "errors" "fmt" "os" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/tuf" ) type CertificateSource func() ([]*x509.Certificate, error) // New returns new CertPool(s) with certificates populated by provided CertificateSources. func New(root *x509.CertPool, opts ...CertificateSource) (*x509.CertPool, *x509.CertPool, error) { var intermediate *x509.CertPool certs := []*x509.Certificate{} for _, fn := range opts { c, err := fn() if err != nil { return nil, nil, err } certs = append(certs, c...) } for _, cert := range certs { // root certificates are self-signed if bytes.Equal(cert.RawSubject, cert.RawIssuer) { root.AddCert(cert) } else { if intermediate == nil { intermediate = x509.NewCertPool() } intermediate.AddCert(cert) } } return root, intermediate, nil } func NewFromConfig(ctx context.Context, cfg *config.Config) (*x509.CertPool, *x509.CertPool, error) { src := []CertificateSource{FromTUF(ctx)} if cfg.FulcioRoot != "" { src = []CertificateSource{FromFile(cfg.FulcioRoot)} } return New(x509.NewCertPool(), src...) } const ( // This is the root in the fulcio project. fulcioTargetStr = "fulcio.crt.pem" // This is the v1 migrated root. fulcioV1TargetStr = "fulcio_v1.crt.pem" // This is the untrusted v1 intermediate CA certificate, used or chain building. fulcioV1IntermediateTargetStr = "fulcio_intermediate_v1.crt.pem" ) // FromTUF loads certs from the TUF client. func FromTUF(ctx context.Context) CertificateSource { return func() ([]*x509.Certificate, error) { tufClient, err := tuf.NewFromEnv(ctx) if err != nil { return nil, fmt.Errorf("initializing tuf: %w", err) } // Retrieve from the embedded or cached TUF root. If expired, a network // call is made to update the root. targets, err := tufClient.GetTargetsByMeta(tuf.Fulcio, []string{fulcioTargetStr, fulcioV1TargetStr, fulcioV1IntermediateTargetStr}) if err != nil { return nil, fmt.Errorf("error getting targets: %w", err) } if len(targets) == 0 { return nil, errors.New("none of the Fulcio roots have been found") } certs := []*x509.Certificate{} for _, t := range targets { c, err := cryptoutils.UnmarshalCertificatesFromPEM(t.Target) if err != nil { return nil, fmt.Errorf("error unmarshalling certificates: %w", err) } certs = append(certs, c...) } return certs, nil } } // FromFile loads certs from a PEM file. func FromFile(path string) CertificateSource { return func() ([]*x509.Certificate, error) { b, err := os.ReadFile(path) if err != nil { return nil, err } return cryptoutils.UnmarshalCertificatesFromPEM(b) } } // Static loads a static set of Certificates. func Static(certs ...*x509.Certificate) CertificateSource { return func() ([]*x509.Certificate, error) { return certs, nil } } gitsign-0.13.0/internal/fulcio/fulcioroots/fulcioroots_test.go000066400000000000000000000036511477253552300246640ustar00rootroot00000000000000// // Copyright 2022 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fulcioroots import ( "crypto/x509" "os" "path/filepath" "testing" "github.com/github/smimesign/fakeca" "github.com/sigstore/sigstore/pkg/cryptoutils" ) func TestNew(t *testing.T) { ca := fakeca.New() certpath := filepath.Join(t.TempDir(), "cert.pem") b, err := cryptoutils.MarshalCertificateToPEM(ca.Certificate) if err != nil { t.Fatalf("error marshalling cert: %v", err) } if err := os.WriteFile(certpath, b, 0600); err != nil { t.Fatalf("error writing cert: %v", err) } for _, tc := range []struct { name string opts []CertificateSource root []*x509.Certificate }{ { name: "FromFile", opts: []CertificateSource{FromFile(certpath)}, root: []*x509.Certificate{ca.Certificate}, }, { name: "Static", opts: []CertificateSource{Static(ca.Certificate)}, root: []*x509.Certificate{ca.Certificate}, }, { name: "None", }, // TODO: Figure out how to test TUF locally. } { t.Run(tc.name, func(t *testing.T) { base := x509.NewCertPool() root, _, err := New(base, tc.opts...) if err != nil { t.Fatal(err) } if !root.Equal(certpool(tc.root...)) { t.Errorf("Root CertPool did not match, want: %+v", tc.root) } }) } } func certpool(certs ...*x509.Certificate) *x509.CertPool { pool := x509.NewCertPool() for _, c := range certs { pool.AddCert(c) } return pool } gitsign-0.13.0/internal/fulcio/identity.go000066400000000000000000000200301477253552300205240ustar00rootroot00000000000000// // 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 ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "encoding/pem" "fmt" "io" "net/rpc" "os" "path/filepath" "github.com/sigstore/cosign/v2/pkg/providers" "github.com/sigstore/gitsign/internal/cache" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/gitsign/internal/fulcio/fulcioroots" "github.com/sigstore/gitsign/internal/signerverifier" "github.com/sigstore/gitsign/pkg/fulcio" "github.com/sigstore/sigstore/pkg/oauth" "github.com/sigstore/sigstore/pkg/oauthflow" "github.com/sigstore/sigstore/pkg/signature" "golang.org/x/oauth2" ) // PrivateKey defines the [crypto.PrivateKey] interface. This should be true for all PrivateKeys. type PrivateKey interface { crypto.PrivateKey Public() crypto.PublicKey } type Identity struct { PrivateKey crypto.PrivateKey CertPEM []byte ChainPEM []byte } func NewIdentity(ctx context.Context, cfg *config.Config, in io.Reader, out io.Writer) (*Identity, error) { var cacheClient *cache.Client cachePath := os.Getenv("GITSIGN_CREDENTIAL_CACHE") if cachePath != "" { absPath, err := filepath.Abs(cachePath) if err != nil { return nil, fmt.Errorf("error resolving cache path: %w", err) } rpcClient, err := rpc.Dial("unix", absPath) if err != nil { return nil, fmt.Errorf("error creating RPC socket client: %w", err) } roots, intermediates, err := fulcioroots.NewFromConfig(ctx, cfg) if err != nil { return nil, fmt.Errorf("error loading certificate roots: %w", err) } cacheClient = &cache.Client{ Client: rpcClient, Roots: roots, Intermediates: intermediates, } priv, cert, chain, err := cacheClient.GetCredentials(ctx, cfg) if err == nil { return &Identity{ PrivateKey: priv, CertPEM: cert, ChainPEM: chain, }, nil } // Only print error on failure - if there's a problem fetching // from the cache just fall through to normal OIDC. fmt.Fprintf(out, "error getting cached creds: %v\n", err) } idf := &IdentityFactory{ in: in, out: out, } id, err := idf.NewIdentity(ctx, cfg) if err != nil { return nil, err } if cacheClient != nil { if err := id.CacheCert(ctx, cacheClient); err != nil { fmt.Fprintf(out, "error storing identity in cache: %v\n", err) } } return id, nil } // Certificate gets the identity's certificate. func (i *Identity) Certificate() (*x509.Certificate, error) { p, _ := pem.Decode(i.CertPEM) cert, err := x509.ParseCertificate(p.Bytes) return cert, err } // CertificateChain attempts to get the identity's full certificate chain. func (i *Identity) CertificateChain() ([]*x509.Certificate, error) { p, _ := pem.Decode(i.ChainPEM) chain, err := x509.ParseCertificates(p.Bytes) if err != nil { return nil, err } // the cert itself needs to be appended to the chain cert, err := i.Certificate() if err != nil { return nil, err } return append([]*x509.Certificate{cert}, chain...), nil } // Signer gets a crypto.Signer that uses the identity's private key. func (i *Identity) Signer() (crypto.Signer, error) { sv, err := i.SignerVerifier() if err != nil { return nil, err } s, ok := sv.SignerVerifier.(crypto.Signer) if !ok { return nil, fmt.Errorf("could not use signer %T as crypto.Signer", sv) } return s, nil } // Delete deletes this identity from the system. func (i *Identity) Delete() error { // Does nothing - keys are ephemeral return nil } // Close any manually managed memory held by the Identity. func (i *Identity) Close() { // noop } func (i *Identity) PublicKey() (crypto.PublicKey, error) { pk, ok := i.PrivateKey.(PrivateKey) if !ok { return nil, fmt.Errorf("private key does not implement public key method") } return pk.Public(), nil } func (i *Identity) SignerVerifier() (*signerverifier.CertSignerVerifier, error) { sv, err := signature.LoadSignerVerifier(i.PrivateKey, crypto.SHA256) if err != nil { return nil, fmt.Errorf("error creating SignerVerifier: %w", err) } return &signerverifier.CertSignerVerifier{ SignerVerifier: sv, Cert: i.CertPEM, Chain: i.ChainPEM, }, nil } func (i *Identity) CacheCert(ctx context.Context, cacheClient *cache.Client) error { return cacheClient.StoreCert(ctx, i.PrivateKey, i.CertPEM, i.ChainPEM) } // IdentityFactory holds reusable values for configuring how identities are created. // Values set here are not expected to change per-request. type IdentityFactory struct { in io.Reader out io.Writer } func NewIdentityFactory(in io.Reader, out io.Writer) *IdentityFactory { return &IdentityFactory{ in: in, out: out, } } func (f *IdentityFactory) NewIdentity(ctx context.Context, cfg *config.Config) (*Identity, error) { clientID := cfg.ClientID clientSecret, err := cfg.ClientSecret() if err != nil { return nil, err } // Autoclose only works if we don't go through the identity selection page // (otherwise it'll show a countdown timer that doesn't work) if cfg.ConnectorID == "" { cfg.Autoclose = false } html, err := oauth.GetInteractiveSuccessHTML(cfg.Autoclose, cfg.AutocloseTimeout) if err != nil { fmt.Println("error getting interactive success html, using static default", err) html = oauth.InteractiveSuccessHTML } defaultFlow := &oauthflow.InteractiveIDTokenGetter{ HTMLPage: html, Input: f.in, Output: f.out, } if cfg.ConnectorID != "" { defaultFlow.ExtraAuthURLParams = []oauth2.AuthCodeOption{oauthflow.ConnectorIDOpt(cfg.ConnectorID)} } var authFlow oauthflow.TokenGetter = defaultFlow // If enabled, try OIDC token providers to get a token. unless the token provider is "interactive" (in which case always do default interactive flow). var provider providers.Interface if cfg.TokenProvider == "" && providers.Enabled(ctx) { // If no token provider is set, look for any available provider to use. provider = defaultFlowProvider{} } else if cfg.TokenProvider != "" && cfg.TokenProvider != "interactive" { fmt.Fprintln(f.out, "using token provider", cfg.TokenProvider) // If a token provider is explicitly set always use it, unless it's "interactive", // which means always use the default interactive flow. p, err := providers.ProvideFrom(ctx, cfg.TokenProvider) if err != nil { return nil, fmt.Errorf("error getting token provider %q: %w", cfg.TokenProvider, err) } provider = p } if provider != nil { idToken, err := provider.Provide(ctx, clientID) if err != nil { fmt.Fprintln(f.out, "error getting id token:", err) return nil, fmt.Errorf("error getting id token: %w", err) } authFlow = &oauthflow.StaticTokenGetter{RawToken: idToken} } priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, fmt.Errorf("generating private key: %w", err) } client, err := fulcio.NewClient(cfg.Fulcio, fulcio.OIDCOptions{ Issuer: cfg.Issuer, ClientID: clientID, ClientSecret: clientSecret, RedirectURL: cfg.RedirectURL, TokenGetter: authFlow, }) if err != nil { return nil, fmt.Errorf("error creating Fulcio client: %w", err) } cert, err := client.GetCert(priv) if err != nil { fmt.Fprintln(f.out, "error getting signer:", err) return nil, err } return &Identity{ PrivateKey: priv, CertPEM: cert.CertPEM, ChainPEM: cert.ChainPEM, }, nil } type defaultFlowProvider struct{} func (defaultFlowProvider) Enabled(ctx context.Context) bool { return providers.Enabled(ctx) } func (defaultFlowProvider) Provide(ctx context.Context, audience string) (string, error) { return providers.Provide(ctx, audience) } gitsign-0.13.0/internal/git/000077500000000000000000000000001477253552300156535ustar00rootroot00000000000000gitsign-0.13.0/internal/git/doc.go000066400000000000000000000015301477253552300167460ustar00rootroot00000000000000// 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 git provides higher level funcs for signing and verifying Git // commits. Functions here generally tie together low level signature writing // and Sigstore components together into useful abstractions for working with // Git objects. package git gitsign-0.13.0/internal/git/git.go000066400000000000000000000056151477253552300167740ustar00rootroot00000000000000// // 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 git import ( "bytes" "context" "fmt" "github.com/sigstore/gitsign/internal/fulcio" "github.com/sigstore/gitsign/internal/signature" "github.com/sigstore/gitsign/pkg/git" "github.com/sigstore/gitsign/pkg/rekor" ) type SignFunc func(ctx context.Context, rekor rekor.Writer, ident *fulcio.Identity, data []byte, opts signature.SignOptions) (*signature.SignResponse, error) // Sign signs the commit, uploading a HashedRekord of the commit content to Rekor // and embedding the Rekor log entry in the signature. // This is suitable for offline verification. func Sign(ctx context.Context, rekor rekor.Writer, ident *fulcio.Identity, data []byte, opts signature.SignOptions) (*signature.SignResponse, error) { opts.Rekor = rekor return signature.Sign(ctx, ident, data, opts) } // LegacySHASign is the old-style signing that signs the commit content, but uploads a signed SHA to Rekor. // Verification for this style of signing relies on the Rekor Search API to match the signed SHA + commit content certs, // and cannot be done offline. // This may be removed in the future. func LegacySHASign(ctx context.Context, rekor rekor.Writer, ident *fulcio.Identity, data []byte, opts signature.SignOptions) (*signature.SignResponse, error) { resp, err := signature.Sign(ctx, ident, data, opts) if err != nil { return nil, fmt.Errorf("failed to sign message: %w", err) } // This uploads the commit SHA + sig(commit SHA) to the tlog using the same // key used to sign the commit data itself. // Since the commit SHA ~= hash(commit data + sig(commit data)) and we're // using the same key, this is probably okay? e.g. even if you could cause a SHA1 collision, // you would still need the underlying commit to be valid and using the same key which seems hard. commit, err := git.ObjectHash(data, resp.Signature) if err != nil { return nil, fmt.Errorf("error generating commit hash: %w", err) } sv, err := ident.SignerVerifier() if err != nil { return nil, fmt.Errorf("error getting signer: %w", err) } commitSig, err := sv.SignMessage(bytes.NewBufferString(commit)) if err != nil { return nil, fmt.Errorf("error signing commit hash: %w", err) } resp.LogEntry, err = rekor.Write(ctx, commit, commitSig, resp.Cert) if err != nil { return nil, fmt.Errorf("error uploading tlog (commit): %w", err) } return resp, nil } gitsign-0.13.0/internal/git/gittest/000077500000000000000000000000001477253552300173365ustar00rootroot00000000000000gitsign-0.13.0/internal/git/gittest/testing.go000066400000000000000000000032661477253552300213510ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gittest import ( "io" "os" "testing" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/storage/memory" ) func ParseCommit(t *testing.T, path string) *object.Commit { raw, err := os.ReadFile(path) if err != nil { t.Fatalf("error reading input: %v", err) } storage := memory.NewStorage() obj := storage.NewEncodedObject() obj.SetType(plumbing.CommitObject) w, err := obj.Writer() if err != nil { t.Fatalf("error getting git object writer: %v", err) } if _, err := w.Write(raw); err != nil { t.Fatalf("error writing git commit: %v", err) } c, err := object.DecodeCommit(storage, obj) if err != nil { t.Fatalf("error decoding commit: %v", err) } return c } func MarshalCommitBody(t *testing.T, commit *object.Commit) []byte { t.Helper() storage := memory.NewStorage() obj := storage.NewEncodedObject() if err := commit.EncodeWithoutSignature(obj); err != nil { t.Fatal(err) } r, err := obj.Reader() if err != nil { t.Fatal(err) } body, err := io.ReadAll(r) if err != nil { t.Fatal(err) } return body } gitsign-0.13.0/internal/gitsign/000077500000000000000000000000001477253552300165345ustar00rootroot00000000000000gitsign-0.13.0/internal/gitsign/gitsign.go000066400000000000000000000110021477253552300205210ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gitsign import ( "context" "crypto/x509" "fmt" "os" cosignopts "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/gitsign/internal/cert" "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/gitsign/internal/fulcio/fulcioroots" rekorinternal "github.com/sigstore/gitsign/internal/rekor" "github.com/sigstore/gitsign/pkg/git" "github.com/sigstore/gitsign/pkg/rekor" "github.com/sigstore/sigstore/pkg/cryptoutils" ) type Verifier struct { git git.Verifier cert cert.Verifier rekor rekor.Verifier } // NewVerifierWithCosignOpts implements a Gitsign verifier using Cosign CertVerifyOptions. // Note: not all options are supported. // - cert: This is always taken from the commit. func NewVerifierWithCosignOpts(ctx context.Context, cfg *config.Config, opts *cosignopts.CertVerifyOptions) (*Verifier, error) { root, intermediate, err := fulcioroots.NewFromConfig(ctx, cfg) if err != nil { return nil, fmt.Errorf("error getting certificate root: %w", err) } tsa, err := x509.SystemCertPool() if err != nil { return nil, fmt.Errorf("error getting system root pool: %w", err) } if path := cfg.TimestampCert; path != "" { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() cert, err := cryptoutils.LoadCertificatesFromPEM(f) if err != nil { return nil, fmt.Errorf("error loading certs from %s: %w", path, err) } for _, c := range cert { tsa.AddCert(c) } } gitverifier, err := git.NewCertVerifier( git.WithRootPool(root), git.WithIntermediatePool(intermediate), git.WithTimestampCertPool(tsa), ) if err != nil { return nil, fmt.Errorf("error creating Git verifier: %w", err) } rekor, err := rekorinternal.NewClientContext(ctx, cfg.Rekor) if err != nil { return nil, fmt.Errorf("failed to create rekor client: %w", err) } // Optionally include cert.Verifier. // This needs to be optional because when verifying with // `git verify-commit` we don't have access to issuer / identity details. // In these cases, clients should look for the certificate validated claim // and warn if missing. var certverifier cert.Verifier if opts != nil { ctpub, err := cosign.GetCTLogPubs(ctx) if err != nil { return nil, fmt.Errorf("error getting CT log public key: %w", err) } identities, err := opts.Identities() if err != nil { return nil, fmt.Errorf("error parsing identities: %w", err) } certverifier = cert.NewCosignVerifier(&cosign.CheckOpts{ RekorClient: rekor.Rekor, RootCerts: root, IntermediateCerts: intermediate, CTLogPubKeys: ctpub, RekorPubKeys: rekor.PublicKeys(), CertGithubWorkflowTrigger: opts.CertGithubWorkflowTrigger, CertGithubWorkflowSha: opts.CertGithubWorkflowSha, CertGithubWorkflowName: opts.CertGithubWorkflowName, CertGithubWorkflowRepository: opts.CertGithubWorkflowRepository, CertGithubWorkflowRef: opts.CertGithubWorkflowRef, Identities: identities, IgnoreSCT: opts.IgnoreSCT, }) } return &Verifier{ git: gitverifier, cert: certverifier, rekor: rekor, }, nil } func (v *Verifier) Verify(ctx context.Context, data []byte, sig []byte, detached bool) (*git.VerificationSummary, error) { // TODO: we probably want to deprecate git.Verify in favor of this struct. summary, err := git.Verify(ctx, v.git, v.rekor, data, sig, detached) if err != nil { return summary, err } if v.cert != nil { if err := v.cert.Verify(summary.Cert); err != nil { summary.Claims = append(summary.Claims, git.NewClaim(git.ClaimValidatedCerificate, false)) return summary, err } summary.Claims = append(summary.Claims, git.NewClaim(git.ClaimValidatedCerificate, true)) } else { summary.Claims = append(summary.Claims, git.NewClaim(git.ClaimValidatedCerificate, false)) } return summary, nil } gitsign-0.13.0/internal/gitsign/gitsign_test.go000066400000000000000000000105631477253552300215730ustar00rootroot00000000000000// Copyright 2023 The Sigstore Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gitsign import ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "io" "math/big" "testing" "time" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/storage/memory" "github.com/sigstore/cosign/v2/pkg/cosign" certverifier "github.com/sigstore/gitsign/internal/cert" "github.com/sigstore/gitsign/internal/signature" "github.com/sigstore/gitsign/pkg/git" "github.com/sigstore/rekor/pkg/generated/models" ) func TestVerify(t *testing.T) { ctx := context.Background() // Generate cert cert, priv := generateCert(t, &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: "tacocat", }, EmailAddresses: []string{"tacocat@example.com"}, ExtraExtensions: []pkix.Extension{{ Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, Value: []byte("example.com"), }}, NotBefore: time.Now(), NotAfter: time.Now().Add(5 * time.Minute), }) // Git verifier roots := x509.NewCertPool() roots.AddCert(cert) gv, err := git.NewCertVerifier(git.WithRootPool(roots)) if err != nil { t.Fatalf("error creating git verifer: %v", err) } // Cert verifier cv := certverifier.NewCosignVerifier(&cosign.CheckOpts{ RootCerts: roots, Identities: []cosign.Identity{{ Issuer: "example.com", Subject: "tacocat@example.com", }}, IgnoreSCT: true, IgnoreTlog: true, }) // Rekor verifier - we don't have a good way to test this right now so mock it out. rekor := fakeRekor{} v := Verifier{ git: gv, cert: cv, rekor: rekor, } data, sig := generateData(t, cert, priv) if _, err := v.Verify(ctx, data, sig, true); err != nil { t.Fatal(err) } } func generateCert(t *testing.T, tmpl *x509.Certificate) (*x509.Certificate, *ecdsa.PrivateKey) { t.Helper() priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) if err != nil { t.Fatalf("error generating private key: %v", err) } pub := &priv.PublicKey raw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) if err != nil { t.Fatalf("error generating certificate: %v", err) } cert, err := x509.ParseCertificate(raw) if err != nil { t.Fatalf("ParseCertificate: %v", err) } return cert, priv } func generateData(t *testing.T, cert *x509.Certificate, priv crypto.Signer) ([]byte, []byte) { t.Helper() ctx := context.Background() // Generate commit data commit := object.Commit{ Message: "hello world!", } obj := memory.NewStorage().NewEncodedObject() if err := commit.Encode(obj); err != nil { t.Fatal(err) } reader, err := obj.Reader() if err != nil { t.Fatal(err) } data, err := io.ReadAll(reader) if err != nil { t.Fatalf("error reading git data: %v", err) } id := &identity{ cert: cert, priv: priv, } resp, err := signature.Sign(ctx, id, data, signature.SignOptions{ Detached: true, Armor: true, // Fake CA outputs self-signed certs, so we need to use -1 to make sure // the self-signed cert itself is included in the chain, otherwise // Verify cannot find a cert to use for verification. IncludeCerts: 0, }) if err != nil { t.Fatalf("Sign() = %v", err) } return data, resp.Signature } type fakeRekor struct{} func (fakeRekor) Verify(_ context.Context, _ string, _ *x509.Certificate) (*models.LogEntryAnon, error) { return nil, nil } func (fakeRekor) VerifyInclusion(_ context.Context, _ []byte, _ *x509.Certificate) (*models.LogEntryAnon, error) { return nil, nil } type identity struct { signature.Identity cert *x509.Certificate priv crypto.Signer } func (i *identity) Certificate() (*x509.Certificate, error) { return i.cert, nil } func (i *identity) CertificateChain() ([]*x509.Certificate, error) { return []*x509.Certificate{i.cert}, nil } func (i *identity) Signer() (crypto.Signer, error) { return i.priv, nil } gitsign-0.13.0/internal/gpg/000077500000000000000000000000001477253552300156455ustar00rootroot00000000000000gitsign-0.13.0/internal/gpg/status.go000066400000000000000000000174731477253552300175330ustar00rootroot00000000000000// // 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 gpg import ( "crypto" "crypto/x509" "fmt" "io" "os" "time" // TODO: this package is unmaintained except for security fixes. // New applications should consider a more focused, modern alternative to OpenPGP for their specific task. // If you are required to interoperate with OpenPGP systems and need a maintained package, consider a community fork. // See https://golang.org/issue/44226 "golang.org/x/crypto/openpgp/packet" //nolint: staticcheck "golang.org/x/crypto/openpgp/s2k" //nolint: staticcheck "github.com/sigstore/gitsign/internal" ) // This file implements gnupg's "status protocol". When the --status-fd argument // is passed, gpg will output machine-readable status updates to that fd. // Details on the "protocol" can be found at https://git.io/vFFKC type Status string const ( // BEGIN_SIGNING // Mark the start of the actual signing process. This may be used as an // indication that all requested secret keys are ready for use. StatusBeginSigning Status = "BEGIN_SIGNING" // SIG_CREATED // A signature has been created using these parameters. // Values for type are: // - D :: detached // - C :: cleartext // - S :: standard // (only the first character should be checked) // // are 2 hex digits with the OpenPGP signature class. // // Note, that TIMESTAMP may either be a number of seconds since Epoch // or an ISO 8601 string which can be detected by the presence of the // letter 'T'. StatusSigCreated Status = "SIG_CREATED" // NEWSIG [] // Is issued right before a signature verification starts. This is // useful to define a context for parsing ERROR status messages. // arguments are currently defined. If SIGNERS_UID is given and is // not "-" this is the percent escape value of the OpenPGP Signer's // User ID signature sub-packet. StatusNewSig Status = "NEWSIG" // GOODSIG // The signature with the keyid is good. For each signature only one // of the codes GOODSIG, BADSIG, EXPSIG, EXPKEYSIG, REVKEYSIG or // ERRSIG will be emitted. In the past they were used as a marker // for a new signature; new code should use the NEWSIG status // instead. The username is the primary one encoded in UTF-8 and %XX // escaped. The fingerprint may be used instead of the long keyid if // it is available. This is the case with CMS and might eventually // also be available for OpenPGP. StatusGoodSig Status = "GOODSIG" // BADSIG // The signature with the keyid has not been verified okay. The username is // the primary one encoded in UTF-8 and %XX escaped. The fingerprint may be // used instead of the long keyid if it is available. This is the case with // CMS and might eventually also be available for OpenPGP. StatusBadSig Status = "BADSIG" // ERRSIG