From ec8f2d403e07a392ea363560d21c31aaee57ba0f Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Fri, 10 Jun 2022 09:52:23 -0400 Subject: [PATCH] Move fulcioroots and tuf packages from cosign (#435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add method to unmarshal certificates with a limit (#430) * Add method to unmarshal certificates with a limit This removes a DOS vector for services that use this method. Otherwise, a client can provide a large PEM block to cause the service to do a significant amount of work. Signed-off-by: Hayden Blauzvern * Add suggested iteration limit Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Add unsafe verifier to verify signatures with SHA1 digests (#441) I relaxed the hash function constraints on VerifyMessage, including the SHA1 digest as a supported function. The expectation is that LoadVerifier will still be the primary way to set up a verifier, which will enforce the hash function. Otherwise, LoadUnsafeVerifier will be used to load a verifier that only supports SHA1. Note that SignMessage will not support SHA1 still. I also dropped SHA1 from ECDSA's supported hash functions. Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.12 to 1.44.13 (#440) Signed-off-by: Jason Hall * Bump github/codeql-action (#439) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 75b4f1c4669133dc294b06c2794e969efa2e5316 to 2.1.10. This release includes the previously tagged commit. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/75b4f1c4669133dc294b06c2794e969efa2e5316...2f58583a1b24a7d3c7034f6bf9fa506d23b1183b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump actions/setup-go from 3.0.0 to 3.1.0 (#438) * Bump actions/setup-go from 3.0.0 to 3.1.0 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/f6164bd8c8acb4a71fb2791a8b6c4024ff038dab...fcdc43634adb5f7ae75a9d7a9b9361790f7293e2) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * update version comments Signed-off-by: cpanato Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: cpanato Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.13 to 1.44.14 (#443) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.13 to 1.44.14. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.13...v1.44.14) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump actions/dependency-review-action (#442) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 3f943b86c9a289f4e632c632695e2e0898d9d67d to 1. This release includes the previously tagged commit. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/3f943b86c9a289f4e632c632695e2e0898d9d67d...39e692fa323107ef86d8fdac0067ce647f239bd7) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Remove dependency on deprecated github.com/pkg/errors (#444) * Remove dependency on deprecated github.com/pkg/errors Signed-off-by: Jason Hall * appease linter Signed-off-by: Jason Hall * fix AWS KMS test Signed-off-by: Jason Hall * Bump google-github-actions/auth from 0.7.1 to 0.7.2 (#446) Bumps [google-github-actions/auth](https://github.com/google-github-actions/auth) from 0.7.1 to 0.7.2. - [Release notes](https://github.com/google-github-actions/auth/releases) - [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md) - [Commits](https://github.com/google-github-actions/auth/compare/b258a9f230b36c9fa86dfaa43d1906bd76399edb...dafc92490a98acbdec38e6eb649f05d55e632447) --- updated-dependencies: - dependency-name: google-github-actions/auth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.14 to 1.44.15 (#447) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.14 to 1.44.15. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.14...v1.44.15) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/Azure/azure-sdk-for-go (#445) Bumps [github.com/Azure/azure-sdk-for-go](https://github.com/Azure/azure-sdk-for-go) from 64.0.0+incompatible to 64.1.0+incompatible. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/v64.0.0...v64.1.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github/codeql-action from 2.1.10 to 2.1.11 (#448) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.1.10 to 2.1.11. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/2f58583a1b24a7d3c7034f6bf9fa506d23b1183b...a3a6c128d771b6b9bdebb1c9d0583ebd2728a108) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.15 to 1.44.16 (#449) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.15 to 1.44.16. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.15...v1.44.16) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/go-rod/rod from 0.106.6 to 0.106.7 (#450) Bumps [github.com/go-rod/rod](https://github.com/go-rod/rod) from 0.106.6 to 0.106.7. - [Release notes](https://github.com/go-rod/rod/releases) - [Commits](https://github.com/go-rod/rod/compare/v0.106.6...v0.106.7) --- updated-dependencies: - dependency-name: github.com/go-rod/rod dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/google/go-containerregistry from 0.8.0 to 0.9.0 (#451) Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.8.0 to 0.9.0. - [Release notes](https://github.com/google/go-containerregistry/releases) - [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml) - [Commits](https://github.com/google/go-containerregistry/compare/v0.8.0...v0.9.0) --- updated-dependencies: - dependency-name: github.com/google/go-containerregistry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.16 to 1.44.17 (#453) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.16 to 1.44.17. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.16...v1.44.17) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump google-github-actions/auth from 0.7.2 to 0.7.3 (#452) Bumps [google-github-actions/auth](https://github.com/google-github-actions/auth) from 0.7.2 to 0.7.3. - [Release notes](https://github.com/google-github-actions/auth/releases) - [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md) - [Commits](https://github.com/google-github-actions/auth/compare/dafc92490a98acbdec38e6eb649f05d55e632447...81012c2689e66f7f020ed6d8ab43596a0f8b503a) --- updated-dependencies: - dependency-name: google-github-actions/auth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/go-rod/rod from 0.106.7 to 0.106.8 (#454) Bumps [github.com/go-rod/rod](https://github.com/go-rod/rod) from 0.106.7 to 0.106.8. - [Release notes](https://github.com/go-rod/rod/releases) - [Commits](https://github.com/go-rod/rod/compare/v0.106.7...v0.106.8) --- updated-dependencies: - dependency-name: github.com/go-rod/rod dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump actions/upload-artifact from 3.0.0 to 3.1.0 (#456) * Bump actions/upload-artifact from 3.0.0 to 3.1.0 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/6673cd052c4cd6fcf4b4e6e60ea986c889389535...3cea5372237819ed00197afe530f5a7ea3e805c8) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * update version comment Signed-off-by: cpanato Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: cpanato Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.17 to 1.44.18 (#455) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.17 to 1.44.18. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.17...v1.44.18) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.18 to 1.44.19 (#457) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.18 to 1.44.19. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.18...v1.44.19) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.19 to 1.44.20 (#461) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.19 to 1.44.20. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.19...v1.44.20) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/Azure/azure-sdk-for-go (#460) Bumps [github.com/Azure/azure-sdk-for-go](https://github.com/Azure/azure-sdk-for-go) from 64.1.0+incompatible to 65.0.0+incompatible. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/v64.1.0...v65.0.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump actions/dependency-review-action from 1.0.1 to 1.0.2 (#459) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 1.0.1 to 1.0.2. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/39e692fa323107ef86d8fdac0067ce647f239bd7...a9c83d3af6b9031e20feba03b904645bb23d1dab) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump google-github-actions/auth from 0.7.3 to 0.8.0 (#458) * Bump google-github-actions/auth from 0.7.3 to 0.8.0 Bumps [google-github-actions/auth](https://github.com/google-github-actions/auth) from 0.7.3 to 0.8.0. - [Release notes](https://github.com/google-github-actions/auth/releases) - [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md) - [Commits](https://github.com/google-github-actions/auth/compare/81012c2689e66f7f020ed6d8ab43596a0f8b503a...ceee102ec2387dd9e844e01b530ccd4ec87ce955) --- updated-dependencies: - dependency-name: google-github-actions/auth dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * update version comment Signed-off-by: cpanato Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: cpanato Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.20 to 1.44.21 (#464) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.20 to 1.44.21. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.20...v1.44.21) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/hashicorp/vault/api from 1.5.0 to 1.6.0 (#463) Bumps [github.com/hashicorp/vault/api](https://github.com/hashicorp/vault) from 1.5.0 to 1.6.0. - [Release notes](https://github.com/hashicorp/vault/releases) - [Changelog](https://github.com/hashicorp/vault/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/vault/compare/v1.5.0...v1.6.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/vault/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.21 to 1.44.22 (#465) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.21 to 1.44.22. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.21...v1.44.22) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Update go-tuf to pick up security fixes (#462) Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Export providerInit type (#466) Also remove unnecessary providerMux indirection, and just use a package-level var directly. Signed-off-by: Jason Hall * Bump actions/setup-go from 3.1.0 to 3.2.0 (#469) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/fcdc43634adb5f7ae75a9d7a9b9361790f7293e2...b22fbbc2921299758641fab08929b4ac52b32923) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.22 to 1.44.23 (#470) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.22 to 1.44.23. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.22...v1.44.23) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/go-rod/rod from 0.106.8 to 0.107.0 (#471) Bumps [github.com/go-rod/rod](https://github.com/go-rod/rod) from 0.106.8 to 0.107.0. - [Release notes](https://github.com/go-rod/rod/releases) - [Commits](https://github.com/go-rod/rod/compare/v0.106.8...v0.107.0) --- updated-dependencies: - dependency-name: github.com/go-rod/rod dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * update error message (#473) Signed-off-by: cpanato Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.23 to 1.44.24 (#474) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.23 to 1.44.24. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.23...v1.44.24) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Allow passing options to GCP's `LoadSignVerifier`. (#468) This lets the caller control authentication, in particular by providing an `option.TokenSource`. Signed-off-by: Matt Moore Signed-off-by: Jason Hall * Migrate AWK KMS to use the v2 SDK. (#475) Looking at doing something similar to https://github.com/sigstore/sigstore/pull/468 for AWS, I noticed that our KMS stuff was using the old SDK. The bulk of this change is migrating things to the v2 SDK, but this also exposes a way to plumb through options to `LoadSignerVerified` similar to #468 for GCP. Signed-off-by: Matt Moore Signed-off-by: Jason Hall * Bump google.golang.org/api from 0.75.0 to 0.81.0 (#476) Signed-off-by: Jason Hall * fix uppercase err msgs to quiet golangci-lint (#477) * fix uppercase err msgs to quiet golangci-lint Signed-off-by: Bob Callaway * fix test case compares Signed-off-by: Bob Callaway * always complain about known lint issues Signed-off-by: Bob Callaway Signed-off-by: Jason Hall * Bump actions/cache from 3.0.2 to 3.0.3 (#478) Bumps [actions/cache](https://github.com/actions/cache) from 3.0.2 to 3.0.3. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/48af2dc4a9e8278b89d7fa154b955c30c6aaab09...30f413bfed0a2bc738fdfd409e5a9e96b24545fd) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/secure-systems-lab/go-securesystemslib (#482) Bumps [github.com/secure-systems-lab/go-securesystemslib](https://github.com/secure-systems-lab/go-securesystemslib) from 0.3.1 to 0.4.0. - [Release notes](https://github.com/secure-systems-lab/go-securesystemslib/releases) - [Commits](https://github.com/secure-systems-lab/go-securesystemslib/compare/v0.3.1...v0.4.0) --- updated-dependencies: - dependency-name: github.com/secure-systems-lab/go-securesystemslib dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.24 to 1.44.26 (#481) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.24 to 1.44.26. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.24...v1.44.26) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github/codeql-action from 2.1.11 to 2.1.12 (#480) * Bump github/codeql-action from 2.1.11 to 2.1.12 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.1.11 to 2.1.12. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/a3a6c128d771b6b9bdebb1c9d0583ebd2728a108...27ea8f8fe5977c00f5b37e076ab846c5bd783b96) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * update version comment Signed-off-by: cpanato Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: cpanato Signed-off-by: Jason Hall * Bump google.golang.org/api from 0.81.0 to 0.82.0 (#483) Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.81.0 to 0.82.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.81.0...v0.82.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Autoclose OAuth success page after 5 seconds. (#484) Small QoL improvement to clean up success pages after they are no longer needed. Shout out to @bobcallaway for the idea! Co-authored-by: Bob Callaway Signed-off-by: Billy Lynch Co-authored-by: Bob Callaway Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.26 to 1.44.27 (#485) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.26 to 1.44.27. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.26...v1.44.27) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Add a warning when using WithDigest with ECDSA (#487) Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Bump github.com/stretchr/testify from 1.7.1 to 1.7.2 (#489) Signed-off-by: Jason Hall * Bump github.com/go-rod/rod from 0.107.0 to 0.107.1 (#488) Signed-off-by: Jason Hall * Bump google.golang.org/api from 0.82.0 to 0.83.0 (#495) Bumps [google.golang.org/api](https://github.com/googleapis/google-api-go-client) from 0.82.0 to 0.83.0. - [Release notes](https://github.com/googleapis/google-api-go-client/releases) - [Changelog](https://github.com/googleapis/google-api-go-client/blob/main/CHANGES.md) - [Commits](https://github.com/googleapis/google-api-go-client/compare/v0.82.0...v0.83.0) --- updated-dependencies: - dependency-name: google.golang.org/api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go-v2 from 1.16.4 to 1.16.5 (#491) Bumps [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) from 1.16.4 to 1.16.5. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.16.4...v1.16.5) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go-v2/config from 1.15.9 to 1.15.10 (#494) Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.15.9 to 1.15.10. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.15.9...config/v1.15.10) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go-v2/service/kms from 1.17.2 to 1.17.3 (#493) Bumps [github.com/aws/aws-sdk-go-v2/service/kms](https://github.com/aws/aws-sdk-go-v2) from 1.17.2 to 1.17.3. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/ecr/v1.17.2...service/ecr/v1.17.3) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/kms dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Bump actions/cache from 3.0.3 to 3.0.4 (#490) * Bump actions/cache from 3.0.3 to 3.0.4 Bumps [actions/cache](https://github.com/actions/cache) from 3.0.3 to 3.0.4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/30f413bfed0a2bc738fdfd409e5a9e96b24545fd...c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * update version comment Signed-off-by: cpanato Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: cpanato Signed-off-by: Jason Hall * Bump github.com/aws/aws-sdk-go from 1.44.27 to 1.44.29 (#492) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.27 to 1.44.29. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.27...v1.44.29) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jason Hall * Add `cosign init` to initialize the SigStore root metadata (#520) * verify TUF root Signed-off-by: Asra Ali * use tuf root for rekor and fulcio data Signed-off-by: Asra Ali * add local tests and configs Signed-off-by: Asra Ali * update Signed-off-by: Asra Ali * update gcs bucket to prod Signed-off-by: Asra Ali Signed-off-by: Jason Hall * Explicitly disable auth for the sigstore-tuf-root. (#528) I had expired credentials that were causing this to fail. The bucket is public, so we should just not use auth (which apparently requires being explicit). Signed-off-by: Dan Lorenc Signed-off-by: Jason Hall * 'cosign init' minor enhancements (file or URL root, write to $HOME/.sigstore) (#530) * make minor changes to cosign init Signed-off-by: Asra Ali * use gcs root Signed-off-by: Asra Ali * also pin sha Signed-off-by: Asra Ali * embed initial root Signed-off-by: Asra Ali * remove sha because of embedded root Signed-off-by: Asra Ali Signed-off-by: Jason Hall * chore: enable whitespace check on golangci-lint and organize imports (#687) Signed-off-by: Carlos Panato Signed-off-by: Jason Hall * Add a policy-init using TUF metadata and Fulcio signers (#469) * add policy init with tuf Signed-off-by: Asra Ali * update go-tuf to my local fork for ease Signed-off-by: Asra Ali * clean up Signed-off-by: Asra Ali * add subcommand Signed-off-by: Asra Ali Signed-off-by: Jason Hall * [root policy] Add root policy signing (#856) * add root policy signing Signed-off-by: Asra Ali * b64 encode Signed-off-by: Asra Ali Signed-off-by: Jason Hall * Remove the preallocation of signatures slice. (#869) This was making codeql upset. I don't think there's a real issue, but better safe than sorry. Signed-off-by: Dan Lorenc Signed-off-by: Jason Hall * update root ux (#747) Signed-off-by: Asra Ali Signed-off-by: Jason Hall * add optional issuer to root policy (#999) Signed-off-by: Asra Ali Signed-off-by: Jason Hall * Update slsa-provenance predicate to v0.2 (#1054) * Update slsa-provenance to v0.2 This dep update also required updating the go-tuf dependency, so there are some bug fixes in the go-tuf code in this PR as well. Signed-off-by: Priya Wadhwa * Remove newlines from targets so that they match expected targets Signed-off-by: Priya Wadhwa Signed-off-by: Jason Hall * Add Fulcio v1 root to the cosign (#1112) * add fulcio v1 root Signed-off-by: Asra Ali * remove unneeded todo Signed-off-by: Asra Ali Signed-off-by: Jason Hall * cjson - Move to go-securesystemslib (#1141) The existing cjson hasn't been maintained. The last update was 9 years ago. This was replaced by the upstream go-securesystemslib https://github.com/secure-systems-lab/go-securesystemslib/pull/10 Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com> Signed-off-by: Jason Hall * return error when rekor pub cannot be retrieved, fix file path construction (#1157) Signed-off-by: Jake Sanders Signed-off-by: Jason Hall * expand CI testing to Windows and OSX, fix issues uncovered (#1158) * Also run unit and secretless e2e tests on OSX * run e2e-tests-with-binary on OSX and Windows * run unit tests on all 3 supported OSes * add `-race` unit tests * `os.Open` -> `os.Stat` for checking file existence * `path.Join` -> `filepath.Join` * simplify `getLocalTarget` * always `Close()` `localTarget` * embed everything in the repository directory * always use `/` as path divider in embedded fs * `path` -> `localCacheDBPath` * assorted improvements in `RootClient` * ensure `remote` is non-nil * fix one straggler call to `filepath.Join` * add `requireCoherence` option * fix fatal memory leak in test * create `embedded{Read, Open}File()` helpers * add link to issue #1160 in TODO * add comments for require coherence Signed-off-by: Jake Sanders Signed-off-by: Jason Hall * use `sync.Once` to init the global tuf root (#1163) Signed-off-by: Jake Sanders Signed-off-by: Jason Hall * update go-tuf and use the newly exposed `Close()` (#1181) Signed-off-by: Jake Sanders Signed-off-by: Jason Hall * Remove the "upload" flag for "cosign initialize" (#1201) The "upload" flag is not used anywhere and it is not really needed. When we update from the remote TUF repo, we expect the same number of root signatures (or more) which is a sensible default. Closes: #1195 Signed-off-by: Radoslav Gerganov Signed-off-by: Jason Hall * update snapshot and timestamp (#1211) Signed-off-by: Asra Ali Signed-off-by: Jason Hall * Spelling (#1246) * spelling: abstractions Signed-off-by: Josh Soref * spelling: annotations Signed-off-by: Josh Soref * spelling: announcement Signed-off-by: Josh Soref * spelling: attached Signed-off-by: Josh Soref * spelling: attachment Signed-off-by: Josh Soref * spelling: attestation Signed-off-by: Josh Soref * spelling: cloudbuild Signed-off-by: Josh Soref * spelling: compatibility Signed-off-by: Josh Soref * spelling: consideration Signed-off-by: Josh Soref * spelling: constituent Signed-off-by: Josh Soref * spelling: dekkagaijin Signed-off-by: Josh Soref * spelling: dependabot Signed-off-by: Josh Soref * spelling: environment Signed-off-by: Josh Soref * spelling: github Signed-off-by: Josh Soref * spelling: gitlab Signed-off-by: Josh Soref * spelling: immutable Signed-off-by: Josh Soref * spelling: include Signed-off-by: Josh Soref * spelling: initialized Signed-off-by: Josh Soref * spelling: mailing Signed-off-by: Josh Soref * spelling: payloads Signed-off-by: Josh Soref * spelling: percent Signed-off-by: Josh Soref * spelling: setting Signed-off-by: Josh Soref * spelling: sigstore Signed-off-by: Josh Soref * spelling: stored Signed-off-by: Josh Soref * spelling: validity Signed-off-by: Josh Soref * spelling: verified Signed-off-by: Josh Soref * spelling: verifier Signed-off-by: Josh Soref * spelling: without Signed-off-by: Josh Soref Co-authored-by: Josh Soref Signed-off-by: Jason Hall * Update the embedded TUF metadata. (#1251) The rekor.json and staging.json files weren't in here before. Signed-off-by: Dan Lorenc Signed-off-by: Jason Hall * Refactor the tuf client code. (#1252) This is my attempt at refactoring the TUF client code to better support the configuration modes we've recently added. This also adds support for SIGSTORE_NO_CACHE, and eliminates most writes to disk from cosign outside of cosign initialize. I think these tests are about equivalent to what we had before, if not a bit better. The coverage is at 72% and hits most non-sporadic errors. Signed-off-by: Dan Lorenc Signed-off-by: Jason Hall * Fix the unit tests with expired TUF metadata. (#1270) These tests worked by mocking at the "isExpired" level. When the real files ARE expired, but we mock them to be NOT expired, the code continues down a path it shouldn't and fails later, trying to use expired metadata. We should fix this "better" by generating real expired and unexpired metadata, or changing the system clock somehow. Signed-off-by: Dan Lorenc Signed-off-by: Jason Hall * Fix a few bugs in cosign initialize (#1280) * In getRoot, the metadata is always stored at the top level, not under repository/. * In Initialize, download all metadata and targets. This should avoid a disk write on verify. * Use path instead of filepath for Windows Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * add error message (#1296) Signed-off-by: Asra Ali Signed-off-by: Asra Ali Signed-off-by: Jason Hall * Bundle TUF timestamp with signature on signing (#1294) * Bundle TUF timestamp with signature on signing This updates the code to support adding the TUF timestamp to the OCI signature. Changes to pkg/oci add support for reading and saving the timestamp by annotation key. Changes to the TUF client add putting the timestamp in memory on client initialization, so callers can access the timestamp. Signed-off-by: Hayden Blauzvern * Add TUF timestamp to OCI signature on sign This adds the TUF timestamp to the Fulcio and Rekor signers. Both are necessary since each relies on TUF metadata. If both signers are used, the latter one will overwrite the TUF timestamp. I also added a basic mock Rekor client for tests. A number of methods are not implemented yet. Signed-off-by: Hayden Blauzvern * Add license Signed-off-by: Hayden Blauzvern * Move timestamp to TUF package Signed-off-by: Hayden Blauzvern * Update TUF client to persist local store Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Bump the snapshot and timestamp roles metadata from root signing. (#1339) Signed-off-by: Dan Lorenc Signed-off-by: Jason Hall * Cache the location of the remote repository when running cosign initialize (#1315) * store remote Signed-off-by: Asra Ali Signed-off-by: Asra Ali * add test Signed-off-by: Asra Ali * use json struct for cached remote info Signed-off-by: Asra Ali * update lint Signed-off-by: Asra Ali * update Signed-off-by: Asra Ali Signed-off-by: Jason Hall * add root status output (#1404) Signed-off-by: Asra Ali Signed-off-by: Jason Hall * Remove TUF timestamp from OCI signature bundle (#1428) As described in #1273, this solution does not work because the TUF root is not included in the snapshot. Removing unused code. Confirmed that verifying images with a timestamp annotation still works. Confimed that signing and verifying works locally too. Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Fetch verification targets by TUF custom metadata (#1423) * Add TUF client method for fetching by metadata Signed-off-by: Hayden Blauzvern * Fetch verification targets by TUF custom metadata This uses GetTargetsByMeta to read the targets using the custom metadata, or fallback to the old targets by filename. Signed-off-by: Hayden Blauzvern * Resolve PR comments, linter, and update tests Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * update go-tuf and simplify TUF client code (#1455) * update go tuf and simplify code Signed-off-by: Asra Ali * add commend Signed-off-by: Asra Ali Signed-off-by: Jason Hall * remove old fulcio root and fix fallback target code (#1738) Signed-off-by: Asra Ali Signed-off-by: Jason Hall * test: create fake TUF test root and create test SETs for verification (#1750) * wip Signed-off-by: Asra Ali add fake SET test Signed-off-by: Asra Ali fix Signed-off-by: Asra Ali fix test Signed-off-by: Asra Ali fix Signed-off-by: Asra Ali * address haydentherapper comments Signed-off-by: Asra Ali Signed-off-by: Jason Hall * tuf: add debug info if tuf update fails (#1766) * add debug info for tuf update fail Signed-off-by: Asra Ali * move debugging funcs to top Signed-off-by: Asra Ali Signed-off-by: Jason Hall * Add rekor.0.pub TUF target to unit tests (#1860) This target was added to the v3 TUF root. Signed-off-by: Priya Wadhwa Signed-off-by: Jason Hall * Remove dependency on deprecated github.com/pkg/errors (#1887) * cmd/cosign/cli: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * cmd/sget/cli: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * internal/pkg/cosign/ephemeral: remove dependency on pkg/errors Signed-off-by: Koichi Shiraishi * pkg/cosign: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * pkg/oci: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * pkg/policy: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * pkg/sget: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * pkg/signature: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * go.mod: go mod tidy Signed-off-by: Koichi Shiraishi * pkg/cosign/kubernetes/webhook: remove unnecessary fmt.Sprintf Signed-off-by: Koichi Shiraishi * pkg/oci/remote: should handle error on name.NewRepository Signed-off-by: Koichi Shiraishi Signed-off-by: Jason Hall * Update go-tuf (#1894) Signed-off-by: Tomasz Janiszewski Signed-off-by: Jason Hall * fix: fix fetching updated targets from TUF root (#1921) * fix: fix fetching updated targets from TUF root Signed-off-by: Asra Ali add comment Signed-off-by: Asra Ali update Signed-off-by: Asra Ali update Signed-off-by: Asra Ali possible fix windows Signed-off-by: Asra Ali lint Signed-off-by: Asra Ali fix windows maybe Signed-off-by: Asra Ali fix close Signed-off-by: Asra Ali * update zack comments Signed-off-by: Asra Ali update fix Signed-off-by: Asra Ali update and add some debug Signed-off-by: Asra Ali add debug Signed-off-by: Asra Ali no cache Signed-off-by: Asra Ali remove debug Signed-off-by: Asra Ali * try haydens comments Signed-off-by: Asra Ali * Use Rekor API for pubkeys before TUF if so specified. Signed-off-by: Ville Aikas * Address PR feedback, bump golangci-lint from 1.46.0 to 1.46.2 Signed-off-by: Ville Aikas * Add comments for the env variables. Signed-off-by: Ville Aikas * Use path instead of filepath, basically revert to what it was before. Signed-off-by: Ville Aikas * ho hum, really just use the path. Signed-off-by: Ville Aikas * When interacting with fs do not use OS specific separators. Signed-off-by: Ville Aikas * fix windows line endings Signed-off-by: Asra Ali * pass embedded into initialization Signed-off-by: Asra Ali Co-authored-by: Ville Aikas Signed-off-by: Jason Hall * tuf: improve TUF client concurrency and caching (#1953) * move rekor public key fetch inside GetRekorPubs Signed-off-by: Asra Ali * use in-memory metadata and targets, sync to disk on start and updates Signed-off-by: Asra Ali update Signed-off-by: Asra Ali * Use TUF singleton. Co-authored-by: Ville Aikas Signed-off-by: Asra Ali * hayden comment, sync.Once used Signed-off-by: Asra Ali * return global error Signed-off-by: Asra Ali Co-authored-by: Ville Aikas Signed-off-by: Jason Hall * Drop tuf client dependency on GCS client library (#1967) * Drop tuf client dependency on GCS client library Signed-off-by: Jason Hall * Add more validation of bucket names, clean paths Signed-off-by: Jason Hall * update-deps.sh Signed-off-by: Jason Hall * remove GCSRemoteStore Signed-off-by: Jason Hall * Add comment about GCS->HTTP fallback Signed-off-by: Jason Hall * update DefaultRemoteRoot Signed-off-by: Jason Hall * make docgen Signed-off-by: Jason Hall * move tuf to pkg/tuf Signed-off-by: Jason Hall * actually move tuf to pkg/tuf Signed-off-by: Jason Hall * update copyright years, unexport, add godoc Signed-off-by: Jason Hall * Break off a `fulcioroot` package. (#639) The `cosigned` webhook pulls in the Fulcio roots, and runs as a K8s controller, which consumes `klog`. However, some of the certificate transparency stuff the Fulcio package pulls in consumes `glog`. These both define conflicting `-log_dir` flags, which cause `cosigned` to crash on startup. With this change, `cosigned` can use `fulcioroots.Get` to load the roots without pulling in `glog`. In a subsequent change, I have tests that should catch this before a breaking change merges. Signed-off-by: Matt Moore Signed-off-by: Jason Hall * update root ux (#747) Signed-off-by: Asra Ali Signed-off-by: Jason Hall * refactor: move from io/ioutil to io and os packages (#978) The io/ioutil package has been deprecated as of Go 1.16, see https://golang.org/doc/go1.16#ioutil. This commit replaces the existing io/ioutil functions with their new definitions in io and os packages. Signed-off-by: Eng Zer Jun Signed-off-by: Jason Hall * Add Fulcio v1 root to the cosign (#1112) * add fulcio v1 root Signed-off-by: Asra Ali * remove unneeded todo Signed-off-by: Asra Ali Signed-off-by: Jason Hall * Do not require multiple Fulcio certs in the TUF root (#1230) cosign requires both fulcio.crt.pem and fulcio_v1.crt.pem in the TUF root which doesn't make sense when using local TUF. fulcio_v1.crt.pem was added in the embedded TUF in order to support Fulcio v1 but it shouldn't be required when users initialize cosign with their own TUF repo. Closes: #1229 Signed-off-by: Radoslav Gerganov Signed-off-by: Jason Hall * Refactor the tuf client code. (#1252) This is my attempt at refactoring the TUF client code to better support the configuration modes we've recently added. This also adds support for SIGSTORE_NO_CACHE, and eliminates most writes to disk from cosign outside of cosign initialize. I think these tests are about equivalent to what we had before, if not a bit better. The coverage is at 72% and hits most non-sporadic errors. Signed-off-by: Dan Lorenc Signed-off-by: Jason Hall * Fetch verification targets by TUF custom metadata (#1423) * Add TUF client method for fetching by metadata Signed-off-by: Hayden Blauzvern * Fetch verification targets by TUF custom metadata This uses GetTargetsByMeta to read the targets using the custom metadata, or fallback to the old targets by filename. Signed-off-by: Hayden Blauzvern * Resolve PR comments, linter, and update tests Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Fix fulcioroots test and linter error (#1741) The linter error is from a deprecated method, but since this is only used in tests and we don't use system roots, this is fine. The test was also failing because the TUF remote can't be called in tests. Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Add intermediate CA certificate pool for Fulcio (#1749) This separates roots and intermediates from the TUF targets. This will be used to configure the default intermediate certificates when none are found. In particular, this will be used by verify-blob when fetching an entry from Rekor. An intermediate CA certificate will be added to the v3 TUF root. Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Add Fulcio intermediate CA certificate to intermediate pool (#1774) This certificate will be necessary for chain building from a leaf certificate to a root once a new version of Fulcio is rolled out. For OCI, the chain is stored in an annotation. This intermediate is currently only needed for verify-blob when looking up the certificate from Rekor. For the V3 TUF Root, the intermediate will be bundled, so that it is easily discoverable and revokable. For now, we'll simply bundle it with Cosign. Note that intermediates are considered untrusted, so it's fine if the intermediate is not in TUF currently, as the root that issued the intermediate certificate is in TUF. Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Load in intermediate cert pool from TUF (#1804) With the v3 TUF root, the intermediate CA certificate will be included, so that if the intermediate signing key was compromised, the intermediate certificate could be revoked by removing it from the TUF targets and replacing it with a trusted certificate. This change loads the intermediate certificate from TUF. However, we don't want to force all users to follow this structure - They may choose to use CRLs to detect revoked intermediates. Also, I don't want to enforce TUF usage in the Verify package. Therefore, for TUF, we lazily create a certificate pool only if an intermediate certificate is found, and if it's not found, then VerifyImageSignature will create a pool using the chain provided in the annotation. Signed-off-by: Hayden Blauzvern Signed-off-by: Jason Hall * Remove dependency on deprecated github.com/pkg/errors (#1887) * cmd/cosign/cli: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * cmd/sget/cli: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * internal/pkg/cosign/ephemeral: remove dependency on pkg/errors Signed-off-by: Koichi Shiraishi * pkg/cosign: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * pkg/oci: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * pkg/policy: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * pkg/sget: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * pkg/signature: remove dependency on deprecated github.com/pkg/errors Signed-off-by: Koichi Shiraishi * go.mod: go mod tidy Signed-off-by: Koichi Shiraishi * pkg/cosign/kubernetes/webhook: remove unnecessary fmt.Sprintf Signed-off-by: Koichi Shiraishi * pkg/oci/remote: should handle error on name.NewRepository Signed-off-by: Koichi Shiraishi Signed-off-by: Jason Hall * fix: fix fetching updated targets from TUF root (#1921) * fix: fix fetching updated targets from TUF root Signed-off-by: Asra Ali add comment Signed-off-by: Asra Ali update Signed-off-by: Asra Ali update Signed-off-by: Asra Ali possible fix windows Signed-off-by: Asra Ali lint Signed-off-by: Asra Ali fix windows maybe Signed-off-by: Asra Ali fix close Signed-off-by: Asra Ali * update zack comments Signed-off-by: Asra Ali update fix Signed-off-by: Asra Ali update and add some debug Signed-off-by: Asra Ali add debug Signed-off-by: Asra Ali no cache Signed-off-by: Asra Ali remove debug Signed-off-by: Asra Ali * try haydens comments Signed-off-by: Asra Ali * Use Rekor API for pubkeys before TUF if so specified. Signed-off-by: Ville Aikas * Address PR feedback, bump golangci-lint from 1.46.0 to 1.46.2 Signed-off-by: Ville Aikas * Add comments for the env variables. Signed-off-by: Ville Aikas * Use path instead of filepath, basically revert to what it was before. Signed-off-by: Ville Aikas * ho hum, really just use the path. Signed-off-by: Ville Aikas * When interacting with fs do not use OS specific separators. Signed-off-by: Ville Aikas * fix windows line endings Signed-off-by: Asra Ali * pass embedded into initialization Signed-off-by: Asra Ali Co-authored-by: Ville Aikas Signed-off-by: Jason Hall * tuf: improve TUF client concurrency and caching (#1953) * move rekor public key fetch inside GetRekorPubs Signed-off-by: Asra Ali * use in-memory metadata and targets, sync to disk on start and updates Signed-off-by: Asra Ali update Signed-off-by: Asra Ali * Use TUF singleton. Co-authored-by: Ville Aikas Signed-off-by: Asra Ali * hayden comment, sync.Once used Signed-off-by: Asra Ali * return global error Signed-off-by: Asra Ali Co-authored-by: Ville Aikas Signed-off-by: Jason Hall * feat(fulcioroots): singleton error pattern (#1965) Signed-off-by: Batuhan Apaydın Co-authored-by: Furkan Türkal Signed-off-by: Batuhan Apaydın Co-authored-by: Furkan Türkal Signed-off-by: Jason Hall * move fulcioroots to pkg/fulcioroots Signed-off-by: Jason Hall * remove alternate root behavior, rm fulcioroots_test.go, use pkg/tuf Signed-off-by: Jason Hall * base on latest go.sum Signed-off-by: Jason Hall * go mod tidy Signed-off-by: Jason Hall * base on latest go.sum, again somehow? Signed-off-by: Jason Hall * address some low-hanging lint fruit Signed-off-by: Jason Hall * lint: only fail PRs on new findings Signed-off-by: Jason Hall * lint: ignore revive lint findings in pkg/tuf Signed-off-by: Jason Hall Co-authored-by: Hayden B Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: cpanato Co-authored-by: Matt Moore Co-authored-by: Matt Moore Co-authored-by: Bob Callaway Co-authored-by: Billy Lynch Co-authored-by: Bob Callaway Co-authored-by: asraa Co-authored-by: dlorenc Co-authored-by: priyawadhwa Co-authored-by: Naveen <172697+naveensrinivasan@users.noreply.github.com> Co-authored-by: Jake Sanders Co-authored-by: Radoslav Gerganov Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> Co-authored-by: Josh Soref Co-authored-by: dlorenc Co-authored-by: priyawadhwa Co-authored-by: Koichi Shiraishi Co-authored-by: Tomasz Janiszewski Co-authored-by: Ville Aikas Co-authored-by: Eng Zer Jun Co-authored-by: Batuhan Apaydın Co-authored-by: Furkan Türkal --- .golangci.yml | 3 + go.mod | 1 + go.sum | 1 + pkg/fulcioroots/fulcioroots.go | 115 +++ pkg/tuf/client.go | 696 +++++++++++++++++++ pkg/tuf/client_test.go | 649 +++++++++++++++++ pkg/tuf/policy.go | 204 ++++++ pkg/tuf/policy_test.go | 94 +++ pkg/tuf/repository/root.json | 144 ++++ pkg/tuf/repository/targets/artifact.pub | 4 + pkg/tuf/repository/targets/ctfe.pub | 4 + pkg/tuf/repository/targets/fulcio.crt.pem | 13 + pkg/tuf/repository/targets/fulcio_v1.crt.pem | 13 + pkg/tuf/repository/targets/rekor.0.pub | 4 + pkg/tuf/repository/targets/rekor.json | 23 + pkg/tuf/repository/targets/rekor.pub | 4 + pkg/tuf/signer.go | 50 ++ pkg/tuf/status_type.go | 60 ++ pkg/tuf/status_type_test.go | 84 +++ pkg/tuf/testutils.go | 130 ++++ pkg/tuf/usage_type.go | 64 ++ pkg/tuf/usage_type_test.go | 84 +++ 22 files changed, 2444 insertions(+) create mode 100644 pkg/fulcioroots/fulcioroots.go create mode 100644 pkg/tuf/client.go create mode 100644 pkg/tuf/client_test.go create mode 100644 pkg/tuf/policy.go create mode 100644 pkg/tuf/policy_test.go create mode 100644 pkg/tuf/repository/root.json create mode 100644 pkg/tuf/repository/targets/artifact.pub create mode 100644 pkg/tuf/repository/targets/ctfe.pub create mode 100644 pkg/tuf/repository/targets/fulcio.crt.pem create mode 100644 pkg/tuf/repository/targets/fulcio_v1.crt.pem create mode 100644 pkg/tuf/repository/targets/rekor.0.pub create mode 100644 pkg/tuf/repository/targets/rekor.json create mode 100644 pkg/tuf/repository/targets/rekor.pub create mode 100644 pkg/tuf/signer.go create mode 100644 pkg/tuf/status_type.go create mode 100644 pkg/tuf/status_type_test.go create mode 100644 pkg/tuf/testutils.go create mode 100644 pkg/tuf/usage_type.go create mode 100644 pkg/tuf/usage_type_test.go diff --git a/.golangci.yml b/.golangci.yml index 283b5f645..ce4040838 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -47,6 +47,9 @@ issues: linters: - errcheck - gosec + - path: pkg/tuf + linters: + revive max-issues-per-linter: 0 max-same-issues: 0 run: diff --git a/go.mod b/go.mod index e66d3d512..fb041d3ef 100644 --- a/go.mod +++ b/go.mod @@ -99,6 +99,7 @@ require ( github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/ysmood/goob v0.4.0 // indirect github.com/ysmood/gson v0.7.1 // indirect diff --git a/go.sum b/go.sum index 181c0f435..7dd6ff9bf 100644 --- a/go.sum +++ b/go.sum @@ -1015,6 +1015,7 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= diff --git a/pkg/fulcioroots/fulcioroots.go b/pkg/fulcioroots/fulcioroots.go new file mode 100644 index 000000000..d5419eb49 --- /dev/null +++ b/pkg/fulcioroots/fulcioroots.go @@ -0,0 +1,115 @@ +// +// 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" + "sync" + + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/tuf" +) + +var ( + rootsOnce sync.Once + roots *x509.CertPool + intermediates *x509.CertPool + singletonRootErr error +) + +// This is the root in the fulcio project. +var fulcioTargetStr = `fulcio.crt.pem` + +// This is the v1 migrated root. +var fulcioV1TargetStr = `fulcio_v1.crt.pem` + +// The untrusted intermediate CA certificate, used for chain building +// TODO: Remove once this is bundled in TUF metadata. +var fulcioIntermediateV1 = `-----BEGIN CERTIFICATE----- +MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl +LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7 +7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS +0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB +BQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp +KFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI +zj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR +nZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP +mygUY7Ii2zbdCdliiow= +-----END CERTIFICATE-----` + +// Get returns the Fulcio root certificate. +func Get() (*x509.CertPool, error) { + rootsOnce.Do(func() { + roots, intermediates, singletonRootErr = initRoots() + if singletonRootErr != nil { + return + } + }) + return roots, singletonRootErr +} + +// GetIntermediates returns the Fulcio intermediate certificates. +func GetIntermediates() (*x509.CertPool, error) { + rootsOnce.Do(func() { + roots, intermediates, singletonRootErr = initRoots() + if singletonRootErr != nil { + return + } + }) + return intermediates, singletonRootErr +} + +func initRoots() (*x509.CertPool, *x509.CertPool, error) { + tufClient, err := tuf.NewFromEnv(context.Background()) + if err != nil { + return nil, 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}) + if err != nil { + return nil, nil, fmt.Errorf("error getting targets: %w", err) + } + if len(targets) == 0 { + return nil, nil, errors.New("none of the Fulcio roots have been found") + } + rootPool := x509.NewCertPool() + intermediatePool := x509.NewCertPool() + for _, t := range targets { + certs, err := cryptoutils.UnmarshalCertificatesFromPEM(t.Target) + if err != nil { + return nil, nil, fmt.Errorf("error unmarshalling certificates: %w", err) + } + for _, cert := range certs { + // root certificates are self-signed + if bytes.Equal(cert.RawSubject, cert.RawIssuer) { + rootPool.AddCert(cert) + } else { + intermediatePool.AddCert(cert) + } + } + } + intermediatePool.AppendCertsFromPEM([]byte(fulcioIntermediateV1)) + + return rootPool, intermediatePool, nil +} diff --git a/pkg/tuf/client.go b/pkg/tuf/client.go new file mode 100644 index 000000000..cd7498fa0 --- /dev/null +++ b/pkg/tuf/client.go @@ -0,0 +1,696 @@ +// +// 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 tuf + +import ( + "bytes" + "context" + "embed" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "io/ioutil" + "net/url" + "os" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "github.com/theupdateframework/go-tuf/client" + tuf_leveldbstore "github.com/theupdateframework/go-tuf/client/leveldbstore" + "github.com/theupdateframework/go-tuf/data" + "github.com/theupdateframework/go-tuf/util" +) + +const ( + // DefaultRemoteRoot is the default remote TUF root location. + DefaultRemoteRoot = "https://sigstore-tuf-root.storage.googleapis.com" + + // TufRootEnv is the name of the environment variable that locates an alternate local TUF root location. + TufRootEnv = "TUF_ROOT" + + // SigstoreNoCache is the name of the environment variable that, if set, configures this code to only store root data in memory. + SigstoreNoCache = "SIGSTORE_NO_CACHE" +) + +var ( + // singletonTUF holds a single instance of TUF that will get reused on + // subsequent invocations of initializeTUF + singletonTUF *TUF + singletonTUFOnce = new(sync.Once) + singletonTUFErr error +) + +// getRemoteRoot is a var for testing. +var getRemoteRoot = func() string { return DefaultRemoteRoot } + +type TUF struct { + client *client.Client + targets targetImpl + local client.LocalStore + remote client.RemoteStore + embedded fs.FS + mirror string // location of mirror +} + +// JSON output representing the configured root status +type RootStatus struct { + Local string `json:"local"` + Remote string `json:"remote"` + Metadata map[string]MetadataStatus `json:"metadata"` + Targets []string `json:"targets"` +} + +type MetadataStatus struct { + Version int `json:"version"` + Size int `json:"len"` + Expiration string `json:"expiration"` + Error string `json:"error"` +} + +type TargetFile struct { + Target []byte + Status StatusKind +} + +type customMetadata struct { + Usage UsageKind `json:"usage"` + Status StatusKind `json:"status"` +} + +type sigstoreCustomMetadata struct { + Sigstore customMetadata `json:"sigstore"` +} + +type signedMeta struct { + Type string `json:"_type"` + Expires time.Time `json:"expires"` + Version int64 `json:"version"` +} + +// RemoteCache contains information to cache on the location of the remote +// repository. +type remoteCache struct { + Mirror string `json:"mirror"` +} + +func resetForTests() { + singletonTUFOnce = new(sync.Once) +} + +func getExpiration(metadata []byte) (*time.Time, error) { + s := &data.Signed{} + if err := json.Unmarshal(metadata, s); err != nil { + return nil, err + } + sm := &signedMeta{} + if err := json.Unmarshal(s.Signed, sm); err != nil { + return nil, err + } + return &sm.Expires, nil +} + +func getVersion(metadata []byte) (int64, error) { + s := &data.Signed{} + if err := json.Unmarshal(metadata, s); err != nil { + return 0, err + } + sm := &signedMeta{} + if err := json.Unmarshal(s.Signed, sm); err != nil { + return 0, err + } + return sm.Version, nil +} + +var isExpiredTimestamp = func(metadata []byte) bool { + expiration, err := getExpiration(metadata) + if err != nil { + return true + } + return time.Until(*expiration) <= 0 +} + +func getMetadataStatus(b []byte) (*MetadataStatus, error) { + expires, err := getExpiration(b) + if err != nil { + return nil, err + } + version, err := getVersion(b) + if err != nil { + return nil, err + } + return &MetadataStatus{ + Size: len(b), + Expiration: expires.Format(time.RFC822), + Version: int(version), + }, nil +} + +func (t *TUF) getRootStatus() (*RootStatus, error) { + local := rootCacheDir() + if noCache() { + local = "in-memory" + } + status := &RootStatus{ + Local: local, + Remote: t.mirror, + Metadata: make(map[string]MetadataStatus), + Targets: []string{}, + } + + // Get targets + targets, err := t.client.Targets() + if err != nil { + return nil, err + } + for t := range targets { + status.Targets = append(status.Targets, t) + } + + // Get metadata expiration + trustedMeta, err := t.local.GetMeta() + if err != nil { + return nil, fmt.Errorf("getting trusted meta: %w", err) + } + for role, md := range trustedMeta { + mdStatus, err := getMetadataStatus(md) + if err != nil { + status.Metadata[role] = MetadataStatus{Error: err.Error()} + continue + } + status.Metadata[role] = *mdStatus + } + + return status, nil +} + +func getRoot(meta map[string]json.RawMessage, fallback fs.FS) (json.RawMessage, error) { + if trustedRoot, ok := meta["root.json"]; ok { + return trustedRoot, nil + } + // On first initialize, there will be no root in the TUF DB, so read from embedded. + rd, ok := fallback.(fs.ReadFileFS) + if !ok { + return nil, errors.New("fs.ReadFileFS unimplemented for embedded repo") + } + trustedRoot, err := rd.ReadFile(path.Join("repository", "root.json")) + if err != nil { + return nil, err + } + return trustedRoot, nil +} + +// GetRootStatus gets the current root status for info logging +func GetRootStatus(ctx context.Context) (*RootStatus, error) { + t, err := NewFromEnv(ctx) + if err != nil { + return nil, err + } + return t.getRootStatus() +} + +// initializeTUF creates a TUF client using the following params: +// * embed: indicates using the embedded metadata and in-memory file updates. +// When this is false, this uses a filesystem cache. +// * mirror: provides a reference to a remote GCS or HTTP mirror. +// * root: provides an external initial root.json. When this is not provided, this +// defaults to the embedded root.json. +// * embedded: An embedded filesystem that provides a trusted root and pre-downloaded +// targets in a targets/ subfolder. +// * forceUpdate: indicates checking the remote for an update, even when the local +// timestamp.json is up to date. +func initializeTUF(mirror string, root []byte, embedded fs.FS, forceUpdate bool) (*TUF, error) { + singletonTUFOnce.Do(func() { + t := &TUF{ + mirror: mirror, + embedded: embedded, + } + + t.targets = newFileImpl() + t.local, singletonTUFErr = newLocalStore() + if singletonTUFErr != nil { + return + } + + t.remote, singletonTUFErr = remoteFromMirror(t.mirror) + if singletonTUFErr != nil { + return + } + + t.client = client.NewClient(t.local, t.remote) + + trustedMeta, err := t.local.GetMeta() + if err != nil { + singletonTUFErr = fmt.Errorf("getting trusted meta: %w", err) + return + } + + // If the caller does not supply a root, then either use the root in the local store + // or default to the embedded one. + if root == nil { + root, err = getRoot(trustedMeta, t.embedded) + if err != nil { + singletonTUFErr = fmt.Errorf("getting trusted root: %w", err) + return + } + } + + if err := t.client.InitLocal(root); err != nil { + singletonTUFErr = fmt.Errorf("unable to initialize client, local cache may be corrupt: %w", err) + return + } + + // We may already have an up-to-date local store! Check to see if it needs to be updated. + trustedTimestamp, ok := trustedMeta["timestamp.json"] + if ok && !isExpiredTimestamp(trustedTimestamp) && !forceUpdate { + // We're golden so stash the TUF object for later use + singletonTUF = t + return + } + + // Update if local is not populated or out of date. + if err := t.updateMetadataAndDownloadTargets(); err != nil { + singletonTUFErr = fmt.Errorf("updating local metadata and targets: %w", err) + return + } + + // We're golden so stash the TUF object for later use + singletonTUF = t + }) + return singletonTUF, singletonTUFErr +} + +// TODO: Remove ctx arg. +func NewFromEnv(_ context.Context) (*TUF, error) { + // Check for the current remote mirror. + mirror := getRemoteRoot() + b, err := os.ReadFile(cachedRemote(rootCacheDir())) + if err == nil { + remoteInfo := remoteCache{} + if err := json.Unmarshal(b, &remoteInfo); err == nil { + mirror = remoteInfo.Mirror + } + } + + // Initializes a new TUF object from the local cache or defaults. + return initializeTUF(mirror, nil, getEmbedded(), false) +} + +func Initialize(ctx context.Context, mirror string, root []byte) error { + // Initialize the client. Force an update with remote. + if _, err := initializeTUF(mirror, root, getEmbedded(), true); err != nil { + return err + } + + // Store the remote for later if we are caching. + if !noCache() { + remoteInfo := &remoteCache{Mirror: mirror} + b, err := json.Marshal(remoteInfo) + if err != nil { + return err + } + if err := os.WriteFile(cachedRemote(rootCacheDir()), b, 0600); err != nil { + return fmt.Errorf("storing remote: %w", err) + } + } + return nil +} + +// Checks if the testTarget matches the valid target file metadata. +func isValidTarget(testTarget []byte, validMeta data.TargetFileMeta) bool { + localMeta, err := util.GenerateTargetFileMeta(bytes.NewReader(testTarget)) + if err != nil { + return false + } + if err := util.TargetFileMetaEqual(localMeta, validMeta); err != nil { + return false + } + return true +} + +func (t *TUF) GetTarget(name string) ([]byte, error) { + // Get valid target metadata. Does a local verification. + validMeta, err := t.client.Target(name) + if err != nil { + return nil, fmt.Errorf("error verifying local metadata; local cache may be corrupt: %w", err) + } + targetBytes, err := t.targets.Get(name) + if err != nil { + return nil, err + } + + if !isValidTarget(targetBytes, validMeta) { + return nil, fmt.Errorf("cache contains invalid target; local cache may be corrupt") + } + + return targetBytes, nil +} + +// Get target files by a custom usage metadata tag. If there are no files found, +// use the fallback target names to fetch the targets by name. +func (t *TUF) GetTargetsByMeta(usage UsageKind, fallbacks []string) ([]TargetFile, error) { + targets, err := t.client.Targets() + if err != nil { + return nil, fmt.Errorf("error getting targets: %w", err) + } + var matchedTargets []TargetFile + for name, targetMeta := range targets { + // Skip any targets that do not include custom metadata. + if targetMeta.Custom == nil { + continue + } + var scm sigstoreCustomMetadata + err := json.Unmarshal(*targetMeta.Custom, &scm) + if err != nil { + fmt.Fprintf(os.Stderr, "**Warning** Custom metadata not configured properly for target %s, skipping target\n", name) + continue + } + if scm.Sigstore.Usage == usage { + target, err := t.GetTarget(name) + if err != nil { + return nil, fmt.Errorf("error getting target %s by usage: %w", name, err) + } + matchedTargets = append(matchedTargets, TargetFile{Target: target, Status: scm.Sigstore.Status}) + } + } + if len(matchedTargets) == 0 { + for _, fallback := range fallbacks { + target, err := t.GetTarget(fallback) + if err != nil { + fmt.Fprintf(os.Stderr, "**Warning** Missing fallback target %s, skipping\n", fallback) + continue + } + matchedTargets = append(matchedTargets, TargetFile{Target: target, Status: Active}) + } + } + if len(matchedTargets) == 0 { + return matchedTargets, fmt.Errorf("no matching targets by custom metadata, fallbacks not found: %s", strings.Join(fallbacks, ", ")) + } + return matchedTargets, nil +} + +// updateClient() updates the TUF client and also caches new metadata, if needed. +func (t *TUF) updateClient() (data.TargetFiles, error) { + targets, err := t.client.Update() + if err != nil { + // Get some extra information for debugging. What was the state of the top-level + // metadata on the remote? + status := struct { + Mirror string `json:"mirror"` + Metadata map[string]MetadataStatus `json:"metadata"` + }{ + Mirror: t.mirror, + Metadata: make(map[string]MetadataStatus), + } + for _, md := range []string{"root.json", "targets.json", "snapshot.json", "timestamp.json"} { + r, _, err := t.remote.GetMeta(md) + if err != nil { + // May be missing, or failed download. + continue + } + defer r.Close() + b, err := ioutil.ReadAll(r) + if err != nil { + continue + } + mdStatus, err := getMetadataStatus(b) + if err != nil { + continue + } + status.Metadata[md] = *mdStatus + } + b, innerErr := json.MarshalIndent(status, "", "\t") + if innerErr != nil { + return nil, innerErr + } + return nil, fmt.Errorf("error updating to TUF remote mirror: %w\nremote status:%s", err, string(b)) + } + // Success! Cache new metadata, if needed. + if noCache() { + return targets, nil + } + // Sync the on-disk cache with the metadata from the in-memory store. + tufDB := filepath.FromSlash(filepath.Join(rootCacheDir(), "tuf.db")) + diskLocal, err := tuf_leveldbstore.FileLocalStore(tufDB) + defer func() { + if diskLocal != nil { + diskLocal.Close() + } + }() + if err != nil { + return nil, fmt.Errorf("creating cached local store: %w", err) + } + if err := syncLocalMeta(t.local, diskLocal); err != nil { + return nil, err + } + // Return updated targets. + return targets, nil +} + +func (t *TUF) updateMetadataAndDownloadTargets() error { + // Download updated targets and cache new metadata and targets in ${TUF_ROOT}. + // NOTE: This only returns *updated* targets. + targetFiles, err := t.updateClient() + if err != nil { + return err + } + + // Download **newly** updated targets. + // TODO: Consider lazily downloading these -- be careful with embedded targets if so. + for name, targetMeta := range targetFiles { + if err := maybeDownloadRemoteTarget(name, targetMeta, t); err != nil { + return err + } + } + + return nil +} + +type targetDestination struct { + buf *bytes.Buffer +} + +func (t *targetDestination) Write(b []byte) (int, error) { + return t.buf.Write(b) +} + +func (t *targetDestination) Delete() error { + t.buf = &bytes.Buffer{} + return nil +} + +func maybeDownloadRemoteTarget(name string, meta data.TargetFileMeta, t *TUF) error { + // If we already have the target locally, don't bother downloading from remote storage. + if cachedTarget, err := t.targets.Get(name); err == nil { + // If the target we have stored matches the meta, use that. + if isValidTarget(cachedTarget, meta) { + return nil + } + } + + // Check if we already have the target in the embedded store. + w := bytes.Buffer{} + rd, ok := t.embedded.(fs.ReadFileFS) + if !ok { + return errors.New("fs.ReadFileFS unimplemented for embedded repo") + } + b, err := rd.ReadFile(path.Join("repository", "targets", name)) + + if err == nil { + // Unfortunately go:embed appears to somehow replace our line endings on windows, we need to switch them back. + // It should theoretically be safe to do this everywhere - but the files only seem to get mutated on Windows so + // let's only change them back there. + if runtime.GOOS == "windows" { + b = bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n")) + } + + if isValidTarget(b, meta) { + if _, err := io.Copy(&w, bytes.NewReader(b)); err != nil { + return fmt.Errorf("using embedded target: %w", err) + } + } + } + + // Nope -- no local matching target, go download it. + if w.Len() == 0 { + dest := targetDestination{buf: &w} + if err := t.client.Download(name, &dest); err != nil { + return fmt.Errorf("downloading target: %w", err) + } + } + + // Set the target in the cache. + if err := t.targets.Set(name, w.Bytes()); err != nil { + return err + } + return nil +} + +func rootCacheDir() string { + rootDir := os.Getenv(TufRootEnv) + if rootDir == "" { + home, err := os.UserHomeDir() + if err != nil { + home = "" + } + return filepath.FromSlash(filepath.Join(home, ".sigstore", "root")) + } + return rootDir +} + +func cachedRemote(cacheRoot string) string { + return filepath.FromSlash(filepath.Join(cacheRoot, "remote.json")) +} + +func cachedTargetsDir(cacheRoot string) string { + return filepath.FromSlash(filepath.Join(cacheRoot, "targets")) +} + +func syncLocalMeta(from client.LocalStore, to client.LocalStore) error { + // Copy trusted metadata in the from LocalStore into the to LocalStore. + tufLocalStoreMeta, err := from.GetMeta() + if err != nil { + return fmt.Errorf("getting metadata to sync: %w", err) + } + for k, v := range tufLocalStoreMeta { + if err := to.SetMeta(k, v); err != nil { + return fmt.Errorf("syncing local store for metadata %s", k) + } + } + return nil +} + +// Local store implementations +func newLocalStore() (client.LocalStore, error) { + local := client.MemoryLocalStore() + if noCache() { + return local, nil + } + // Otherwise populate the in-memory local store with data fetched from the cache. + tufDB := filepath.FromSlash(filepath.Join(rootCacheDir(), "tuf.db")) + diskLocal, err := tuf_leveldbstore.FileLocalStore(tufDB) + defer func() { + if diskLocal != nil { + diskLocal.Close() + } + }() + if err != nil { + return nil, fmt.Errorf("creating cached local store: %w", err) + } + // Populate the in-memory local store with data fetched from the cache. + if err := syncLocalMeta(diskLocal, local); err != nil { + return nil, err + } + return local, nil +} + +//go:embed repository +var embeddedRootRepo embed.FS + +// getEmbedded is a var for testing. +var getEmbedded = func() fs.FS { return embeddedRootRepo } + +// Target Implementations +type targetImpl interface { + Set(string, []byte) error + Get(string) ([]byte, error) +} + +func newFileImpl() targetImpl { + memTargets := &memoryCache{} + if noCache() { + return memTargets + } + // Otherwise use a disk-cache with in-memory cached targets. + return &diskCache{ + base: cachedTargetsDir(rootCacheDir()), + memory: memTargets, + } +} + +// In-memory cache for targets +type memoryCache struct { + targets map[string][]byte +} + +func (m *memoryCache) Set(p string, b []byte) error { + if m.targets == nil { + m.targets = map[string][]byte{} + } + m.targets[p] = b + return nil +} + +func (m *memoryCache) Get(p string) ([]byte, error) { + if m.targets == nil { + return nil, fmt.Errorf("no cached targets available, cannot retrieve %s", p) + } + b, ok := m.targets[p] + if !ok { + return nil, fmt.Errorf("missing cached target %s", p) + } + return b, nil +} + +// On-disk cache for targets +type diskCache struct { + // Base directory for accessing targets. + base string + // An in-memory map of targets that are kept in sync. + memory *memoryCache +} + +func (d *diskCache) Get(p string) ([]byte, error) { + // Read from the in-memory cache first. + if b, err := d.memory.Get(p); err == nil { + return b, nil + } + fp := filepath.FromSlash(filepath.Join(d.base, p)) + return os.ReadFile(fp) +} + +func (d *diskCache) Set(p string, b []byte) error { + if err := d.memory.Set(p, b); err != nil { + return err + } + if err := os.MkdirAll(d.base, 0700); err != nil { + return fmt.Errorf("creating targets dir: %w", err) + } + fp := filepath.FromSlash(filepath.Join(d.base, p)) + return os.WriteFile(fp, b, 0600) +} + +func noCache() bool { + b, err := strconv.ParseBool(os.Getenv(SigstoreNoCache)) + if err != nil { + return false + } + return b +} + +func remoteFromMirror(mirror string) (client.RemoteStore, error) { + // This is for compatibility with specifying a GCS bucket remote. + if _, parseErr := url.ParseRequestURI(mirror); parseErr != nil { + mirror = fmt.Sprintf("https://%s.storage.googleapis.com", mirror) + } + return client.HTTPRemoteStore(mirror, nil, nil) +} diff --git a/pkg/tuf/client_test.go b/pkg/tuf/client_test.go new file mode 100644 index 000000000..1555f0870 --- /dev/null +++ b/pkg/tuf/client_test.go @@ -0,0 +1,649 @@ +// +// 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 tuf + +import ( + "bytes" + "context" + "encoding/json" + "io/fs" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path" + "path/filepath" + "reflect" + "sort" + "strings" + "sync" + "testing" + "testing/fstest" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/theupdateframework/go-tuf" + "github.com/theupdateframework/go-tuf/data" + "github.com/theupdateframework/go-tuf/verify" +) + +var targets = []string{ + "artifact.pub", + "fulcio.crt.pem", + "fulcio_v1.crt.pem", + "ctfe.pub", + "rekor.pub", + "rekor.0.pub", +} + +func TestNewFromEnv(t *testing.T) { + td := t.TempDir() + t.Setenv("TUF_ROOT", td) + ctx := context.Background() + + // Make sure nothing is expired + tuf, err := NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + + checkTargetsAndMeta(t, tuf) + resetForTests() + + // Now try with expired targets + forceExpiration(t, true) + tuf, err = NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + checkTargetsAndMeta(t, tuf) + resetForTests() + + if err := Initialize(ctx, DefaultRemoteRoot, nil); err != nil { + t.Error() + } + if l := dirLen(t, td); l == 0 { + t.Errorf("expected filesystem writes, got %d entries", l) + } + + // And go from there! + tuf, err = NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + checkTargetsAndMeta(t, tuf) + resetForTests() +} + +func TestNoCache(t *testing.T) { + ctx := context.Background() + // Once more with NO_CACHE + t.Setenv("SIGSTORE_NO_CACHE", "true") + td := t.TempDir() + t.Setenv("TUF_ROOT", td) + + // First initialization, populate the cache. + tuf, err := NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + checkTargetsAndMeta(t, tuf) + resetForTests() + + // Force expiration so we have some content to download + forceExpiration(t, true) + + tuf, err = NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + checkTargetsAndMeta(t, tuf) + resetForTests() + + // No filesystem writes when using SIGSTORE_NO_CACHE. + if l := dirLen(t, td); l != 0 { + t.Errorf("expected no filesystem writes, got %d entries", l) + } + resetForTests() +} + +func TestCache(t *testing.T) { + ctx := context.Background() + // Once more with cache. + t.Setenv("SIGSTORE_NO_CACHE", "false") + td := t.TempDir() + t.Setenv("TUF_ROOT", td) + + // Make sure nothing is in that directory to start with + if l := dirLen(t, td); l != 0 { + t.Errorf("expected no filesystem writes, got %d entries", l) + } + + // First initialization, populate the cache. Expect disk writes. + tuf, err := NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + checkTargetsAndMeta(t, tuf) + resetForTests() + cachedDirLen := dirLen(t, td) + if cachedDirLen == 0 { + t.Errorf("expected filesystem writes, got %d entries", cachedDirLen) + } + + // Nothing should get downloaded if everything is up to date. + forceExpiration(t, false) + _, err = NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + resetForTests() + + if l := dirLen(t, td); cachedDirLen != l { + t.Errorf("expected no filesystem writes, got %d entries", l-cachedDirLen) + } + + // Forcing expiration, but expect no disk writes because all targets up to date. + forceExpiration(t, true) + tuf, err = NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + + if l := dirLen(t, td); l != cachedDirLen { + t.Errorf("expected filesystem writes, got %d entries", l) + } + checkTargetsAndMeta(t, tuf) + resetForTests() +} + +func TestCustomRoot(t *testing.T) { + ctx := context.Background() + // Create a remote repository. + td := t.TempDir() + remote, r := newTufRepo(t, td, "foo") + + // Serve remote repository. + s := httptest.NewServer(http.FileServer(http.Dir(filepath.Join(td, "repository")))) + defer s.Close() + + // Initialize with custom root. + tufRoot := t.TempDir() + t.Setenv("TUF_ROOT", tufRoot) + meta, err := remote.GetMeta() + if err != nil { + t.Error(err) + } + rootBytes, ok := meta["root.json"] + if !ok { + t.Error(err) + } + if err := Initialize(ctx, s.URL, rootBytes); err != nil { + t.Error(err) + } + if l := dirLen(t, tufRoot); l == 0 { + t.Errorf("expected filesystem writes, got %d entries", l) + } + + // Successfully get target. + tufObj, err := NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + if b, err := tufObj.GetTarget("foo.txt"); err != nil || !bytes.Equal(b, []byte("foo")) { + t.Fatal(err) + } + resetForTests() + + // Force expiration on the first timestamp and internal go-tuf verification. + forceExpirationVersion(t, 1) + oldIsExpired := verify.IsExpired + verify.IsExpired = func(time time.Time) bool { + return true + } + + // This should cause an error that remote metadata is expired. + if _, err = NewFromEnv(ctx); err == nil { + t.Errorf("expected expired timestamp from the remote") + } + + // Let internal TUF verification succeed normally now. + verify.IsExpired = oldIsExpired + + // Update remote targets, issue a timestamp v2. + updateTufRepo(t, td, r, "foo1") + + // Use newTuf and successfully get updated metadata using the cached remote location. + resetForTests() + tufObj, err = NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + if b, err := tufObj.GetTarget("foo.txt"); err != nil || !bytes.Equal(b, []byte("foo1")) { + t.Fatal(err) + } + resetForTests() +} + +func TestGetTargetsByMeta(t *testing.T) { + ctx := context.Background() + // Create a remote repository. + td := t.TempDir() + remote, _ := newTufCustomRepo(t, td, "foo") + + // Serve remote repository. + s := httptest.NewServer(http.FileServer(http.Dir(filepath.Join(td, "repository")))) + defer s.Close() + + // Initialize with custom root. + tufRoot := t.TempDir() + t.Setenv("TUF_ROOT", tufRoot) + meta, err := remote.GetMeta() + if err != nil { + t.Error(err) + } + rootBytes, ok := meta["root.json"] + if !ok { + t.Error(err) + } + if err := Initialize(ctx, s.URL, rootBytes); err != nil { + t.Error(err) + } + if l := dirLen(t, tufRoot); l == 0 { + t.Errorf("expected filesystem writes, got %d entries", l) + } + + tufObj, err := NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + defer resetForTests() + // Fetch a target with no custom metadata. + targets, err := tufObj.GetTargetsByMeta(UnknownUsage, []string{"fooNoCustom.txt"}) + if err != nil { + t.Fatal(err) + } + if len(targets) != 1 { + t.Fatalf("expected one target without custom metadata, got %d targets", len(targets)) + } + if !bytes.Equal(targets[0].Target, []byte("foo")) { + t.Fatalf("target metadata mismatched, expected: %s, got: %s", "foo", string(targets[0].Target)) + } + if targets[0].Status != Active { + t.Fatalf("target without custom metadata not active, got: %v", targets[0].Status) + } + // Fetch multiple targets with no custom metadata. + targets, err = tufObj.GetTargetsByMeta(UnknownUsage, []string{"fooNoCustom.txt", "fooNoCustomOther.txt"}) + if err != nil { + t.Fatal(err) + } + if len(targets) != 2 { + t.Fatalf("expected two targets without custom metadata, got %d targets", len(targets)) + } + if targets[0].Status != Active || targets[1].Status != Active { + t.Fatalf("target without custom metadata not active, got: %v and %v", targets[0].Status, targets[1].Status) + } + // Specify multiple fallbacks with no custom metadata. + targets, err = tufObj.GetTargetsByMeta(UnknownUsage, []string{"fooNoCustom.txt", "fooNoCustomOtherMissingTarget.txt"}) + if err != nil { + t.Fatal(err) + } + if len(targets) != 1 { + t.Fatalf("expected one targets without custom metadata, got %d targets", len(targets)) + } + if targets[0].Status != Active { + t.Fatalf("target without custom metadata not active, got: %v and %v", targets[0].Status, targets[1].Status) + } + // Fetch targets with custom metadata. + targets, err = tufObj.GetTargetsByMeta(Fulcio, []string{"fooNoCustom.txt"}) + if err != nil { + t.Fatal(err) + } + if len(targets) != 2 { + t.Fatalf("expected two targets without custom metadata, got %d targets", len(targets)) + } + targetBytes := []string{string(targets[0].Target), string(targets[1].Target)} + expectedTB := []string{"foo", "foo"} + if !reflect.DeepEqual(targetBytes, expectedTB) { + t.Fatalf("target metadata mismatched, expected: %v, got: %v", expectedTB, targetBytes) + } + targetStatuses := []StatusKind{targets[0].Status, targets[1].Status} + sort.Slice(targetStatuses, func(i, j int) bool { + return targetStatuses[i] < targetStatuses[j] + }) + expectedTS := []StatusKind{Active, Expired} + if !reflect.DeepEqual(targetStatuses, expectedTS) { + t.Fatalf("unexpected target status with custom metadata, expected %v, got: %v", expectedTS, targetStatuses) + } + // Error when fetching target that does not exist. + _, err = tufObj.GetTargetsByMeta(UsageKind(UnknownStatus), []string{"unknown.txt"}) + expectedErr := "file not found: unknown.txt" + if !strings.Contains(err.Error(), "not found: unknown.txt") { + t.Fatalf("unexpected error fetching missing metadata, expected: %s, got: %s", expectedErr, err.Error()) + } +} + +func makeMapFS(repo string) (fs fstest.MapFS) { + fs = make(fstest.MapFS) + _ = filepath.Walk(repo, + func(fpath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + rel, _ := filepath.Rel(repo, fpath) + if info.IsDir() { + fs[path.Join("repository", rel)] = &fstest.MapFile{Mode: os.ModeDir} + } else { + b, _ := os.ReadFile(fpath) + fs[path.Join("repository", rel)] = &fstest.MapFile{Data: b} + } + return nil + }) + return +} + +// Regression test for failure to fetch a target that does not exist in the embedded +// repository on an update. The new target exists on the remote before the TUF object +// is initialized. +func TestUpdatedTargetNamesEmbedded(t *testing.T) { + td := t.TempDir() + // Set the TUF_ROOT so we don't interact with other tests and local TUF roots. + t.Setenv("TUF_ROOT", td) + + origEmbedded := getEmbedded + origDefaultRemote := getRemoteRoot + + // Create an "expired" embedded repository that does not contain newTarget. + ctx := context.Background() + store, r := newTufCustomRepo(t, td, "foo") + repository := filepath.FromSlash(filepath.Join(td, "repository")) + mapfs := makeMapFS(repository) + getEmbedded = func() fs.FS { return mapfs } + + oldIsExpired := isExpiredTimestamp + isExpiredTimestamp = func(metadata []byte) bool { + m, _ := store.GetMeta() + timestampExpires, _ := getExpiration(m["timestamp.json"]) + metadataExpires, _ := getExpiration(metadata) + return metadataExpires.Sub(*timestampExpires) <= 0 + } + defer func() { + getEmbedded = origEmbedded + getRemoteRoot = origDefaultRemote + isExpiredTimestamp = oldIsExpired + }() + + // Assert that the embedded repository does not contain the newTarget. + newTarget := "fooNew.txt" + rd, ok := getEmbedded().(fs.ReadFileFS) + if !ok { + t.Fatal("fs.ReadFileFS unimplemented for embedded repo") + } + if _, err := rd.ReadFile(path.Join("repository", "targets", newTarget)); err == nil { + t.Fatal("embedded repository should not contain new target") + } + + // Serve an updated remote repository with the newTarget. + addNewCustomTarget(t, td, r, map[string]string{newTarget: "newdata"}) + s := httptest.NewServer(http.FileServer(http.Dir(repository))) + defer s.Close() + getRemoteRoot = func() string { return s.URL } + + // Initialize. + tufObj, err := NewFromEnv(ctx) + if err != nil { + t.Fatal(err) + } + defer resetForTests() + + // Try to retrieve the newly added target. + targets, err := tufObj.GetTargetsByMeta(Fulcio, []string{"fooNoCustom.txt"}) + if err != nil { + t.Fatal(err) + } + if len(targets) != 3 { + t.Fatalf("expected three target without custom metadata, got %d targets", len(targets)) + } + targetBytes := []string{string(targets[0].Target), string(targets[1].Target), string(targets[2].Target)} + expectedTB := []string{"foo", "foo", "newdata"} + if !cmp.Equal(targetBytes, expectedTB, + cmpopts.SortSlices(func(a, b string) bool { return a < b })) { + t.Fatalf("target data mismatched, expected: %v, got: %v", expectedTB, targetBytes) + } +} + +func checkTargetsAndMeta(t *testing.T, tuf *TUF) { + // Check the targets + t.Helper() + for _, target := range targets { + if _, err := tuf.GetTarget(target); err != nil { + t.Fatal(err) + } + } + + // An invalid target + if _, err := tuf.GetTarget("invalid"); err == nil { + t.Error("expected error reading target, got nil") + } + + // Check root status matches + status, err := tuf.getRootStatus() + if err != nil { + t.Fatal(err) + } + if !cmp.Equal(targets, status.Targets, + cmpopts.SortSlices(func(a, b string) bool { return a < b })) { + t.Errorf("mismatched targets, expected %s, got %s", targets, status.Targets) + } +} + +func dirLen(t *testing.T, td string) int { + t.Helper() + de, err := os.ReadDir(td) + if err != nil { + t.Fatal(err) + } + return len(de) +} + +func forceExpiration(t *testing.T, expire bool) { + oldIsExpiredTimestamp := isExpiredTimestamp + isExpiredTimestamp = func(_ []byte) bool { + return expire + } + t.Cleanup(func() { + isExpiredTimestamp = oldIsExpiredTimestamp + }) +} + +func forceExpirationVersion(t *testing.T, version int64) { + oldIsExpiredTimestamp := isExpiredTimestamp + isExpiredTimestamp = func(metadata []byte) bool { + s := &data.Signed{} + if err := json.Unmarshal(metadata, s); err != nil { + return true + } + sm := &data.Timestamp{} + if err := json.Unmarshal(s.Signed, sm); err != nil { + return true + } + return sm.Version <= version + } + t.Cleanup(func() { + isExpiredTimestamp = oldIsExpiredTimestamp + }) +} + +// newTufCustomRepo initializes a TUF repository with root, targets, snapshot, and timestamp roles +// 4 targets are created to exercise various code paths, including two targets with no custom metadata, +// one target with custom metadata marked as active, and another with custom metadata marked as expired. +func newTufCustomRepo(t *testing.T, td string, targetData string) (tuf.LocalStore, *tuf.Repo) { + scmActive, err := json.Marshal(&sigstoreCustomMetadata{Sigstore: customMetadata{Usage: Fulcio, Status: Active}}) + if err != nil { + t.Error(err) + } + scmExpired, err := json.Marshal(&sigstoreCustomMetadata{Sigstore: customMetadata{Usage: Fulcio, Status: Expired}}) + if err != nil { + t.Error(err) + } + + remote := tuf.FileSystemStore(td, nil) + r, err := tuf.NewRepo(remote) + if err != nil { + t.Error(err) + } + if err := r.Init(false); err != nil { + t.Error(err) + } + for _, role := range []string{"root", "targets", "snapshot", "timestamp"} { + if _, err := r.GenKey(role); err != nil { + t.Error(err) + } + } + for name, scm := range map[string]json.RawMessage{ + "fooNoCustom.txt": nil, "fooNoCustomOther.txt": nil, + "fooActive.txt": scmActive, "fooExpired.txt": scmExpired} { + targetPath := filepath.FromSlash(filepath.Join(td, "staged", "targets", name)) + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + t.Error(err) + } + if err := ioutil.WriteFile(targetPath, []byte(targetData), 0600); err != nil { + t.Error(err) + } + if err := r.AddTarget(name, scm); err != nil { + t.Error(err) + } + } + if err := r.Snapshot(); err != nil { + t.Error(err) + } + if err := r.Timestamp(); err != nil { + t.Error(err) + } + if err := r.Commit(); err != nil { + t.Error(err) + } + return remote, r +} + +func addNewCustomTarget(t *testing.T, td string, r *tuf.Repo, targetData map[string]string) { + scmActive, err := json.Marshal(&sigstoreCustomMetadata{Sigstore: customMetadata{Usage: Fulcio, Status: Active}}) + if err != nil { + t.Error(err) + } + + for name, data := range targetData { + targetPath := filepath.FromSlash(filepath.Join(td, "staged", "targets", name)) + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + t.Error(err) + } + if err := ioutil.WriteFile(targetPath, []byte(data), 0600); err != nil { + t.Error(err) + } + if err := r.AddTarget(name, scmActive); err != nil { + t.Error(err) + } + } + + if err := r.Snapshot(); err != nil { + t.Error(err) + } + if err := r.Timestamp(); err != nil { + t.Error(err) + } + if err := r.Commit(); err != nil { + t.Error(err) + } +} + +func newTufRepo(t *testing.T, td string, targetData string) (tuf.LocalStore, *tuf.Repo) { + remote := tuf.FileSystemStore(td, nil) + r, err := tuf.NewRepo(remote) + if err != nil { + t.Error(err) + } + if err := r.Init(false); err != nil { + t.Error(err) + } + for _, role := range []string{"root", "targets", "snapshot", "timestamp"} { + if _, err := r.GenKey(role); err != nil { + t.Error(err) + } + } + targetPath := filepath.FromSlash(filepath.Join(td, "staged", "targets", "foo.txt")) + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + t.Error(err) + } + if err := ioutil.WriteFile(targetPath, []byte(targetData), 0600); err != nil { + t.Error(err) + } + if err := r.AddTarget("foo.txt", nil); err != nil { + t.Error(err) + } + if err := r.Snapshot(); err != nil { + t.Error(err) + } + if err := r.Timestamp(); err != nil { + t.Error(err) + } + if err := r.Commit(); err != nil { + t.Error(err) + } + return remote, r +} + +func updateTufRepo(t *testing.T, td string, r *tuf.Repo, targetData string) { + targetPath := filepath.FromSlash(filepath.Join(td, "staged", "targets", "foo.txt")) + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + t.Error(err) + } + if err := ioutil.WriteFile(targetPath, []byte(targetData), 0600); err != nil { + t.Error(err) + } + if err := r.AddTarget("foo.txt", nil); err != nil { + t.Error(err) + } + if err := r.Snapshot(); err != nil { + t.Error(err) + } + if err := r.Timestamp(); err != nil { + t.Error(err) + } + if err := r.Commit(); err != nil { + t.Error(err) + } +} +func TestConcurrentAccess(t *testing.T) { + var wg sync.WaitGroup + + for i := 0; i < 20; i++ { + wg.Add(1) + go func() { + defer wg.Done() + tufObj, err := NewFromEnv(context.Background()) + if err != nil { + t.Errorf("Failed to construct NewFromEnv: %s", err) + } + if tufObj == nil { + t.Error("Got back nil tufObj") + } + time.Sleep(1 * time.Second) + }() + } + wg.Wait() + resetForTests() +} diff --git a/pkg/tuf/policy.go b/pkg/tuf/policy.go new file mode 100644 index 000000000..055103aa1 --- /dev/null +++ b/pkg/tuf/policy.go @@ -0,0 +1,204 @@ +// +// 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. + +// Contains root policy definitions. +// Eventually, this will move this to go-tuf definitions. + +package tuf + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "sync" + "time" + + cjson "github.com/secure-systems-lab/go-securesystemslib/cjson" +) + +type Signed struct { + Signed json.RawMessage `json:"signed"` + Signatures []Signature `json:"signatures"` +} + +type Signature struct { + KeyID string `json:"keyid"` + Signature string `json:"sig"` + Cert string `json:"cert,omitempty"` +} + +type Key struct { + Type string `json:"keytype"` + Scheme string `json:"scheme"` + Algorithms []string `json:"keyid_hash_algorithms,omitempty"` + Value json.RawMessage `json:"keyval"` + + id string + idOnce sync.Once +} + +func (k *Key) ID() string { + k.idOnce.Do(func() { + data, _ := cjson.EncodeCanonical(k) + digest := sha256.Sum256(data) + k.id = hex.EncodeToString(digest[:]) + }) + return k.id +} + +func (k *Key) ContainsID(id string) bool { + return id == k.ID() +} + +type Root struct { + Type string `json:"_type"` + SpecVersion string `json:"spec_version"` + Version int `json:"version"` + Expires time.Time `json:"expires"` + Keys map[string]*Key `json:"keys"` + Roles map[string]*Role `json:"roles"` + Namespace string `json:"namespace"` + + ConsistentSnapshot bool `json:"consistent_snapshot"` +} + +func NewRoot() *Root { + return &Root{ + Type: "root", + SpecVersion: "1.0", + Version: 1, + // Default expires in 3 months + Expires: time.Now().AddDate(0, 3, 0).UTC().Round(time.Second), + Keys: make(map[string]*Key), + Roles: make(map[string]*Role), + ConsistentSnapshot: true, + } +} + +func (r *Root) AddKey(key *Key) bool { + changed := false + if _, ok := r.Keys[key.ID()]; !ok { + changed = true + r.Keys[key.ID()] = key + } + + return changed +} + +type Role struct { + KeyIDs []string `json:"keyids"` + Threshold int `json:"threshold"` +} + +func (r *Role) AddKeysWithThreshold(keys []*Key, threshold int) bool { + roleIDs := make(map[string]struct{}) + for _, id := range r.KeyIDs { + roleIDs[id] = struct{}{} + } + changed := false + for _, key := range keys { + if _, ok := roleIDs[key.ID()]; !ok { + changed = true + r.KeyIDs = append(r.KeyIDs, key.ID()) + } + } + r.Threshold = threshold + return changed +} + +func (r *Root) Marshal() (*Signed, error) { + // Marshals the Root into a Signed type + b, err := cjson.EncodeCanonical(r) + if err != nil { + return nil, err + } + return &Signed{Signed: b}, nil +} + +func (r *Root) ValidKey(key *Key, role string) (string, error) { + // Checks if id is a valid key for role by matching the identity and issuer if specified. + // Returns the key ID or an error if invalid key. + fulcioKeyVal, err := GetFulcioKeyVal(key) + if err != nil { + return "", fmt.Errorf("error parsing signer key: %w", err) + } + + result := "" + for keyid, rootKey := range r.Keys { + fulcioRootKeyVal, err := GetFulcioKeyVal(rootKey) + if err != nil { + return "", fmt.Errorf("error parsing root key: %w", err) + } + if fulcioKeyVal.Identity == fulcioRootKeyVal.Identity { + if fulcioRootKeyVal.Issuer == "" || fulcioRootKeyVal.Issuer == fulcioKeyVal.Issuer { + result = keyid + break + } + } + } + if result == "" { + return "", errors.New("key not found in root keys") + } + + rootRole, ok := r.Roles[role] + if !ok { + return "", errors.New("invalid role") + } + for _, id := range rootRole.KeyIDs { + if id == result { + return result, nil + } + } + return "", errors.New("key not found in role") +} + +func (s *Signed) JSONMarshal(prefix, indent string) ([]byte, error) { + // Marshals Signed with prefix and indent. + b, err := cjson.EncodeCanonical(s) + if err != nil { + return []byte{}, err + } + + var out bytes.Buffer + if err := json.Indent(&out, b, prefix, indent); err != nil { + return []byte{}, err + } + + return out.Bytes(), nil +} + +func (s *Signed) AddOrUpdateSignature(key *Key, signature Signature) error { + root := &Root{} + if err := json.Unmarshal(s.Signed, root); err != nil { + return fmt.Errorf("unmarshalling root policy: %w", err) + } + var err error + signature.KeyID, err = root.ValidKey(key, "root") + if err != nil { + return errors.New("invalid root key") + } + signatures := []Signature{} + for _, sig := range s.Signatures { + if sig.KeyID != signature.KeyID { + signatures = append(signatures, sig) + } + } + signatures = append(signatures, signature) + s.Signatures = signatures + return nil +} diff --git a/pkg/tuf/policy_test.go b/pkg/tuf/policy_test.go new file mode 100644 index 000000000..0b8c6387e --- /dev/null +++ b/pkg/tuf/policy_test.go @@ -0,0 +1,94 @@ +// +// 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. + +// Contains root policy definitions. +// Eventually, this will move this to go-tuf definitions. + +package tuf + +import ( + "encoding/json" + "testing" +) + +func TestAddKey(t *testing.T) { + root := NewRoot() + publicKey := FulcioVerificationKey("test@rekor.dev", "") + if !root.AddKey(publicKey) { + t.Errorf("Adding new key failed") + } + if _, ok := root.Keys[publicKey.ID()]; !ok { + t.Errorf("Error adding public key") + } + // Add duplicate key. + if root.AddKey(publicKey) { + t.Errorf("Duplicate key should not add to dictionary") + } + if len(root.Keys) != 1 { + t.Errorf("Root keys should contain exactly one key.") + } +} + +func TestValidKey(t *testing.T) { + root := NewRoot() + publicKey := FulcioVerificationKey("test@rekor.dev", "https://accounts.google.com") + if !root.AddKey(publicKey) { + t.Errorf("Adding new key failed") + } + role := &Role{KeyIDs: []string{}, Threshold: 1} + role.AddKeysWithThreshold([]*Key{publicKey}, 2) + root.Roles["root"] = role + + if _, ok := root.Keys[publicKey.ID()]; !ok { + t.Errorf("Error adding public key") + } + if _, err := root.ValidKey(publicKey, "root"); err != nil { + t.Errorf("Error checking key validity %s", err) + } + // Now change issuer, and expect error. + publicKey = FulcioVerificationKey("test@rekor.dev", "") + if _, err := root.ValidKey(publicKey, "root"); err == nil { + t.Errorf("Expected invalid key with mismatching issuer") + } +} + +func TestRootRole(t *testing.T) { + root := NewRoot() + publicKey := FulcioVerificationKey("test@rekor.dev", "") + role := &Role{KeyIDs: []string{}, Threshold: 1} + role.AddKeysWithThreshold([]*Key{publicKey}, 2) + root.Roles["root"] = role + policy, err := root.Marshal() + if err != nil { + t.Errorf("Error marshalling root policy") + } + newRoot := Root{} + if err := json.Unmarshal(policy.Signed, &newRoot); err != nil { + t.Errorf("Error marshalling root policy") + } + rootRole, ok := newRoot.Roles["root"] + if !ok { + t.Errorf("Missing root role") + } + if len(rootRole.KeyIDs) != 1 { + t.Errorf("Missing root key ID") + } + if rootRole.KeyIDs[0] != publicKey.ID() { + t.Errorf("Bad root role key ID") + } + if rootRole.Threshold != 2 { + t.Errorf("Threshold incorrect") + } +} diff --git a/pkg/tuf/repository/root.json b/pkg/tuf/repository/root.json new file mode 100644 index 000000000..386ebe62c --- /dev/null +++ b/pkg/tuf/repository/root.json @@ -0,0 +1,144 @@ +{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "304502206eaef40564403ce572c6d062e0c9b0aab5e0223576133e081e1b495e8deb9efd02210080fd6f3464d759601b4afec596bbd5952f3a224cd06ed1cdfc3c399118752ba2" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "304502207baace02f56d8e6069f10b6ff098a26e7f53a7f9324ad62cffa0557bdeb9036c022100fb3032baaa090d0040c3f2fd872571c84479309b773208601d65948df87a9720" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304402205180c01905505dd88acd7a2dad979dd75c979b3722513a7bdedac88c6ae8dbeb022056d1ddf7a192f0b1c2c90ff487de2fb3ec9f0c03f66ea937c78d3b6a493504ca" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3046022100c8806d4647c514d80fd8f707d3369444c4fd1d0812a2d25f828e564c99790e3f022100bb51f12e862ef17a7d3da2ac103bebc5c7e792237006c4cafacd76267b249c2f" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2022-05-11T19:09:02.663975009Z", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d" + ], + "threshold": 1 + } + }, + "spec_version": "1.0", + "version": 2 + } +} \ No newline at end of file diff --git a/pkg/tuf/repository/targets/artifact.pub b/pkg/tuf/repository/targets/artifact.pub new file mode 100644 index 000000000..d6e745bdd --- /dev/null +++ b/pkg/tuf/repository/targets/artifact.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhyQCx0E9wQWSFI9ULGwy3BuRklnt +IqozONbbdbqz11hlRJy9c7SG+hdcFl9jE9uE/dwtuwU2MqU9T/cN0YkWww== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/pkg/tuf/repository/targets/ctfe.pub b/pkg/tuf/repository/targets/ctfe.pub new file mode 100644 index 000000000..1bb1488c9 --- /dev/null +++ b/pkg/tuf/repository/targets/ctfe.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3Pyu +dDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/pkg/tuf/repository/targets/fulcio.crt.pem b/pkg/tuf/repository/targets/fulcio.crt.pem new file mode 100644 index 000000000..6a06ff300 --- /dev/null +++ b/pkg/tuf/repository/targets/fulcio.crt.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq +MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx +MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu +ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy +A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas +taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm +MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE +FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u +Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx +Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup +Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/pkg/tuf/repository/targets/fulcio_v1.crt.pem b/pkg/tuf/repository/targets/fulcio_v1.crt.pem new file mode 100644 index 000000000..3afc46bb6 --- /dev/null +++ b/pkg/tuf/repository/targets/fulcio_v1.crt.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl +LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 +XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex +X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j +YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY +wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ +KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM +WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 +TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/pkg/tuf/repository/targets/rekor.0.pub b/pkg/tuf/repository/targets/rekor.0.pub new file mode 100644 index 000000000..050ef6014 --- /dev/null +++ b/pkg/tuf/repository/targets/rekor.0.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr +kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw== +-----END PUBLIC KEY----- diff --git a/pkg/tuf/repository/targets/rekor.json b/pkg/tuf/repository/targets/rekor.json new file mode 100644 index 000000000..f86930d53 --- /dev/null +++ b/pkg/tuf/repository/targets/rekor.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "ae0c689c6347ada7359df48934991f4e013193d6ddf3482a5ffb293f74f3b217", + "sig": "3045022076eadd73f6664bac5cc91f12d3a7ddcdd53f9bde661f147651196ff66e7235d1022100f7b3143792405f9e8a75331a05d4128bdf083de302801e99c3d027919a4b03da" + } + ], + "signed": { + "_type": "targets", + "expires": "2022-05-11T19:10:11Z", + "spec_version": "1.0", + "targets": { + "rekor.0.pub": { + "hashes": { + "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd", + "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35" + }, + "length": 178 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/pkg/tuf/repository/targets/rekor.pub b/pkg/tuf/repository/targets/rekor.pub new file mode 100644 index 000000000..050ef6014 --- /dev/null +++ b/pkg/tuf/repository/targets/rekor.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr +kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw== +-----END PUBLIC KEY----- diff --git a/pkg/tuf/signer.go b/pkg/tuf/signer.go new file mode 100644 index 000000000..3ad56874a --- /dev/null +++ b/pkg/tuf/signer.go @@ -0,0 +1,50 @@ +// +// 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 tuf + +import ( + "encoding/json" +) + +const ( + KeyTypeFulcio = "sigstore-oidc" + KeySchemeFulcio = "https://fulcio.sigstore.dev" +) + +var ( + KeyAlgorithms = []string{"sha256", "sha512"} +) + +type FulcioKeyVal struct { + Identity string `json:"identity"` + Issuer string `json:"issuer,omitempty"` +} + +func FulcioVerificationKey(email string, issuer string) *Key { + keyValBytes, _ := json.Marshal(FulcioKeyVal{Identity: email, Issuer: issuer}) + return &Key{ + Type: KeyTypeFulcio, + Scheme: KeySchemeFulcio, + Algorithms: KeyAlgorithms, + Value: keyValBytes, + } +} + +func GetFulcioKeyVal(key *Key) (*FulcioKeyVal, error) { + fulcioKeyVal := &FulcioKeyVal{} + err := json.Unmarshal(key.Value, fulcioKeyVal) + return fulcioKeyVal, err +} diff --git a/pkg/tuf/status_type.go b/pkg/tuf/status_type.go new file mode 100644 index 000000000..8a020e5d2 --- /dev/null +++ b/pkg/tuf/status_type.go @@ -0,0 +1,60 @@ +// 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 tuf + +import ( + "fmt" + "strings" +) + +type StatusKind int + +const ( + UnknownStatus StatusKind = iota + Active + Expired +) + +var toStatusString = map[StatusKind]string{ + UnknownStatus: "Unknown", + Active: "Active", + Expired: "Expired", +} + +func (s StatusKind) String() string { + return toStatusString[s] +} + +func (s StatusKind) MarshalText() ([]byte, error) { + str := s.String() + if len(str) == 0 { + return nil, fmt.Errorf("error while marshalling, int(StatusKind)=%d not valid", int(s)) + } + return []byte(s.String()), nil +} + +func (s *StatusKind) UnmarshalText(text []byte) error { + switch strings.ToLower(string(text)) { + case "unknown": + *s = UnknownStatus + case "active": + *s = Active + case "expired": + *s = Expired + default: + return fmt.Errorf("error while unmarshalling, StatusKind=%v not valid", string(text)) + } + return nil +} diff --git a/pkg/tuf/status_type_test.go b/pkg/tuf/status_type_test.go new file mode 100644 index 000000000..bc34a3451 --- /dev/null +++ b/pkg/tuf/status_type_test.go @@ -0,0 +1,84 @@ +// 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 tuf + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "testing" +) + +func TestMarshalStatusType(t *testing.T) { + statuses := []StatusKind{UnknownStatus, Active, Expired} + bytes, err := json.Marshal(statuses) + if err != nil { + t.Fatalf("expected no error marshalling struct, got: %v", err) + } + expected := `["Unknown","Active","Expired"]` + if string(bytes) != expected { + t.Fatalf("error while marshalling, expected: %s, got: %s", expected, bytes) + } +} + +func TestMarshalInvalidStatusType(t *testing.T) { + invalidStatus := 42 + statuses := []StatusKind{StatusKind(invalidStatus)} + bytes, err := json.Marshal(statuses) + if bytes != nil { + t.Fatalf("expected error marshalling struct, got: %v", bytes) + } + expectedErr := fmt.Sprintf("error while marshalling, int(StatusKind)=%d not valid", invalidStatus) + if !strings.Contains(err.Error(), expectedErr) { + t.Fatalf("expected error marshalling struct, expected: %v, got: %v", expectedErr, err) + } +} + +func TestUnmarshalStatusType(t *testing.T) { + var statuses []StatusKind + j := json.RawMessage(`["expired", "active", "unknown"]`) + err := json.Unmarshal(j, &statuses) + if err != nil { + t.Fatalf("expected no error unmarshalling struct, got: %v", err) + } + if !reflect.DeepEqual(statuses, []StatusKind{Expired, Active, UnknownStatus}) { + t.Fatalf("expected [Expired, Active, Unknown], got: %v", statuses) + } +} + +func TestUnmarshalStatusTypeCapitalization(t *testing.T) { + // Any capitalization is allowed. + var statuses []StatusKind + j := json.RawMessage(`["eXpIrEd", "aCtIvE", "uNkNoWn"]`) + err := json.Unmarshal(j, &statuses) + if err != nil { + t.Fatalf("expected no error unmarshalling struct, got: %v", err) + } + if !reflect.DeepEqual(statuses, []StatusKind{Expired, Active, UnknownStatus}) { + t.Fatalf("expected [Expired, Active, Unknown], got: %v", statuses) + } +} + +func TestUnmarshalInvalidStatusType(t *testing.T) { + var statuses []StatusKind + invalidStatus := "invalid" + j := json.RawMessage(fmt.Sprintf(`["%s"]`, invalidStatus)) + err := json.Unmarshal(j, &statuses) + expectedErr := fmt.Sprintf("error while unmarshalling, StatusKind=%s not valid", invalidStatus) + if !strings.Contains(err.Error(), expectedErr) { + t.Fatalf("expected error unmarshalling struct, expected: %v, got: %v", expectedErr, err) + } +} diff --git a/pkg/tuf/testutils.go b/pkg/tuf/testutils.go new file mode 100644 index 000000000..c4efe5f13 --- /dev/null +++ b/pkg/tuf/testutils.go @@ -0,0 +1,130 @@ +// +// 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 tuf + +import ( + "context" + "crypto/x509" + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" + "github.com/theupdateframework/go-tuf" +) + +type TestSigstoreRoot struct { + Rekor signature.Verifier + FulcioCertificate *x509.Certificate + // TODO: Include a CTFE key if/when cosign verifies SCT. +} + +// This creates a new sigstore TUF repo whose signers can be used to create dynamic +// signed Rekor entries. +func NewSigstoreTufRepo(t *testing.T, root TestSigstoreRoot) (tuf.LocalStore, *tuf.Repo) { + td := t.TempDir() + ctx := context.Background() + remote := tuf.FileSystemStore(td, nil) + r, err := tuf.NewRepo(remote) + if err != nil { + t.Error(err) + } + if err := r.Init(false); err != nil { + t.Error(err) + } + + for _, role := range []string{"root", "targets", "snapshot", "timestamp"} { + if _, err := r.GenKey(role); err != nil { + t.Error(err) + } + } + targetsPath := filepath.Join(td, "staged", "targets") + if err := os.MkdirAll(filepath.Dir(targetsPath), 0755); err != nil { + t.Error(err) + } + // Add the rekor key target + pk, err := root.Rekor.PublicKey(options.WithContext(ctx)) + if err != nil { + t.Error(err) + } + b, err := x509.MarshalPKIXPublicKey(pk) + if err != nil { + t.Error(err) + } + rekorPath := "rekor.pub" + rekorData := cryptoutils.PEMEncode(cryptoutils.PublicKeyPEMType, b) + if err := ioutil.WriteFile(filepath.Join(targetsPath, rekorPath), rekorData, 0600); err != nil { + t.Error(err) + } + scmRekor, err := json.Marshal(&sigstoreCustomMetadata{Sigstore: customMetadata{Usage: Rekor, Status: Active}}) + if err != nil { + t.Error(err) + } + if err := r.AddTarget("rekor.pub", scmRekor); err != nil { + t.Error(err) + } + // Add Fulcio Certificate information. + fulcioPath := "fulcio.crt.pem" + fulcioData := cryptoutils.PEMEncode(cryptoutils.CertificatePEMType, root.FulcioCertificate.Raw) + if err := ioutil.WriteFile(filepath.Join(targetsPath, fulcioPath), fulcioData, 0600); err != nil { + t.Error(err) + } + scmFulcio, err := json.Marshal(&sigstoreCustomMetadata{Sigstore: customMetadata{Usage: Fulcio, Status: Active}}) + if err != nil { + t.Error(err) + } + if err := r.AddTarget(fulcioPath, scmFulcio); err != nil { + t.Error(err) + } + if err := r.Snapshot(); err != nil { + t.Error(err) + } + if err := r.Timestamp(); err != nil { + t.Error(err) + } + if err := r.Commit(); err != nil { + t.Error(err) + } + // Serve remote repository. + s := httptest.NewServer(http.FileServer(http.Dir(filepath.Join(td, "repository")))) + defer s.Close() + + // Initialize with custom root. + tufRoot := t.TempDir() + t.Setenv("TUF_ROOT", tufRoot) + meta, err := remote.GetMeta() + if err != nil { + t.Error(err) + } + rootBytes, ok := meta["root.json"] + if !ok { + t.Error(err) + } + resetForTests() + if err := Initialize(ctx, s.URL, rootBytes); err != nil { + t.Error(err) + } + t.Cleanup(func() { + resetForTests() + }) + return remote, r +} diff --git a/pkg/tuf/usage_type.go b/pkg/tuf/usage_type.go new file mode 100644 index 000000000..4ea7ad04f --- /dev/null +++ b/pkg/tuf/usage_type.go @@ -0,0 +1,64 @@ +// 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 tuf + +import ( + "fmt" + "strings" +) + +type UsageKind int + +const ( + UnknownUsage UsageKind = iota + Fulcio + Rekor + CTFE +) + +var toUsageString = map[UsageKind]string{ + UnknownUsage: "Unknown", + Fulcio: "Fulcio", + Rekor: "Rekor", + CTFE: "CTFE", +} + +func (u UsageKind) String() string { + return toUsageString[u] +} + +func (u UsageKind) MarshalText() ([]byte, error) { + str := u.String() + if len(str) == 0 { + return nil, fmt.Errorf("error while marshalling, int(UsageKind)=%d not valid", int(u)) + } + return []byte(u.String()), nil +} + +func (u *UsageKind) UnmarshalText(text []byte) error { + switch strings.ToLower(string(text)) { + case "unknown": + *u = UnknownUsage + case "fulcio": + *u = Fulcio + case "rekor": + *u = Rekor + case "ctfe": + *u = CTFE + default: + return fmt.Errorf("error while unmarshalling, UsageKind=%v not valid", string(text)) + } + return nil +} diff --git a/pkg/tuf/usage_type_test.go b/pkg/tuf/usage_type_test.go new file mode 100644 index 000000000..9fca0cf73 --- /dev/null +++ b/pkg/tuf/usage_type_test.go @@ -0,0 +1,84 @@ +// 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 tuf + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "testing" +) + +func TestMarshalUsageType(t *testing.T) { + usages := []UsageKind{UnknownUsage, Fulcio, Rekor, CTFE} + bytes, err := json.Marshal(usages) + if err != nil { + t.Fatalf("expected no error marshalling struct, got: %v", err) + } + expected := `["Unknown","Fulcio","Rekor","CTFE"]` + if string(bytes) != expected { + t.Fatalf("error while marshalling, expected: %s, got: %s", expected, bytes) + } +} + +func TestMarshalInvalidUsageType(t *testing.T) { + invalidUsage := 42 + usages := []UsageKind{UsageKind(invalidUsage)} + bytes, err := json.Marshal(usages) + if bytes != nil { + t.Fatalf("expected error marshalling struct, got: %v", bytes) + } + expectedErr := fmt.Sprintf("error while marshalling, int(UsageKind)=%d not valid", invalidUsage) + if !strings.Contains(err.Error(), expectedErr) { + t.Fatalf("expected error marshalling struct, expected: %v, got: %v", expectedErr, err) + } +} + +func TestUnmarshalUsageType(t *testing.T) { + var usages []UsageKind + j := json.RawMessage(`["fulcio", "rekor", "ctfe", "unknown"]`) + err := json.Unmarshal(j, &usages) + if err != nil { + t.Fatalf("expected no error unmarshalling struct, got: %v", err) + } + if !reflect.DeepEqual(usages, []UsageKind{Fulcio, Rekor, CTFE, UnknownUsage}) { + t.Fatalf("expected [Fulcio, Rekor, CTFE, UnknownUsage], got: %v", usages) + } +} + +func TestUnmarshalUsageTypeCapitalization(t *testing.T) { + // Any capitalization is allowed. + var usages []UsageKind + j := json.RawMessage(`["fUlCiO", "rEkOr", "cTfE", "uNkNoWn"]`) + err := json.Unmarshal(j, &usages) + if err != nil { + t.Fatalf("expected no error unmarshalling struct, got: %v", err) + } + if !reflect.DeepEqual(usages, []UsageKind{Fulcio, Rekor, CTFE, UnknownUsage}) { + t.Fatalf("expected [Fulcio, Rekor, CTFE, UnknownUsage], got: %v", usages) + } +} + +func TestUnmarshalInvalidUsageType(t *testing.T) { + var usages []UsageKind + invalidUsage := "invalid" + j := json.RawMessage(fmt.Sprintf(`["%s"]`, invalidUsage)) + err := json.Unmarshal(j, &usages) + expectedErr := fmt.Sprintf("error while unmarshalling, UsageKind=%s not valid", invalidUsage) + if !strings.Contains(err.Error(), expectedErr) { + t.Fatalf("expected error unmarshalling struct, expected: %v, got: %v", expectedErr, err) + } +}