├── .github ├── codecov.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── examples.yml │ ├── linting.yml │ └── tests.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── .minder.yaml ├── CODE-OF-CONDUCT.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── NOTICE ├── README.md ├── SECURITY.md ├── examples ├── README.md ├── cli │ ├── README.md │ ├── tuf-client │ │ ├── README.md │ │ ├── cmd │ │ │ ├── get.go │ │ │ ├── init.go │ │ │ ├── reset.go │ │ │ └── root.go │ │ └── main.go │ └── tuf │ │ ├── README.md │ │ ├── cmd │ │ ├── init.go │ │ └── root.go │ │ └── main.go ├── client │ └── client_example.go ├── multirepo │ ├── client │ │ ├── README.md │ │ ├── client_example.go │ │ └── root.json │ └── repository │ │ ├── README.md │ │ ├── generate_metadata.go │ │ ├── metadata │ │ ├── 1.root.json │ │ ├── 1.snapshot.json │ │ ├── 1.targets.json │ │ └── timestamp.json │ │ └── targets │ │ ├── 562fc7cffe872542a430342995998546e3949dc3acbe7d37668dc76d657032ff.map.json │ │ ├── map.json │ │ ├── sigstore-tuf-root │ │ ├── e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json │ │ └── root.json │ │ └── staging │ │ ├── e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json │ │ └── root.json └── repository │ ├── basic_repository.go │ └── repository │ ├── repository.go │ └── repository_test.go ├── go.mod ├── go.sum ├── internal └── testutils │ ├── repository_data │ ├── keystore │ │ ├── delegation_key │ │ ├── delegation_key.pub │ │ ├── root_key │ │ ├── root_key.pub │ │ ├── root_key2 │ │ ├── root_key2.pub │ │ ├── snapshot_key │ │ ├── snapshot_key.pub │ │ ├── targets_key │ │ ├── targets_key.pub │ │ ├── timestamp_key │ │ └── timestamp_key.pub │ └── repository │ │ ├── metadata │ │ ├── 1.root.json │ │ ├── role1.json │ │ ├── role2.json │ │ ├── root.json │ │ ├── snapshot.json │ │ ├── targets.json │ │ └── timestamp.json │ │ └── targets │ │ ├── file1.txt │ │ ├── file2.txt │ │ └── file3.txt │ ├── rsapss │ └── signer.go │ ├── setup.go │ ├── signer │ └── signer.go │ └── simulator │ ├── repository_simulator.go │ └── repository_simulator_setup.go └── metadata ├── config ├── config.go └── config_test.go ├── errors.go ├── fetcher ├── fetcher.go └── fetcher_test.go ├── keys.go ├── logger.go ├── logger_test.go ├── marshal.go ├── metadata.go ├── metadata_api_test.go ├── metadata_test.go ├── multirepo └── multirepo.go ├── trustedmetadata ├── trustedmetadata.go └── trustedmetadata_test.go ├── types.go └── updater ├── updater.go ├── updater_consistent_snapshot_test.go └── updater_top_level_update_test.go /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | # basic 6 | target: auto 7 | threshold: 5% 8 | patch: off -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Monitor Go dependencies 9 | - package-ecosystem: "gomod" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | time: "10:00" 14 | commit-message: 15 | prefix: "chore" 16 | include: "scope" 17 | open-pull-requests-limit: 10 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 The Update Framework Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License 15 | # 16 | # SPDX-License-Identifier: Apache-2.0 17 | on: 18 | pull_request: 19 | push: 20 | branches: 21 | - "master" 22 | tags: 23 | - "v*" 24 | name: CI 25 | jobs: 26 | linting: 27 | uses: ./.github/workflows/linting.yml 28 | tests: 29 | uses: ./.github/workflows/tests.yml 30 | examples: 31 | uses: ./.github/workflows/examples.yml 32 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [master] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [master] 20 | schedule: 21 | - cron: "25 14 * * 6" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["go"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout code 41 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 42 | 43 | - name: Setup - Go 44 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 45 | with: 46 | go-version-file: 'go.mod' 47 | cache: true 48 | # Initializes the CodeQL tools for scanning. 49 | - name: Initialize CodeQL 50 | uses: github/codeql-action/init@47b3d888fe66b639e431abf22ebca059152f1eea # 3.24.5 51 | with: 52 | languages: ${{ matrix.language }} 53 | # If you wish to specify custom queries, you can do so here or in a config file. 54 | # By default, queries listed here will override any specified in a config file. 55 | # Prefix the list here with "+" to use these queries and those in the config file. 56 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Autobuild 61 | uses: github/codeql-action/autobuild@47b3d888fe66b639e431abf22ebca059152f1eea # 3.24.5 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 https://git.io/JvXDl 65 | 66 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 67 | # and modify them (or add more) to build your code if your project 68 | # uses a compiled language 69 | 70 | #- run: | 71 | # make bootstrap 72 | # make release 73 | 74 | - name: Perform CodeQL Analysis 75 | uses: github/codeql-action/analyze@47b3d888fe66b639e431abf22ebca059152f1eea # 3.24.5 76 | -------------------------------------------------------------------------------- /.github/workflows/examples.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 The Update Framework Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License 15 | # 16 | # SPDX-License-Identifier: Apache-2.0 17 | on: 18 | workflow_call: 19 | # Not exactly right to test functionality in such a way, but it does act as a set of end-to-end test cases for the time 20 | # being, nevertheless should be updated 21 | name: Examples 22 | jobs: 23 | client: 24 | strategy: 25 | fail-fast: false # Keep running if one leg fails. 26 | matrix: 27 | os: [ubuntu-latest, macos-latest, windows-latest] 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 32 | 33 | - name: Setup - Go 34 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 35 | with: 36 | go-version-file: 'go.mod' 37 | cache: true 38 | 39 | - run: make example-client 40 | 41 | repository: 42 | strategy: 43 | fail-fast: false # Keep running if one leg fails. 44 | matrix: 45 | os: [ubuntu-latest, macos-latest, windows-latest] 46 | runs-on: ${{ matrix.os }} 47 | steps: 48 | - name: Checkout code 49 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 50 | 51 | - name: Setup - Go 52 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 53 | with: 54 | go-version-file: 'go.mod' 55 | cache: true 56 | 57 | - run: make example-repository 58 | 59 | multirepo: 60 | strategy: 61 | fail-fast: false # Keep running if one leg fails. 62 | matrix: 63 | os: [ubuntu-latest, macos-latest, windows-latest] 64 | runs-on: ${{ matrix.os }} 65 | steps: 66 | - name: Checkout code 67 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 68 | 69 | - name: Setup - Go 70 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 71 | with: 72 | go-version-file: 'go.mod' 73 | cache: true 74 | 75 | - run: make example-multirepo 76 | 77 | tuf-client-cli: 78 | strategy: 79 | fail-fast: false # Keep running if one leg fails. 80 | matrix: 81 | os: [ubuntu-latest, macos-latest, windows-latest] 82 | runs-on: ${{ matrix.os }} 83 | steps: 84 | - name: Checkout code 85 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 86 | 87 | - name: Setup - Go 88 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 89 | with: 90 | go-version-file: 'go.mod' 91 | cache: true 92 | 93 | - run: make example-tuf-client-cli 94 | 95 | root-signing: 96 | strategy: 97 | fail-fast: false # Keep running if one leg fails. 98 | matrix: 99 | os: [ubuntu-latest, macos-latest, windows-latest] 100 | runs-on: ${{ matrix.os }} 101 | steps: 102 | - name: Checkout code 103 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 104 | 105 | - name: Setup - Go 106 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 107 | with: 108 | go-version-file: 'go.mod' 109 | cache: true 110 | 111 | - run: make example-root-signing 112 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 The Update Framework Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License 15 | # 16 | # SPDX-License-Identifier: Apache-2.0 17 | on: 18 | workflow_call: 19 | name: Linting 20 | jobs: 21 | govulncheck_job: 22 | runs-on: ubuntu-latest 23 | name: govulncheck 24 | steps: 25 | - id: govulncheck 26 | uses: golang/govulncheck-action@3a32958c2706f7048305d5a2e53633d7e37e97d0 27 | continue-on-error: true 28 | with: 29 | go-version-file: 'go.mod' 30 | go-package: ./... 31 | 32 | golangci: 33 | name: golangci-lint 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout code 37 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 38 | 39 | - name: Setup - Go 40 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 41 | with: 42 | go-version-file: 'go.mod' 43 | cache: true 44 | 45 | - name: Run golangci-lint 46 | uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 47 | with: 48 | # Require: The version of golangci-lint to use. 49 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. 50 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. 51 | version: v2.1.1 52 | args: --timeout 5m --verbose 53 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 The Update Framework Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License 15 | # 16 | # SPDX-License-Identifier: Apache-2.0 17 | on: 18 | workflow_call: 19 | name: Tests 20 | jobs: 21 | run: 22 | name: Run 23 | strategy: 24 | fail-fast: false # Keep running if one leg fails. 25 | matrix: 26 | os: [ubuntu-latest, macos-latest, windows-latest] 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - name: Set git to use LF 30 | run: git config --global core.autocrlf false 31 | 32 | - name: Checkout code 33 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 34 | 35 | - name: Setup - Go 36 | uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 37 | with: 38 | go-version-file: 'go.mod' 39 | cache: true 40 | 41 | - name: Run tests 42 | run: go test -race -covermode=atomic -coverpkg=./metadata/... -coverprofile=coverage.out ./... 43 | 44 | - name: Send coverage 45 | uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | dist/ 3 | .idea/ 4 | *~ 5 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | exclusions: 4 | generated: lax 5 | presets: 6 | - comments 7 | - common-false-positives 8 | - legacy 9 | - std-error-handling 10 | paths: 11 | - third_party$ 12 | - builtin$ 13 | - examples$ 14 | formatters: 15 | exclusions: 16 | generated: lax 17 | paths: 18 | - third_party$ 19 | - builtin$ 20 | - examples$ 21 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - skip: true 11 | # - env: 12 | # - CGO_ENABLED=0 13 | # goos: 14 | # - linux 15 | # - windows 16 | # - darwin 17 | 18 | archives: 19 | - format: tar.gz 20 | # this name template makes the OS and Arch compatible with the results of uname. 21 | name_template: >- 22 | {{ .ProjectName }}_ 23 | {{- title .Os }}_ 24 | {{- if eq .Arch "amd64" }}x86_64 25 | {{- else if eq .Arch "386" }}i386 26 | {{- else }}{{ .Arch }}{{ end }} 27 | {{- if .Arm }}v{{ .Arm }}{{ end }} 28 | # use zip for windows archives 29 | format_overrides: 30 | - goos: windows 31 | format: zip 32 | checksum: 33 | name_template: "checksums.txt" 34 | snapshot: 35 | name_template: "{{ incpatch .Version }}-next" 36 | source: 37 | enabled: true 38 | changelog: 39 | use: github 40 | groups: 41 | - title: "Breaking changes" 42 | regexp: "^.*(?:BREAKING CHANGE)|![(\\w)]*:+.*$" 43 | order: 0 44 | - title: Features 45 | regexp: "^.*feat[(\\w)]*:+.*$" 46 | order: 1 47 | - title: "Bug fixes" 48 | regexp: "^.*fix[(\\w)]*:+.*$" 49 | order: 2 50 | - title: Others 51 | order: 999 52 | release: 53 | # If set to auto, will mark the release as not ready for production 54 | # in case there is an indicator for this in the tag e.g. v1.0.0-rc1 55 | prerelease: auto 56 | -------------------------------------------------------------------------------- /.minder.yaml: -------------------------------------------------------------------------------- 1 | # This is the Minder profile file used for securely monitoring rdimitrov/go-tuf-metadata. 2 | # For more information, see https://github.com/stacklok/minder. 3 | --- 4 | version: v1 5 | type: profile 6 | name: go-tuf-metadata 7 | context: 8 | provider: github 9 | alert: "on" 10 | remediate: "on" 11 | repository: 12 | - type: secret_scanning 13 | def: 14 | enabled: true 15 | - type: secret_push_protection 16 | def: 17 | enabled: true 18 | - type: github_actions_allowed 19 | def: 20 | allowed_actions: all 21 | # - type: allowed_selected_actions 22 | # def: 23 | # github_owned_allowed: true 24 | # verified_allowed: true 25 | # patterns_allowed: [] 26 | - type: default_workflow_permissions 27 | def: 28 | default_workflow_permissions: write 29 | can_approve_pull_request_reviews: true 30 | - type: codeql_enabled 31 | def: 32 | languages: [go] 33 | schedule_interval: '30 4-6 * * *' 34 | - type: actions_check_pinned_tags 35 | def: {} 36 | - type: dependabot_configured 37 | def: 38 | package_ecosystem: gomod 39 | schedule_interval: weekly 40 | apply_if_file: go.mod 41 | - type: dockerfile_no_latest_tag 42 | def: {} 43 | # - type: trivy_action_enabled 44 | # def: {} 45 | - type: branch_protection_enabled 46 | params: 47 | branch: main 48 | def: {} 49 | - type: branch_protection_allow_deletions 50 | params: 51 | branch: main 52 | def: 53 | allow_deletions: false 54 | - type: branch_protection_allow_force_pushes 55 | params: 56 | branch: main 57 | def: 58 | allow_force_pushes: true 59 | # - type: branch_protection_enforce_admins 60 | # params: 61 | # branch: main 62 | # def: 63 | # enforce_admins: true 64 | - type: branch_protection_lock_branch 65 | params: 66 | branch: main 67 | def: 68 | lock_branch: false 69 | - type: branch_protection_require_conversation_resolution 70 | params: 71 | branch: main 72 | def: 73 | required_conversation_resolution: true 74 | - type: branch_protection_require_linear_history 75 | params: 76 | branch: main 77 | def: 78 | required_linear_history: true 79 | - type: branch_protection_require_pull_request_approving_review_count 80 | params: 81 | branch: main 82 | def: 83 | required_approving_review_count: 1 84 | - type: branch_protection_require_pull_request_code_owners_review 85 | params: 86 | branch: main 87 | def: 88 | require_code_owner_reviews: true 89 | - type: branch_protection_require_pull_request_dismiss_stale_reviews 90 | params: 91 | branch: main 92 | def: 93 | dismiss_stale_reviews: true 94 | - type: branch_protection_require_pull_request_last_push_approval 95 | params: 96 | branch: main 97 | def: 98 | require_last_push_approval: true 99 | - type: branch_protection_require_pull_requests 100 | params: 101 | branch: main 102 | def: 103 | required_pull_request_reviews: true 104 | - type: branch_protection_require_signatures 105 | params: 106 | branch: main 107 | def: 108 | required_signatures: false 109 | - type: license 110 | def: 111 | license_filename: LICENSE 112 | license_type: "Apache License" 113 | # artifact: 114 | # - type: artifact_signature 115 | # params: 116 | # tags: [main] 117 | # name: test 118 | # def: 119 | # is_signed: true 120 | # is_verified: true 121 | # is_bundle_verified: true 122 | pull_request: 123 | - type: pr_vulnerability_check 124 | def: 125 | action: review 126 | ecosystem_config: 127 | - name: go 128 | vulnerability_database_type: osv 129 | vulnerability_database_endpoint: https://vuln.go.dev 130 | package_repository: 131 | url: https://proxy.golang.org 132 | sum_repository: 133 | url: https://sum.golang.org 134 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at oss-coc@vmware.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @theupdateframework/go-tuf-maintainers 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Developer Certificate of Origin 4 | 5 | Before you start working with this project, please read our [Developer Certificate of Origin](https://cla.vmware.com/dco). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. 6 | 7 | ## Contribution Process 8 | 9 | * Follow the [GitHub process](https://help.github.com/articles/fork-a-repo) 10 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers should review changes promptly. 2 | # Github will auto-assign the below maintainers to a pull request. 3 | 4 | ## Current 5 | 6 | The [@theupdateframework/go-tuf-maintainers](https://github.com/orgs/theupdateframework/teams/go-tuf-maintainers) team is: 7 | 8 | | Maintainer | GitHub ID | Affiliation | 9 | | -------------------------- | ---------------------------------------------------------- | ------------------------------------------ | 10 | | Fredrik Skogman | [@kommendorkapten](https://github.com/kommendorkapten) | [@GitHub](https://github.com/github) | 11 | | Marina Moore | [@mnm678](https://github.com/mnm678) | NYU | 12 | | Marvin Drees | [@MDr164](https://github.com/MDr164) | [@9elements](https://github.com/9elements) | 13 | | Radoslav Dimitrov | [@rdimitrov](https://github.com/rdimitrov) | [@Stacklok](https://github.com/stacklok) | 14 | 15 | 16 | ## Emeritus 17 | 18 | We are deeply indebted to our emeritus maintainers below: 19 | 20 | | Maintainer | GitHub ID | 21 | | -------------------------- | ---------------------------------------------------------- | 22 | | Joshua Lock | [@joshuagl](https://github.com/joshuagl) | 23 | | Trishank Karthik Kuppusamy | [@trishankatdatadog](https://github.com/trishankatdatadog) | 24 | | Zack Newman | [@znewman01](https://github.com/znewman01) | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2024 The Update Framework Authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License 15 | # 16 | # SPDX-License-Identifier: Apache-2.0 17 | 18 | # We want to use bash 19 | SHELL:=/bin/bash 20 | 21 | # Set environment variables 22 | CLIS:=tuf-client # tuf 23 | 24 | # Default target 25 | .PHONY: default 26 | default: build 27 | 28 | ##################### 29 | # build section 30 | ##################### 31 | 32 | # Build 33 | .PHONY: build 34 | build: $(addprefix build-, $(CLIS)) 35 | 36 | # Target for building a Go binary 37 | .PHONY: build-% 38 | build-%: 39 | @echo "Building $*" 40 | @go build -o $* examples/cli/$*/main.go 41 | 42 | ##################### 43 | # test section 44 | ##################### 45 | 46 | # Test target 47 | .PHONY: test 48 | test: 49 | go test -race -covermode atomic ./... 50 | 51 | ##################### 52 | # lint section 53 | ##################### 54 | 55 | .PHONY: lint 56 | lint: 57 | golangci-lint run 58 | 59 | .PHONY: fmt 60 | fmt: 61 | go fmt ./... 62 | 63 | ##################### 64 | # examples section 65 | ##################### 66 | 67 | # Target for running all examples 68 | .PHONY: example-all 69 | example-all: example-client example-repository example-multirepo example-tuf-client-cli example-root-signing 70 | 71 | # Target for demoing the examples/client/client_example.go 72 | .PHONY: example-client 73 | example-client: 74 | @echo "Executing the following example - client/client_example.go" 75 | @cd examples/client/ && go run . 76 | 77 | # Target for demoing the examples/repository/basic_repository.go 78 | .PHONY: example-repository 79 | example-repository: 80 | @echo "Executing the following example - repository/basic_repository.go" 81 | @cd examples/repository/ && go run . 82 | 83 | # Target for demoing the examples/multirepo/client/client_example.go 84 | .PHONY: example-multirepo 85 | example-multirepo: 86 | @echo "Executing the following example - multirepo/client/client_example.go" 87 | @cd examples/multirepo/client/ && go run . 88 | 89 | # Target for demoing the tuf-client cli 90 | .PHONY: example-tuf-client-cli 91 | example-tuf-client-cli: build-tuf-client 92 | @echo "Clearing any leftover artifacts..." 93 | ./tuf-client reset --force 94 | @echo "Initializing the following https://jku.github.io/tuf-demo/ TUF repository" 95 | @sleep 2 96 | ./tuf-client init --url https://jku.github.io/tuf-demo/metadata 97 | @echo "Downloading the following target file - rdimitrov/artifact-example.md" 98 | @sleep 2 99 | ./tuf-client get --url https://jku.github.io/tuf-demo/metadata --turl https://jku.github.io/tuf-demo/targets rdimitrov/artifact-example.md 100 | 101 | # Target for demoing the tuf-client cli with root-signing repo 102 | .PHONY: example-root-signing 103 | example-root-signing: build-tuf-client 104 | @echo "Clearing any leftover artifacts..." 105 | ./tuf-client reset --force 106 | @echo "Downloading the initial root of trust" 107 | @curl -L "https://tuf-repo-cdn.sigstore.dev/5.root.json" > root.json 108 | @echo "Initializing the following https://tuf-repo-cdn.sigstore.dev TUF repository" 109 | @sleep 2 110 | ./tuf-client init --url https://tuf-repo-cdn.sigstore.dev --file root.json 111 | @echo "Downloading the following target file - rekor.pub" 112 | @sleep 2 113 | ./tuf-client get --url https://tuf-repo-cdn.sigstore.dev --turl https://tuf-repo-cdn.sigstore.dev/targets rekor.pub 114 | 115 | # Clean target 116 | .PHONY: clean 117 | clean: 118 | @rm -rf examples/multirepo/client/bootstrap/ 119 | @rm -rf examples/multirepo/client/download/ 120 | @rm -rf examples/multirepo/client/metadata/ 121 | @rm -rf examples/repository/tmp* 122 | @rm -rf examples/client/tmp* 123 | @rm -rf tuf_download 124 | @rm -rf tuf_metadata 125 | @rm -f tuf-client 126 | @rm -f root.json 127 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2024 The Update Framework Authors 2 | 3 | Apache 2.0 License 4 | Copyright 2024 The Apache Software Foundation 5 | 6 | This product includes software developed at 7 | The Apache Software Foundation (/). 8 | 9 | SPDX-License-Identifier: Apache-2.0 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/theupdateframework/go-tuf/ci.yml?branch=master) 2 | [![codecov](https://codecov.io/github/theupdateframework/go-tuf/branch/master/graph/badge.svg?token=2ZUA68ZL13)](https://codecov.io/github/theupdateframework/go-tuf) 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/theupdateframework/go-tuf.svg)](https://pkg.go.dev/github.com/theupdateframework/go-tuf) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/theupdateframework/go-tuf)](https://goreportcard.com/report/github.com/theupdateframework/go-tuf) 5 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 6 | 7 | # TUF go-tuf/v2 - Framework for Securing Software Update Systems 8 | 9 | ---------------------------- 10 | 11 | [The Update Framework (TUF)](https://theupdateframework.io/) is a framework for 12 | secure content delivery and updates. It protects against various types of 13 | supply chain attacks and provides resilience to compromise. 14 | 15 | ## About The Update Framework 16 | 17 | ---------------------------- 18 | The Update Framework (TUF) design helps developers maintain the security of a 19 | software update system, even against attackers that compromise the repository 20 | or signing keys. 21 | TUF provides a flexible 22 | [specification](https://github.com/theupdateframework/specification/blob/master/tuf-spec.md) 23 | defining functionality that developers can use in any software update system or 24 | re-implement to fit their needs. 25 | 26 | TUF is hosted by the [Linux Foundation](https://www.linuxfoundation.org/) as 27 | part of the [Cloud Native Computing Foundation](https://www.cncf.io/) (CNCF) 28 | and its design is [used in production](https://theupdateframework.io/adoptions/) 29 | by various tech companies and open-source organizations. 30 | 31 | Please see [TUF's website](https://theupdateframework.com/) for more information about TUF! 32 | 33 | ## Overview 34 | 35 | ---------------------------- 36 | 37 | The go-tuf v2 project provides a lightweight library with the following functionality: 38 | 39 | * creation, reading, and writing of TUF metadata 40 | * an easy object-oriented approach for interacting with TUF metadata 41 | * consistent snapshots 42 | * signing and verifying TUF metadata 43 | * ED25519, RSA, and ECDSA key types referenced by the latest TUF specification 44 | * top-level role delegation 45 | * target delegation via standard and hash bin delegations 46 | * support of [succinct hash bin delegations](https://github.com/theupdateframework/taps/blob/master/tap15.md) which significantly reduce the size of the TUF metadata 47 | * support for unrecognized fields within the metadata (i.e. preserved and accessible through `root.Signed.UnrecognizedFields["some-unknown-field"]`, also used for verifying/signing (if included in the Signed portion of the metadata)) 48 | * TUF client API 49 | * TUF multi-repository client API (implements [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md)) 50 | 51 | ## Examples 52 | 53 | ---------------------------- 54 | 55 | There are several examples that can act as a guideline on how to use the library and its features. Some of which are: 56 | 57 | * [basic_repository.go](examples/repository/basic_repository.go) example which demonstrates how to *manually* create and 58 | maintain repository metadata using the low-level Metadata API. 59 | 60 | To try it - run `make example-repository` (the artifacts will be located at `examples/repository/`). 61 | 62 | * [client_example.go](examples/client/client_example.go) which demonstrates how to implement a client using the [updater](metadata/updater/updater.go) package. 63 | 64 | To try it - run `make example-client` (the artifacts will be located at `examples/client/`) 65 | 66 | * [tuf-client CLI](examples/cli/tuf-client/) - a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification. 67 | 68 | To try it - run `make example-tuf-client-cli` 69 | 70 | * [multi-repository client example (TAP4)](examples/multirepo/client/client_example.go) which demonstrates how to implement a multi-repository TUF client using the [multirepo](metadata/multirepo/multirepo.go) package. 71 | 72 | To try it - run `make example-multirepo` 73 | 74 | ## Package details 75 | 76 | ---------------------------- 77 | 78 | ### The `metadata` package 79 | 80 | * The `metadata` package provides access to a Metadata file abstraction that closely 81 | follows the TUF specification’s document formats. This API handles de/serialization 82 | to and from files and bytes. It also covers the process of creating and verifying metadata 83 | signatures and makes it easier to access and modify metadata content. It is purely 84 | focused on individual pieces of Metadata and provides no concepts like “repository” 85 | or “update workflow”. 86 | 87 | ### The `trustedmetadata` package 88 | 89 | * A `TrustedMetadata` instance ensures that the collection of metadata in it is valid 90 | and trusted through the whole client update workflow. It provides easy ways to update 91 | the metadata with the caller making decisions on what is updated. 92 | 93 | ### The `config` package 94 | 95 | * The `config` package stores configuration for an ``Updater`` instance. 96 | 97 | ### The `fetcher` package 98 | 99 | * The `fetcher` package defines an interface for abstract network download. 100 | 101 | ### The `updater` package 102 | 103 | * The `updater` package provides an implementation of the TUF client workflow. 104 | It provides ways to query and download target files securely while handling the 105 | TUF update workflow behind the scenes. It is implemented on top of the Metadata API 106 | and can be used to implement various TUF clients with relatively little effort. 107 | 108 | ### The `multirepo` package 109 | 110 | * The `multirepo` package provides an implementation of [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md). It provides a secure search for particular targets across multiple repositories. It provides the functionality for how multiple repositories with separate roots of trust can be required to sign off on the same targets, effectively creating an AND relation and ensuring any files obtained can be trusted. It offers a way to initialize multiple repositories using a `map.json` file and also mechanisms to query and download target files securely. It is implemented on top of the Updater API and can be used to implement various multi-repository TUF clients with relatively little effort. 111 | 112 | ## Documentation 113 | 114 | ---------------------------- 115 | 116 | * [Documentation](https://pkg.go.dev/github.com/theupdateframework/go-tuf/v2) 117 | 118 | * [Introduction to TUF's Design](https://theupdateframework.io/overview/) 119 | 120 | * [The TUF Specification](https://theupdateframework.github.io/specification/latest/) 121 | 122 | ## History - legacy go-tuf vs go-tuf/v2 123 | 124 | The [legacy go-tuf (v0.7.0)](https://github.com/theupdateframework/go-tuf/tree/v0.7.0) codebase was difficult to maintain and prone to errors due to its initial design decisions. Now it is considered deprecated in favour of go-tuf v2 (originaly from [rdimitrov/go-tuf-metadata](https://github.com/rdimitrov/go-tuf-metadata)) which started from the idea of providing a Go implementation of TUF that is heavily influenced by the design decisions made in [python-tuf](https://github.com/theupdateframework/python-tuf). 125 | 126 | ## Contact 127 | 128 | ---------------------------- 129 | 130 | Questions, feedback, and suggestions are welcomed on the [#tuf](https://cloud-native.slack.com/archives/C8NMD3QJ3) and/or [#go-tuf](https://cloud-native.slack.com/archives/C02D577GX54) channels on 131 | [CNCF Slack](https://slack.cncf.io/). 132 | 133 | We strive to make the specification easy to implement, so if you come across 134 | any inconsistencies or experience any difficulty, do let us know by sending an 135 | email, or by reporting an issue in the GitHub [specification 136 | repo](https://github.com/theupdateframework/specification/issues). 137 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | The go-tuf community is committed to maintaining a reliable and consistent TUF client implementation. If you believe you have identified a security issue in go-tuf's client protocol, please follow these guidelines for responsible disclosure. 4 | 5 | ## Supported Versions 6 | 7 | You may report issues for the most recent version of go-tuf. We will not retroactively make changes to older versions. 8 | 9 | ## Reporting a Vulnerability 10 | 11 | If you discover a potential security issue in this project we ask that you notify the go-tuf maintainers via [Github's private reporting feature](https://github.com/theupdateframework/go-tuf/security/advisories/new) (requires being signed in to GitHub). At the minimum, the report must contain the following: 12 | 13 | * A description of the issue. 14 | * A specific version or commit SHA of `go-tuf` where the issue reproduces. 15 | * Instructions to reproduce the issue. 16 | 17 | Please do **not** create a public GitHub issue or pull request to submit vulnerability reports. These public trackers are intended for non-time-sensitive and non-security-related bug reports and feature requests. Major feature requests, such as design changes to the specification, should be proposed via a [TUF Augmentation Protocol](https://theupdateframework.github.io/specification/latest/#tuf-augmentation-proposal-tap-support) (TAP). 18 | 19 | ## Disclosure 20 | 21 | This project follows a 90 day disclosure timeline. 22 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ---------------------------- 4 | 5 | ## Repository 6 | 7 | ---------------------------- 8 | 9 | See the [basic_repository.go](repository/basic_repository.go) example which demonstrates how to *manually* create and 10 | maintain repository metadata using the low-level Metadata API. 11 | 12 | The example highlights the following functionality supported by the metadata API: 13 | 14 | * creation of top-level metadata 15 | * target file handling 16 | * consistent snapshots 17 | * support a mixture of key types - ED25519, RSA and ECDSA 18 | * top-level delegation and signing thresholds 19 | * metadata verification 20 | * target delegation 21 | * in-band and out-of-band metadata signing 22 | * writing and reading metadata files 23 | * root key rotation 24 | 25 | ## Client 26 | 27 | ---------------------------- 28 | There's also a [client_example.go](client/client_example.go) which demonstrates how to implement a client using the [updater](metadata/updater/updater.go) package. 29 | 30 | * it uses [https://jku.github.io/tuf-demo](https://jku.github.io/tuf-demo), a live TUF repository hosted on GitHub 31 | * shows an example of how to initialize a client 32 | * shows an example of how to download a target file 33 | * the repository is based on python-tuf so it also highlights the interoperability between the two implementations 34 | 35 | ## Multi-repository client 36 | 37 | ---------------------------- 38 | There's a [client_example.go](multirepo/client/client_example.go) which demonstrates how to implement a multi-repository client using the [multirepo](metadata/multirepo/multirepo.go) package which implements [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md). The example consists of the following: 39 | 40 | * The `map.json` along with the root files for each repository are distributed via a trusted repository used for initialization 41 | * The metadata, these target files and the script generating them are located in the [examples/multirepo/repository](../repository/) folder 42 | * These files are then used to bootstrap the multi-repository TUF client 43 | * Shows the API provided by the `multirepo` package 44 | 45 | ## CLI tools 46 | 47 | ---------------------------- 48 | 49 | The following CLIs are experimental replacements of the CLI tools provided by the go-tuf package. At some point these will be moved to a separate repository. 50 | 51 | * [tuf-client](cli/tuf-client/README.md) - a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification 52 | 53 | * [tuf](cli/tuf/README.md) - Not implemented 54 | -------------------------------------------------------------------------------- /examples/cli/README.md: -------------------------------------------------------------------------------- 1 | # tuf and tuf-client CLI tools 2 | 3 | ---------------------------- 4 | 5 | ## Overview 6 | 7 | ---------------------------- 8 | 9 | The following CLIs are experimental replacements of the CLI tools provided by the go-tuf package: 10 | 11 | * [tuf-client](tuf-client/README.md) - a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification 12 | 13 | * [tuf](tuf/README.md) - Not implemented 14 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/README.md: -------------------------------------------------------------------------------- 1 | # tuf-client CLI 2 | 3 | ---------------------------- 4 | 5 | ## Overview 6 | 7 | ---------------------------- 8 | 9 | `tuf-client` is a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification. 10 | 11 | The tuf-client can be used to query for available targets and to download them in a secure manner. 12 | 13 | All downloaded files are verified by signed metadata. 14 | 15 | The CLI provides three commands: 16 | 17 | * `tuf-client init` - Initialize the client with trusted root.json metadata 18 | * `tuf-client get` - Download a target file 19 | * `tuf-client reset` - Resets the local environment. Warning: this deletes both the metadata and download folders and all of their contents 20 | 21 | All commands except `reset` require the URL of the TUF repository passed as a flag via `--url/u` 22 | 23 | Run `tuf-client help` from the command line to get more detailed usage information. 24 | 25 | ## Usage 26 | 27 | ---------------------------- 28 | 29 | ```bash 30 | # Initialize by providing a root.json 31 | # 32 | # Usage: tuf-client init --url -f root.json 33 | # 34 | $ tuf-client init --url https://jku.github.io/tuf-demo/metadata -f root.json 35 | 36 | # Initialize without providing a root.json 37 | # 38 | # Usage: tuf-client init --url 39 | # 40 | $ tuf-client init --url https://jku.github.io/tuf-demo/metadata 41 | 42 | # Get a target 43 | # 44 | # Usage: tuf-client get --url 45 | # 46 | $ tuf-client get --url https://jku.github.io/tuf-demo/metadata demo/succinctly-delegated-5.txt 47 | 48 | # Get a target by providing a URL of where target files are located 49 | # 50 | # Usage: tuf-client get --url -t 51 | # 52 | # Use --nonprefixed for non-prefixed target files 53 | # 54 | $ tuf-client get --url https://jku.github.io/tuf-demo/metadata --turl https://jku.github.io/tuf-demo/targets --nonprefixed demo/succinctly-delegated-5.txt 55 | 56 | # Reset your local environment 57 | $ tuf-client reset 58 | ``` 59 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/cmd/get.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package cmd 19 | 20 | import ( 21 | "fmt" 22 | stdlog "log" 23 | "os" 24 | "path/filepath" 25 | 26 | "github.com/go-logr/stdr" 27 | "github.com/spf13/cobra" 28 | "github.com/theupdateframework/go-tuf/v2/metadata" 29 | "github.com/theupdateframework/go-tuf/v2/metadata/config" 30 | "github.com/theupdateframework/go-tuf/v2/metadata/updater" 31 | ) 32 | 33 | var targetsURL string 34 | var useNonHashPrefixedTargetFiles bool 35 | 36 | type localConfig struct { 37 | MetadataDir string 38 | DownloadDir string 39 | MetadataURL string 40 | TargetsURL string 41 | } 42 | 43 | var getCmd = &cobra.Command{ 44 | Use: "get", 45 | Aliases: []string{"g"}, 46 | Short: "Download a target file", 47 | Args: cobra.ExactArgs(1), 48 | RunE: func(cmd *cobra.Command, args []string) error { 49 | if RepositoryURL == "" { 50 | fmt.Println("Error: required flag(s) \"url\" not set") 51 | os.Exit(1) 52 | } 53 | return GetCmd(args[0]) 54 | }, 55 | } 56 | 57 | func init() { 58 | getCmd.Flags().StringVarP(&targetsURL, "turl", "t", "", "URL of where the target files are hosted") 59 | getCmd.Flags().BoolVarP(&useNonHashPrefixedTargetFiles, "nonprefixed", "", false, "Do not use hash-prefixed target files with consistent snapshots") 60 | rootCmd.AddCommand(getCmd) 61 | } 62 | 63 | func GetCmd(target string) error { 64 | // set logger and debug verbosity level 65 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "get_cmd", stdlog.LstdFlags))) 66 | if Verbosity { 67 | stdr.SetVerbosity(5) 68 | } 69 | 70 | // verify the client environment was initialized and fetch path names 71 | env, err := verifyEnv() 72 | if err != nil { 73 | return err 74 | } 75 | // read the trusted root metadata 76 | rootBytes, err := os.ReadFile(filepath.Join(env.MetadataDir, "root.json")) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | // updater configuration 82 | cfg, err := config.New(env.MetadataURL, rootBytes) // default config 83 | if err != nil { 84 | return err 85 | } 86 | cfg.LocalMetadataDir = env.MetadataDir 87 | cfg.LocalTargetsDir = env.DownloadDir 88 | cfg.RemoteTargetsURL = env.TargetsURL 89 | cfg.PrefixTargetsWithHash = !useNonHashPrefixedTargetFiles 90 | 91 | // create an Updater instance 92 | up, err := updater.New(cfg) 93 | if err != nil { 94 | return fmt.Errorf("failed to create Updater instance: %w", err) 95 | } 96 | 97 | // try to build the top-level metadata 98 | err = up.Refresh() 99 | if err != nil { 100 | return fmt.Errorf("failed to refresh trusted metadata: %w", err) 101 | } 102 | 103 | // search if the desired target is available 104 | targetInfo, err := up.GetTargetInfo(target) 105 | if err != nil { 106 | return fmt.Errorf("target %s not found: %w", target, err) 107 | } 108 | 109 | // target is available, so let's see if the target is already present locally 110 | path, _, err := up.FindCachedTarget(targetInfo, "") 111 | if err != nil { 112 | return fmt.Errorf("failed while finding a cached target: %w", err) 113 | } 114 | 115 | if path != "" { 116 | fmt.Printf("Target %s is already present at - %s\n", target, path) 117 | return nil 118 | } 119 | 120 | // target is not present locally, so let's try to download it 121 | path, _, err = up.DownloadTarget(targetInfo, "", "") 122 | if err != nil { 123 | return fmt.Errorf("failed to download target file %s - %w", target, err) 124 | } 125 | 126 | fmt.Printf("Successfully downloaded target %s at - %s\n", target, path) 127 | 128 | return nil 129 | } 130 | 131 | func verifyEnv() (*localConfig, error) { 132 | // get working directory 133 | cwd, err := os.Getwd() 134 | if err != nil { 135 | return nil, err 136 | } 137 | // if no targetsURL is set, we expect that the target files are located at the same location where the metadata is 138 | if targetsURL == "" { 139 | targetsURL = RepositoryURL 140 | } 141 | // start populating what we need 142 | env := &localConfig{ 143 | MetadataDir: filepath.Join(cwd, DefaultMetadataDir), 144 | DownloadDir: filepath.Join(cwd, DefaultDownloadDir), 145 | MetadataURL: RepositoryURL, 146 | TargetsURL: targetsURL, 147 | } 148 | 149 | // verify there's local metadata folder 150 | _, err = os.Stat(env.MetadataDir) 151 | if err != nil { 152 | return nil, fmt.Errorf("no local metadata folder: %w", err) 153 | } 154 | // verify there's local download folder 155 | _, err = os.Stat(env.DownloadDir) 156 | if err != nil { 157 | return nil, fmt.Errorf("no local download folder: %w", err) 158 | } 159 | // verify there's a local root.json available for bootstrapping trust 160 | _, err = os.Stat(filepath.Join(env.MetadataDir, fmt.Sprintf("%s.json", metadata.ROOT))) 161 | if err != nil { 162 | return nil, fmt.Errorf("no local download folder: %w", err) 163 | } 164 | return env, nil 165 | } 166 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/cmd/init.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package cmd 19 | 20 | import ( 21 | "fmt" 22 | "io" 23 | stdlog "log" 24 | "net/http" 25 | "net/url" 26 | "os" 27 | "path/filepath" 28 | 29 | "github.com/go-logr/stdr" 30 | "github.com/spf13/cobra" 31 | "github.com/theupdateframework/go-tuf/v2/metadata" 32 | "github.com/theupdateframework/go-tuf/v2/metadata/trustedmetadata" 33 | ) 34 | 35 | var rootPath string 36 | 37 | var initCmd = &cobra.Command{ 38 | Use: "init", 39 | Aliases: []string{"i"}, 40 | Short: "Initialize the client with trusted root.json metadata", 41 | Args: cobra.ExactArgs(0), 42 | RunE: func(cmd *cobra.Command, args []string) error { 43 | if RepositoryURL == "" { 44 | fmt.Println("Error: required flag(s) \"url\" not set") 45 | os.Exit(1) 46 | } 47 | return InitializeCmd() 48 | }, 49 | } 50 | 51 | func init() { 52 | initCmd.Flags().StringVarP(&rootPath, "file", "f", "", "location of the trusted root metadata file") 53 | rootCmd.AddCommand(initCmd) 54 | } 55 | 56 | func InitializeCmd() error { 57 | copyTrusted := true 58 | // set logger and debug verbosity level 59 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "ini_cmd", stdlog.LstdFlags))) 60 | if Verbosity { 61 | stdr.SetVerbosity(5) 62 | } 63 | 64 | // prepare the local environment 65 | localMetadataDir, err := prepareEnvironment() 66 | if err != nil { 67 | return err 68 | } 69 | 70 | // if there's no root.json file passed, try to download the 1.root.json from the repository URL 71 | if rootPath == "" { 72 | 73 | fmt.Printf("No root.json file was provided. Trying to download one from %s\n", RepositoryURL) 74 | rootPath, err = fetchTrustedRoot(localMetadataDir) 75 | if err != nil { 76 | return err 77 | } 78 | rootPath = filepath.Join(rootPath, fmt.Sprintf("%s.json", metadata.ROOT)) 79 | // no need to copy root.json to the metadata folder as we already download it in the expected location 80 | copyTrusted = false 81 | } 82 | 83 | // read the content of root.json 84 | rootBytes, err := os.ReadFile(rootPath) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | // verify the content 90 | _, err = trustedmetadata.New(rootBytes) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | // Save the trusted root.json file to the metadata folder so it is available for future operations (if we haven't downloaded it) 96 | if copyTrusted { 97 | err = os.WriteFile(filepath.Join(localMetadataDir, rootPath), rootBytes, 0644) 98 | if err != nil { 99 | return err 100 | } 101 | } 102 | 103 | fmt.Println("Initialization successful") 104 | 105 | return nil 106 | } 107 | 108 | // prepareEnvironment prepares the local environment 109 | func prepareEnvironment() (string, error) { 110 | // get working directory 111 | cwd, err := os.Getwd() 112 | if err != nil { 113 | return "", fmt.Errorf("failed to get current working directory: %w", err) 114 | } 115 | metadataPath := filepath.Join(cwd, DefaultMetadataDir) 116 | downloadPath := filepath.Join(cwd, DefaultDownloadDir) 117 | 118 | // create a folder for storing the artifacts 119 | err = os.Mkdir(metadataPath, 0750) 120 | if err != nil { 121 | return "", fmt.Errorf("failed to create local metadata folder: %w", err) 122 | } 123 | 124 | // create a destination folder for storing the downloaded target 125 | err = os.Mkdir(downloadPath, 0750) 126 | if err != nil { 127 | return "", fmt.Errorf("failed to create download folder: %w", err) 128 | } 129 | return metadataPath, nil 130 | } 131 | 132 | // fetchTrustedRoot downloads the initial root metadata 133 | func fetchTrustedRoot(metadataDir string) (string, error) { 134 | // download the initial root metadata so we can bootstrap Trust-On-First-Use 135 | rootURL, err := url.JoinPath(RepositoryURL, "1.root.json") 136 | if err != nil { 137 | return "", fmt.Errorf("failed to create URL path for 1.root.json: %w", err) 138 | } 139 | 140 | req, err := http.NewRequest("GET", rootURL, nil) 141 | if err != nil { 142 | return "", fmt.Errorf("failed to create http request: %w", err) 143 | } 144 | 145 | client := http.DefaultClient 146 | 147 | res, err := client.Do(req) 148 | if err != nil { 149 | return "", fmt.Errorf("failed to executed the http request: %w", err) 150 | } 151 | 152 | defer res.Body.Close() 153 | 154 | data, err := io.ReadAll(res.Body) 155 | if err != nil { 156 | return "", fmt.Errorf("failed to read the http request body: %w", err) 157 | } 158 | 159 | // write the downloaded root metadata to file 160 | err = os.WriteFile(filepath.Join(metadataDir, "root.json"), data, 0644) 161 | if err != nil { 162 | return "", fmt.Errorf("failed to write root.json metadata: %w", err) 163 | } 164 | return metadataDir, nil 165 | } 166 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/cmd/reset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package cmd 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | "path/filepath" 24 | "strings" 25 | 26 | "github.com/spf13/cobra" 27 | ) 28 | 29 | var ForceDelete bool 30 | 31 | var resetCmd = &cobra.Command{ 32 | Use: "reset", 33 | Aliases: []string{"r"}, 34 | Short: "Resets the local environment. Warning: this deletes both the metadata and download folders and all of their contents", 35 | Args: cobra.ExactArgs(0), 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | return ResetCmd() 38 | }, 39 | } 40 | 41 | func init() { 42 | resetCmd.Flags().BoolVarP(&ForceDelete, "force", "f", false, "force delete without waiting for confirmation") 43 | rootCmd.AddCommand(resetCmd) 44 | } 45 | 46 | func ResetCmd() error { 47 | // get working directory 48 | cwd, err := os.Getwd() 49 | if err != nil { 50 | return fmt.Errorf("failed to get current working directory: %w", err) 51 | } 52 | 53 | // folders to delete 54 | metadataPath := filepath.Join(cwd, DefaultMetadataDir) 55 | downloadPath := filepath.Join(cwd, DefaultDownloadDir) 56 | 57 | // warning: deletes the metadata folder and all of its contents 58 | fmt.Printf("Warning: Are you sure you want to delete the \"%s\" folder and all of its contents? (y/n)\n", metadataPath) 59 | if ForceDelete || askForConfirmation() { 60 | os.RemoveAll(metadataPath) 61 | fmt.Printf("Folder %s was successfully deleted\n", metadataPath) 62 | } else { 63 | fmt.Printf("Folder \"%s\" was not deleted\n", metadataPath) 64 | } 65 | 66 | // warning: deletes the download folder and all of its contents 67 | fmt.Printf("Warning: Are you sure you want to delete the \"%s\" folder and all of its contents? (y/n)\n", downloadPath) 68 | if ForceDelete || askForConfirmation() { 69 | os.RemoveAll(downloadPath) 70 | fmt.Printf("Folder %s was successfully deleted\n", downloadPath) 71 | } else { 72 | fmt.Printf("Folder \"%s\" was not deleted\n", downloadPath) 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func askForConfirmation() bool { 79 | var response string 80 | _, err := fmt.Scanln(&response) 81 | if err != nil { 82 | fmt.Println(err) 83 | os.Exit(1) 84 | } 85 | switch strings.ToLower(response) { 86 | case "y", "yes": 87 | return true 88 | case "n", "no": 89 | return false 90 | default: 91 | fmt.Println("I'm sorry but I didn't get what you meant, please type (y)es or (n)o and then press enter:") 92 | return askForConfirmation() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package cmd 19 | 20 | import ( 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | const ( 27 | DefaultMetadataDir = "tuf_metadata" 28 | DefaultDownloadDir = "tuf_download" 29 | ) 30 | 31 | var Verbosity bool 32 | var RepositoryURL string 33 | 34 | var rootCmd = &cobra.Command{ 35 | Use: "tuf-client", 36 | Short: "tuf-client - a client-side CLI tool for The Update Framework (TUF)", 37 | Long: `tuf-client is a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification. 38 | 39 | The tuf-client can be used to query for available targets and to download them in a secure manner. 40 | 41 | All downloaded files are verified by signed metadata.`, 42 | Run: func(cmd *cobra.Command, args []string) { 43 | // show the help message if no command has been used 44 | if len(args) == 0 { 45 | _ = cmd.Help() 46 | os.Exit(0) 47 | } 48 | }, 49 | } 50 | 51 | func Execute() { 52 | rootCmd.PersistentFlags().BoolVarP(&Verbosity, "verbose", "v", false, "verbose output") 53 | rootCmd.PersistentFlags().StringVarP(&RepositoryURL, "url", "u", "", "URL of the TUF repository") 54 | 55 | if err := rootCmd.Execute(); err != nil { 56 | os.Exit(1) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package main 19 | 20 | import ( 21 | tufclient "github.com/theupdateframework/go-tuf/v2/examples/cli/tuf-client/cmd" 22 | ) 23 | 24 | func main() { 25 | tufclient.Execute() 26 | } 27 | -------------------------------------------------------------------------------- /examples/cli/tuf/README.md: -------------------------------------------------------------------------------- 1 | # tuf CLI 2 | 3 | ---------------------------- 4 | 5 | ## Overview 6 | 7 | ---------------------------- 8 | 9 | Not implemented 10 | -------------------------------------------------------------------------------- /examples/cli/tuf/cmd/init.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package cmd 19 | 20 | import ( 21 | "fmt" 22 | stdlog "log" 23 | "os" 24 | 25 | "github.com/go-logr/stdr" 26 | "github.com/spf13/cobra" 27 | "github.com/theupdateframework/go-tuf/v2/metadata" 28 | ) 29 | 30 | var initCmd = &cobra.Command{ 31 | Use: "init", 32 | Aliases: []string{"i"}, 33 | Short: "Initialize a repository", 34 | Args: cobra.ExactArgs(0), 35 | RunE: func(cmd *cobra.Command, args []string) error { 36 | return InitializeCmd() 37 | }, 38 | } 39 | 40 | func init() { 41 | rootCmd.AddCommand(initCmd) 42 | } 43 | 44 | func InitializeCmd() error { 45 | // set logger and debug verbosity level 46 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "ini_cmd", stdlog.LstdFlags))) 47 | if Verbosity { 48 | stdr.SetVerbosity(5) 49 | } 50 | 51 | fmt.Println("Initialization successful") 52 | 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /examples/cli/tuf/cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package cmd 19 | 20 | import ( 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var Verbosity bool 27 | 28 | var rootCmd = &cobra.Command{ 29 | Use: "tuf", 30 | Short: "tuf - a repository-side CLI tool for The Update Framework (TUF)", 31 | Long: "tuf - a repository-side CLI tool for The Update Framework (TUF)", 32 | Run: func(cmd *cobra.Command, args []string) { 33 | 34 | }, 35 | } 36 | 37 | func Execute() { 38 | rootCmd.PersistentFlags().BoolVarP(&Verbosity, "verbose", "v", false, "verbose output") 39 | 40 | if err := rootCmd.Execute(); err != nil { 41 | os.Exit(1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/cli/tuf/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | 24 | tuf "github.com/theupdateframework/go-tuf/v2/examples/cli/tuf/cmd" 25 | ) 26 | 27 | func main() { 28 | fmt.Println("Not implemented") 29 | os.Exit(1) 30 | tuf.Execute() 31 | } 32 | -------------------------------------------------------------------------------- /examples/client/client_example.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "io" 23 | stdlog "log" 24 | "net/http" 25 | "net/url" 26 | "os" 27 | "path/filepath" 28 | 29 | "github.com/go-logr/stdr" 30 | 31 | "github.com/theupdateframework/go-tuf/v2/metadata" 32 | "github.com/theupdateframework/go-tuf/v2/metadata/config" 33 | "github.com/theupdateframework/go-tuf/v2/metadata/updater" 34 | ) 35 | 36 | // The following config is used to fetch a target from Jussi's GitHub repository example 37 | const ( 38 | metadataURL = "https://jku.github.io/tuf-demo/metadata" 39 | targetsURL = "https://jku.github.io/tuf-demo/targets" 40 | targetName = "rdimitrov/artifact-example.md" 41 | verbosity = 4 42 | generateRandomFolder = false 43 | ) 44 | 45 | func main() { 46 | // set logger to stdout with info level 47 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "client_example", stdlog.LstdFlags))) 48 | stdr.SetVerbosity(verbosity) 49 | 50 | log := metadata.GetLogger() 51 | 52 | // initialize environment - temporary folders, etc. 53 | metadataDir, err := InitEnvironment() 54 | if err != nil { 55 | log.Error(err, "Failed to initialize environment") 56 | } 57 | 58 | // initialize client with Trust-On-First-Use 59 | err = InitTrustOnFirstUse(metadataDir) 60 | if err != nil { 61 | log.Error(err, "Trust-On-First-Use failed") 62 | } 63 | 64 | // download the desired target 65 | err = DownloadTarget(metadataDir, targetName) 66 | if err != nil { 67 | log.Error(err, "Download failed") 68 | } 69 | } 70 | 71 | // InitEnvironment prepares the local environment - temporary folders, etc. 72 | func InitEnvironment() (string, error) { 73 | var tmpDir string 74 | // get working directory 75 | cwd, err := os.Getwd() 76 | if err != nil { 77 | return "", fmt.Errorf("failed to get current working directory: %w", err) 78 | } 79 | if !generateRandomFolder { 80 | tmpDir = filepath.Join(cwd, "tmp") 81 | // create a temporary folder for storing the demo artifacts 82 | _ = os.Mkdir(tmpDir, 0750) 83 | } else { 84 | // create a temporary folder for storing the demo artifacts 85 | tmpDir, err = os.MkdirTemp(cwd, "tmp") 86 | if err != nil { 87 | return "", fmt.Errorf("failed to create a temporary folder: %w", err) 88 | } 89 | } 90 | 91 | // create a destination folder for storing the downloaded target 92 | _ = os.Mkdir(filepath.Join(tmpDir, "download"), 0750) 93 | return tmpDir, nil 94 | } 95 | 96 | // InitTrustOnFirstUse initialize local trusted metadata (Trust-On-First-Use) 97 | func InitTrustOnFirstUse(metadataDir string) error { 98 | // check if there's already a local root.json available for bootstrapping trust 99 | _, err := os.Stat(filepath.Join(metadataDir, "root.json")) 100 | if err == nil { 101 | return nil 102 | } 103 | 104 | // download the initial root metadata so we can bootstrap Trust-On-First-Use 105 | rootURL, err := url.JoinPath(metadataURL, "1.root.json") 106 | if err != nil { 107 | return fmt.Errorf("failed to create URL path for 1.root.json: %w", err) 108 | } 109 | 110 | req, err := http.NewRequest("GET", rootURL, nil) 111 | if err != nil { 112 | return fmt.Errorf("failed to create http request: %w", err) 113 | } 114 | 115 | client := http.DefaultClient 116 | 117 | res, err := client.Do(req) 118 | if err != nil { 119 | return fmt.Errorf("failed to executed the http request: %w", err) 120 | } 121 | 122 | defer res.Body.Close() 123 | 124 | data, err := io.ReadAll(res.Body) 125 | if err != nil { 126 | return fmt.Errorf("failed to read the http request body: %w", err) 127 | } 128 | 129 | // write the downloaded root metadata to file 130 | err = os.WriteFile(filepath.Join(metadataDir, "root.json"), data, 0644) 131 | if err != nil { 132 | return fmt.Errorf("failed to write root.json metadata: %w", err) 133 | } 134 | 135 | return nil 136 | } 137 | 138 | // DownloadTarget downloads the target file using Updater. The Updater refreshes the top-level metadata, 139 | // get the target information, verifies if the target is already cached, and in case it 140 | // is not cached, downloads the target file. 141 | func DownloadTarget(localMetadataDir, target string) error { 142 | log := metadata.GetLogger() 143 | 144 | rootBytes, err := os.ReadFile(filepath.Join(localMetadataDir, "root.json")) 145 | if err != nil { 146 | return err 147 | } 148 | // create updater configuration 149 | cfg, err := config.New(metadataURL, rootBytes) // default config 150 | if err != nil { 151 | return err 152 | } 153 | cfg.LocalMetadataDir = localMetadataDir 154 | cfg.LocalTargetsDir = filepath.Join(localMetadataDir, "download") 155 | cfg.RemoteTargetsURL = targetsURL 156 | cfg.PrefixTargetsWithHash = true 157 | 158 | // create a new Updater instance 159 | up, err := updater.New(cfg) 160 | if err != nil { 161 | return fmt.Errorf("failed to create Updater instance: %w", err) 162 | } 163 | 164 | // try to build the top-level metadata 165 | err = up.Refresh() 166 | if err != nil { 167 | return fmt.Errorf("failed to refresh trusted metadata: %w", err) 168 | } 169 | 170 | // search if the desired target is available 171 | targetInfo, err := up.GetTargetInfo(target) 172 | if err != nil { 173 | return fmt.Errorf("target %s not found: %w", target, err) 174 | } 175 | 176 | // target is available, so let's see if the target is already present locally 177 | path, _, err := up.FindCachedTarget(targetInfo, "") 178 | if err != nil { 179 | return fmt.Errorf("failed while finding a cached target: %w", err) 180 | } 181 | if path != "" { 182 | log.Info("Target is already present", "target", target, "path", path) 183 | } 184 | 185 | // target is not present locally, so let's try to download it 186 | path, _, err = up.DownloadTarget(targetInfo, "", "") 187 | if err != nil { 188 | return fmt.Errorf("failed to download target file %s - %w", target, err) 189 | } 190 | 191 | log.Info("Successfully downloaded target", "target", target, "path", path) 192 | 193 | return nil 194 | } 195 | -------------------------------------------------------------------------------- /examples/multirepo/client/README.md: -------------------------------------------------------------------------------- 1 | # Example repository showing a multi repository TUF client (TAP 4) 2 | 3 | The following is a TUF multi-repository client example of the `multirepo` package which implements [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md): 4 | 5 | - The `map.json` along with the root files for each repository are distributed via a trusted repository used for initialization 6 | - The metadata, these target files and the script generating them are located in the [examples/multirepo/repository](../repository/) folder 7 | - These files are then used to bootstrap the multi-repository TUF client 8 | - Shows the API provided by the `multirepo` package 9 | -------------------------------------------------------------------------------- /examples/multirepo/client/client_example.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | stdlog "log" 23 | "os" 24 | "path/filepath" 25 | "strings" 26 | 27 | "github.com/go-logr/stdr" 28 | 29 | "github.com/theupdateframework/go-tuf/v2/metadata" 30 | "github.com/theupdateframework/go-tuf/v2/metadata/config" 31 | "github.com/theupdateframework/go-tuf/v2/metadata/multirepo" 32 | "github.com/theupdateframework/go-tuf/v2/metadata/updater" 33 | ) 34 | 35 | const ( 36 | metadataURL = "https://raw.githubusercontent.com/theupdateframework/go-tuf/master/examples/multirepo/repository/metadata" 37 | targetsURL = "https://raw.githubusercontent.com/theupdateframework/go-tuf/master/examples/multirepo/repository/targets" 38 | verbosity = 4 39 | ) 40 | 41 | func main() { 42 | // set logger to stdout with info level 43 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "multirepo_client_example", stdlog.LstdFlags))) 44 | stdr.SetVerbosity(verbosity) 45 | 46 | // Bootstrap TUF 47 | fmt.Printf("Bootstrapping the initial TUF repo - fetching map.json file and necessary trusted root files\n\n") 48 | mapBytes, trustedRoots, err := BootstrapTUF() // returns the map.json and the trusted root files 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | // Initialize the multi-repository TUF client 54 | fmt.Printf("Initializing the multi-repository TUF client with the given map.json file\n\n") 55 | client, err := InitMultiRepoTUF(mapBytes, trustedRoots) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | // Refresh all repositories 61 | fmt.Printf("Refreshing each TUF client (updating metadata/client update workflow)\n\n") 62 | err = client.Refresh() 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | // Get target info for the given target 68 | fmt.Printf("Searching for a target using the multi-repository TUF client\n\n") 69 | targetInfo, repositories, err := client.GetTargetInfo("rekor.pub") // rekor.pub trusted_root.json fulcio_v1.crt.pem 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | // Download the target using that target info 75 | fmt.Println("Downloading a target using the multi-repository TUF client") 76 | _, _, err = client.DownloadTarget(repositories, targetInfo, "", "") 77 | if err != nil { 78 | panic(err) 79 | } 80 | } 81 | 82 | // BootstrapTUF returns the map file and the related trusted root metadata files 83 | func BootstrapTUF() ([]byte, map[string][]byte, error) { 84 | log := metadata.GetLogger() 85 | 86 | trustedRoots := map[string][]byte{} 87 | mapBytes := []byte{} 88 | // get working directory 89 | cwd, err := os.Getwd() 90 | if err != nil { 91 | return nil, nil, fmt.Errorf("failed to get current working directory: %w", err) 92 | } 93 | targetsDir := filepath.Join(cwd, "bootstrap/targets") 94 | 95 | // ensure the necessary folder layout 96 | err = os.MkdirAll(targetsDir, os.ModePerm) 97 | if err != nil { 98 | return nil, nil, err 99 | } 100 | 101 | // read the trusted root metadata 102 | rootBytes, err := os.ReadFile(filepath.Join(cwd, "root.json")) 103 | if err != nil { 104 | return nil, nil, err 105 | } 106 | 107 | // create updater configuration 108 | cfg, err := config.New(metadataURL, rootBytes) // default config 109 | if err != nil { 110 | return nil, nil, err 111 | } 112 | cfg.LocalMetadataDir = filepath.Join(cwd, "bootstrap") 113 | cfg.LocalTargetsDir = targetsDir 114 | cfg.RemoteTargetsURL = targetsURL 115 | 116 | // create a new Updater instance 117 | up, err := updater.New(cfg) 118 | if err != nil { 119 | return nil, nil, fmt.Errorf("failed to create Updater instance: %w", err) 120 | } 121 | 122 | // build the top-level metadata 123 | err = up.Refresh() 124 | if err != nil { 125 | return nil, nil, fmt.Errorf("failed to refresh trusted metadata: %w", err) 126 | } 127 | 128 | // download all target files 129 | for name, targetInfo := range up.GetTopLevelTargets() { 130 | // see if the target is already present locally 131 | path, _, err := up.FindCachedTarget(targetInfo, "") 132 | if err != nil { 133 | return nil, nil, fmt.Errorf("failed while finding a cached target: %w", err) 134 | } 135 | if path != "" { 136 | log.Info("Target is already present", "target", name, "path", path) 137 | } 138 | 139 | // target is not present locally, so let's try to download it 140 | // keeping the same path layout as its target path 141 | expectedTargetLocation := filepath.Join(targetsDir, name) 142 | dirName, _ := filepath.Split(expectedTargetLocation) 143 | err = os.MkdirAll(dirName, os.ModePerm) 144 | if err != nil { 145 | return nil, nil, err 146 | } 147 | 148 | // download targets (we don't have to actually store them other than for the sake of the example) 149 | path, bytes, err := up.DownloadTarget(targetInfo, expectedTargetLocation, "") 150 | if err != nil { 151 | return nil, nil, fmt.Errorf("failed to download target file %s - %w", name, err) 152 | } 153 | 154 | // populate the return values 155 | if name == "map.json" { 156 | mapBytes = bytes 157 | } else { 158 | // Target names uses forwardslash even on Windows 159 | repositoryName := strings.Split(name, "/") 160 | trustedRoots[repositoryName[0]] = bytes 161 | } 162 | log.Info("Successfully downloaded target", "target", name, "path", path) 163 | } 164 | 165 | return mapBytes, trustedRoots, nil 166 | } 167 | 168 | func InitMultiRepoTUF(mapBytes []byte, trustedRoots map[string][]byte) (*multirepo.MultiRepoClient, error) { 169 | // get working directory 170 | cwd, err := os.Getwd() 171 | if err != nil { 172 | return nil, fmt.Errorf("failed to get current working directory: %w", err) 173 | } 174 | 175 | // create a new configuration for a multi-repository client 176 | cfg, err := multirepo.NewConfig(mapBytes, trustedRoots) 177 | if err != nil { 178 | return nil, err 179 | } 180 | cfg.LocalMetadataDir = filepath.Join(cwd, "metadata") 181 | cfg.LocalTargetsDir = filepath.Join(cwd, "download") 182 | 183 | // create a new instance of a multi-repository TUF client 184 | return multirepo.New(cfg) 185 | } 186 | -------------------------------------------------------------------------------- /examples/multirepo/client/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "8be9c0369e7f2e2aacd9fde0267c5ab224bd65b3e8f892f09168050c53dd1a4d", 5 | "sig": "86e985ac792a7c92489b781e371c9c4ea0b4ba944f56f19783df8404cc7d76dc2ea44b13fd508bd70cbf9cd481e155b4a41ce0b4f030ea481bea9015fa055c06" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "root", 10 | "consistent_snapshot": true, 11 | "expires": "2034-09-30T11:29:58.188964Z", 12 | "keys": { 13 | "1e0c46c0f988b6f45eb0956ed6b0836697a39dca123c56453a3d8ad57c64726b": { 14 | "keytype": "ed25519", 15 | "keyval": { 16 | "public": "a5ebf16e4bfec00df5b3a0a580ef1edf8f5c786f398ad82e4bf1b3761c39fc9b" 17 | }, 18 | "scheme": "ed25519" 19 | }, 20 | "6ee434fdb4e723ed7d5c556a34e5fabc7412c37ce652dd1a4aeec1e06f86a44c": { 21 | "keytype": "ed25519", 22 | "keyval": { 23 | "public": "c934af418d5a32992e5ab6e1bb1a0fddbd0d944654b9dbe53b507b1552ac7057" 24 | }, 25 | "scheme": "ed25519" 26 | }, 27 | "8be9c0369e7f2e2aacd9fde0267c5ab224bd65b3e8f892f09168050c53dd1a4d": { 28 | "keytype": "ed25519", 29 | "keyval": { 30 | "public": "e48f729e90a19ac8cf227d7a5e56dcfd52bdc30258fc426255c856959935cb9e" 31 | }, 32 | "scheme": "ed25519" 33 | }, 34 | "b779120edb45353d2a151004fe463ec6f10d90d83c0fa1c755e4e436e2ac8009": { 35 | "keytype": "ed25519", 36 | "keyval": { 37 | "public": "097df4cf52f263630b0e5dfac96b2955b83a253842d0e7fcaffa121a349e6efa" 38 | }, 39 | "scheme": "ed25519" 40 | } 41 | }, 42 | "roles": { 43 | "root": { 44 | "keyids": [ 45 | "8be9c0369e7f2e2aacd9fde0267c5ab224bd65b3e8f892f09168050c53dd1a4d" 46 | ], 47 | "threshold": 1 48 | }, 49 | "snapshot": { 50 | "keyids": [ 51 | "6ee434fdb4e723ed7d5c556a34e5fabc7412c37ce652dd1a4aeec1e06f86a44c" 52 | ], 53 | "threshold": 1 54 | }, 55 | "targets": { 56 | "keyids": [ 57 | "b779120edb45353d2a151004fe463ec6f10d90d83c0fa1c755e4e436e2ac8009" 58 | ], 59 | "threshold": 1 60 | }, 61 | "timestamp": { 62 | "keyids": [ 63 | "1e0c46c0f988b6f45eb0956ed6b0836697a39dca123c56453a3d8ad57c64726b" 64 | ], 65 | "threshold": 1 66 | } 67 | }, 68 | "spec_version": "1.0.31", 69 | "version": 1 70 | } 71 | } -------------------------------------------------------------------------------- /examples/multirepo/repository/README.md: -------------------------------------------------------------------------------- 1 | # Example repository used for bootstrapping a multi repository TUF client (TAP 4) 2 | 3 | The following is a helper TUF repository that serves several targets: 4 | 5 | - `map.json` which holds repository mappings and can be used to bootstrap a TUF client supporting multiple repositories 6 | - A set of trusted root files for each repository listed in the `map.json` file 7 | - The `examples/multirepo/client/client_example.go`(../client/client_example.go) is a client which uses this repository to bootstrap a multi-repository TUF client 8 | 9 | ## Usage 10 | 11 | To regenerate the multi-repo repository, 12 | run the following command from inside the `examples/multirepo/repository` directory: 13 | 14 | ```bash 15 | go run . 16 | ``` 17 | 18 | This should generate the necessary metadata files in the `metadata` directory and the `map.json` file. 19 | It will also copy the new `root.json` files to the `client` directory. 20 | -------------------------------------------------------------------------------- /examples/multirepo/repository/generate_metadata.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package main 19 | 20 | import ( 21 | "crypto" 22 | "crypto/ed25519" 23 | "fmt" 24 | "os" 25 | "path/filepath" 26 | "strings" 27 | "time" 28 | 29 | "github.com/sigstore/sigstore/pkg/signature" 30 | "github.com/theupdateframework/go-tuf/v2/examples/repository/repository" 31 | "github.com/theupdateframework/go-tuf/v2/metadata" 32 | ) 33 | 34 | func main() { 35 | // Create top-level metadata 36 | roles := repository.New() 37 | keys := map[string]ed25519.PrivateKey{} 38 | 39 | // Create Targets metadata 40 | targets := metadata.Targets(helperExpireIn(10)) 41 | roles.SetTargets("targets", targets) 42 | 43 | // Add each target to Targets metadata 44 | for _, targetName := range []string{"targets/map.json", "targets/sigstore-tuf-root/root.json", "targets/staging/root.json"} { 45 | targetPath, localPath := helperGetPathForTarget(targetName) 46 | targetFileInfo, err := metadata.TargetFile().FromFile(localPath, "sha256") 47 | if err != nil { 48 | panic(fmt.Sprintln("generate_metadata.go:", "generating target file info failed", err)) 49 | } 50 | roles.Targets("targets").Signed.Targets[strings.TrimPrefix(targetPath, "targets/")] = targetFileInfo 51 | for _, eachHashValue := range targetFileInfo.Hashes { 52 | err := copyHashPrefixed(localPath, eachHashValue.String()) 53 | if err != nil { 54 | panic(err) 55 | } 56 | } 57 | } 58 | 59 | // Create Snapshot metadata 60 | snapshot := metadata.Snapshot(helperExpireIn(10)) 61 | roles.SetSnapshot(snapshot) 62 | 63 | // Create Timestamp metadata 64 | timestamp := metadata.Timestamp(helperExpireIn(10)) 65 | roles.SetTimestamp(timestamp) 66 | 67 | // Create Root metadata 68 | root := metadata.Root(helperExpireIn(10)) 69 | roles.SetRoot(root) 70 | 71 | // For this example, we generate one private key of type 'ed25519' for each top-level role 72 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { 73 | _, private, err := ed25519.GenerateKey(nil) 74 | if err != nil { 75 | panic(fmt.Sprintln("generate_metadata.go:", "key generation failed", err)) 76 | } 77 | keys[name] = private 78 | key, err := metadata.KeyFromPublicKey(private.Public()) 79 | if err != nil { 80 | panic(fmt.Sprintln("generate_metadata.go:", "key conversion failed", err)) 81 | } 82 | err = roles.Root().Signed.AddKey(key, name) 83 | if err != nil { 84 | panic(fmt.Sprintln("generate_metadata.go:", "adding key to root failed", err)) 85 | } 86 | } 87 | 88 | // Sign top-level metadata (in-band) 89 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { 90 | key := keys[name] 91 | signer, err := signature.LoadSigner(key, crypto.Hash(0)) 92 | if err != nil { 93 | panic(fmt.Sprintln("generate_metadata.go:", "loading a signer failed", err)) 94 | } 95 | switch name { 96 | case "targets": 97 | _, err = roles.Targets("targets").Sign(signer) 98 | case "snapshot": 99 | _, err = roles.Snapshot().Sign(signer) 100 | case "timestamp": 101 | _, err = roles.Timestamp().Sign(signer) 102 | case "root": 103 | _, err = roles.Root().Sign(signer) 104 | } 105 | if err != nil { 106 | panic(fmt.Sprintln("generate_metadata.go:", "metadata signing failed", err)) 107 | } 108 | } 109 | 110 | // Persist metadata (consistent snapshot) 111 | cwd, err := os.Getwd() 112 | if err != nil { 113 | panic(fmt.Sprintln("generate_metadata.go:", "getting cwd failed", err)) 114 | } 115 | // Save to metadata folder 116 | cwd = filepath.Join(cwd, "metadata") 117 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { 118 | switch name { 119 | case "targets": 120 | filename := fmt.Sprintf("%d.%s.json", roles.Targets("targets").Signed.Version, name) 121 | err = roles.Targets("targets").ToFile(filepath.Join(cwd, filename), true) 122 | case "snapshot": 123 | filename := fmt.Sprintf("%d.%s.json", roles.Snapshot().Signed.Version, name) 124 | err = roles.Snapshot().ToFile(filepath.Join(cwd, filename), true) 125 | case "timestamp": 126 | filename := fmt.Sprintf("%s.json", name) 127 | err = roles.Timestamp().ToFile(filepath.Join(cwd, filename), true) 128 | case "root": 129 | filename := fmt.Sprintf("%d.%s.json", roles.Root().Signed.Version, name) 130 | err = roles.Root().ToFile(filepath.Join(cwd, filename), true) 131 | } 132 | if err != nil { 133 | panic(fmt.Sprintln("generate_metadata.go:", "saving metadata to file failed", err)) 134 | } 135 | } 136 | 137 | // Save the created root metadata in the client folder, this is the initial trusted root metadata 138 | err = roles.Root().ToFile(filepath.Join(cwd, "../../client/root.json"), true) 139 | if err != nil { 140 | panic(fmt.Sprintln("generate_metadata.go:", "saving trusted root metadata to client folder failed", err)) 141 | } 142 | 143 | // Verify that metadata is signed correctly 144 | // Verify root 145 | err = roles.Root().VerifyDelegate("root", roles.Root()) 146 | if err != nil { 147 | panic(fmt.Sprintln("generate_metadata.go:", "verifying root metadata failed", err)) 148 | } 149 | 150 | // Verify targets 151 | err = roles.Root().VerifyDelegate("targets", roles.Targets("targets")) 152 | if err != nil { 153 | panic(fmt.Sprintln("generate_metadata.go:", "verifying targets metadata failed", err)) 154 | } 155 | 156 | // Verify snapshot 157 | err = roles.Root().VerifyDelegate("snapshot", roles.Snapshot()) 158 | if err != nil { 159 | panic(fmt.Sprintln("generate_metadata.go:", "verifying snapshot metadata failed", err)) 160 | } 161 | 162 | // Verify timestamp 163 | err = roles.Root().VerifyDelegate("timestamp", roles.Timestamp()) 164 | if err != nil { 165 | panic(fmt.Sprintln("generate_metadata.go:", "verifying timestamp metadata failed", err)) 166 | } 167 | 168 | fmt.Println("Done! Metadata files location:", cwd) 169 | } 170 | 171 | // helperExpireIn returns time offset by years (for the sake of the example) 172 | func helperExpireIn(years int) time.Time { 173 | return time.Now().AddDate(years, 0, 0).UTC() 174 | } 175 | 176 | // helperGetPathForTarget returns local and target paths for target 177 | func helperGetPathForTarget(name string) (string, string) { 178 | cwd, err := os.Getwd() 179 | if err != nil { 180 | panic(fmt.Sprintln("generate_metadata.go:", "getting cwd failed", err)) 181 | } 182 | // _, dir := filepath.Split(cwd) 183 | // return filepath.Join(dir, name), filepath.Join(cwd, name) 184 | return name, filepath.Join(cwd, name) 185 | } 186 | 187 | func copyHashPrefixed(src string, hash string) error { 188 | data, err := os.ReadFile(src) 189 | if err != nil { 190 | return err 191 | } 192 | dirName, fileName := filepath.Split(src) 193 | err = os.WriteFile(filepath.Join(dirName, fmt.Sprintf("%s.%s", hash, fileName)), data, 0644) 194 | if err != nil { 195 | return err 196 | } 197 | return nil 198 | } 199 | -------------------------------------------------------------------------------- /examples/multirepo/repository/metadata/1.root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "8be9c0369e7f2e2aacd9fde0267c5ab224bd65b3e8f892f09168050c53dd1a4d", 5 | "sig": "86e985ac792a7c92489b781e371c9c4ea0b4ba944f56f19783df8404cc7d76dc2ea44b13fd508bd70cbf9cd481e155b4a41ce0b4f030ea481bea9015fa055c06" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "root", 10 | "consistent_snapshot": true, 11 | "expires": "2034-09-30T11:29:58.188964Z", 12 | "keys": { 13 | "1e0c46c0f988b6f45eb0956ed6b0836697a39dca123c56453a3d8ad57c64726b": { 14 | "keytype": "ed25519", 15 | "keyval": { 16 | "public": "a5ebf16e4bfec00df5b3a0a580ef1edf8f5c786f398ad82e4bf1b3761c39fc9b" 17 | }, 18 | "scheme": "ed25519" 19 | }, 20 | "6ee434fdb4e723ed7d5c556a34e5fabc7412c37ce652dd1a4aeec1e06f86a44c": { 21 | "keytype": "ed25519", 22 | "keyval": { 23 | "public": "c934af418d5a32992e5ab6e1bb1a0fddbd0d944654b9dbe53b507b1552ac7057" 24 | }, 25 | "scheme": "ed25519" 26 | }, 27 | "8be9c0369e7f2e2aacd9fde0267c5ab224bd65b3e8f892f09168050c53dd1a4d": { 28 | "keytype": "ed25519", 29 | "keyval": { 30 | "public": "e48f729e90a19ac8cf227d7a5e56dcfd52bdc30258fc426255c856959935cb9e" 31 | }, 32 | "scheme": "ed25519" 33 | }, 34 | "b779120edb45353d2a151004fe463ec6f10d90d83c0fa1c755e4e436e2ac8009": { 35 | "keytype": "ed25519", 36 | "keyval": { 37 | "public": "097df4cf52f263630b0e5dfac96b2955b83a253842d0e7fcaffa121a349e6efa" 38 | }, 39 | "scheme": "ed25519" 40 | } 41 | }, 42 | "roles": { 43 | "root": { 44 | "keyids": [ 45 | "8be9c0369e7f2e2aacd9fde0267c5ab224bd65b3e8f892f09168050c53dd1a4d" 46 | ], 47 | "threshold": 1 48 | }, 49 | "snapshot": { 50 | "keyids": [ 51 | "6ee434fdb4e723ed7d5c556a34e5fabc7412c37ce652dd1a4aeec1e06f86a44c" 52 | ], 53 | "threshold": 1 54 | }, 55 | "targets": { 56 | "keyids": [ 57 | "b779120edb45353d2a151004fe463ec6f10d90d83c0fa1c755e4e436e2ac8009" 58 | ], 59 | "threshold": 1 60 | }, 61 | "timestamp": { 62 | "keyids": [ 63 | "1e0c46c0f988b6f45eb0956ed6b0836697a39dca123c56453a3d8ad57c64726b" 64 | ], 65 | "threshold": 1 66 | } 67 | }, 68 | "spec_version": "1.0.31", 69 | "version": 1 70 | } 71 | } -------------------------------------------------------------------------------- /examples/multirepo/repository/metadata/1.snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "6ee434fdb4e723ed7d5c556a34e5fabc7412c37ce652dd1a4aeec1e06f86a44c", 5 | "sig": "d585b3ee76eb84c386215ec96d4c09a1d24a895d4620b1275b68110e255604037e180deb666aae9bf62ff7cda1844a0f1ddad34e8e911833482c7fcaf6a0cd07" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "snapshot", 10 | "expires": "2034-09-30T11:29:58.188962Z", 11 | "meta": { 12 | "targets.json": { 13 | "version": 1 14 | } 15 | }, 16 | "spec_version": "1.0.31", 17 | "version": 1 18 | } 19 | } -------------------------------------------------------------------------------- /examples/multirepo/repository/metadata/1.targets.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "b779120edb45353d2a151004fe463ec6f10d90d83c0fa1c755e4e436e2ac8009", 5 | "sig": "548ef3fb01c6def4b972444e05a5bc7a7c0e5750eb0c78dc04bd52997849185b3ec72a440157a467c42ff6dd63a808a047a0aee944855e319734bb927e9d7e05" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "targets", 10 | "expires": "2034-09-30T11:29:58.187585Z", 11 | "spec_version": "1.0.31", 12 | "targets": { 13 | "map.json": { 14 | "hashes": { 15 | "sha256": "562fc7cffe872542a430342995998546e3949dc3acbe7d37668dc76d657032ff" 16 | }, 17 | "length": 596 18 | }, 19 | "sigstore-tuf-root/root.json": { 20 | "hashes": { 21 | "sha256": "e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4" 22 | }, 23 | "length": 6388 24 | }, 25 | "staging/root.json": { 26 | "hashes": { 27 | "sha256": "e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4" 28 | }, 29 | "length": 6388 30 | } 31 | }, 32 | "version": 1 33 | } 34 | } -------------------------------------------------------------------------------- /examples/multirepo/repository/metadata/timestamp.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "1e0c46c0f988b6f45eb0956ed6b0836697a39dca123c56453a3d8ad57c64726b", 5 | "sig": "29e2506d14263991f9a178a2197921fc9acd33725a82705fc49fc7a22d50603e5d7faf0a8a70e70252c397ed5ad08d5632c102988741502ad26a0481f881cc07" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "timestamp", 10 | "expires": "2034-09-30T11:29:58.188963Z", 11 | "meta": { 12 | "snapshot.json": { 13 | "version": 1 14 | } 15 | }, 16 | "spec_version": "1.0.31", 17 | "version": 1 18 | } 19 | } -------------------------------------------------------------------------------- /examples/multirepo/repository/targets/562fc7cffe872542a430342995998546e3949dc3acbe7d37668dc76d657032ff.map.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "repositories": { 4 | "sigstore-tuf-root": ["https://tuf-repo-cdn.sigstore.dev"], 5 | "staging": ["https://tuf-repo-cdn.sigstore.dev"] 6 | }, 7 | "mapping": [ 8 | { 9 | "paths": ["fulcio*", "*.json"], 10 | "repositories": ["staging"], 11 | "threshold": 1, 12 | "terminating": true 13 | }, 14 | { 15 | "paths": ["*.pub"], 16 | "repositories": ["sigstore-tuf-root", "staging"], 17 | "threshold": 2, 18 | "terminating": false 19 | }, 20 | { 21 | "paths": ["*"], 22 | "repositories": ["sigstore-tuf-root"], 23 | "terminating": true, 24 | "threshold": 1 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /examples/multirepo/repository/targets/map.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "repositories": { 4 | "sigstore-tuf-root": ["https://tuf-repo-cdn.sigstore.dev"], 5 | "staging": ["https://tuf-repo-cdn.sigstore.dev"] 6 | }, 7 | "mapping": [ 8 | { 9 | "paths": ["fulcio*", "*.json"], 10 | "repositories": ["staging"], 11 | "threshold": 1, 12 | "terminating": true 13 | }, 14 | { 15 | "paths": ["*.pub"], 16 | "repositories": ["sigstore-tuf-root", "staging"], 17 | "threshold": 2, 18 | "terminating": false 19 | }, 20 | { 21 | "paths": ["*"], 22 | "repositories": ["sigstore-tuf-root"], 23 | "terminating": true, 24 | "threshold": 1 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /examples/multirepo/repository/targets/sigstore-tuf-root/e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "root", 4 | "spec_version": "1.0", 5 | "version": 5, 6 | "expires": "2023-04-18T18:13:43Z", 7 | "keys": { 8 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { 9 | "keytype": "ecdsa-sha2-nistp256", 10 | "scheme": "ecdsa-sha2-nistp256", 11 | "keyid_hash_algorithms": [ 12 | "sha256", 13 | "sha512" 14 | ], 15 | "keyval": { 16 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" 17 | } 18 | }, 19 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": { 20 | "keytype": "ecdsa-sha2-nistp256", 21 | "scheme": "ecdsa-sha2-nistp256", 22 | "keyid_hash_algorithms": [ 23 | "sha256", 24 | "sha512" 25 | ], 26 | "keyval": { 27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" 28 | } 29 | }, 30 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": { 31 | "keytype": "ecdsa-sha2-nistp256", 32 | "scheme": "ecdsa-sha2-nistp256", 33 | "keyid_hash_algorithms": [ 34 | "sha256", 35 | "sha512" 36 | ], 37 | "keyval": { 38 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n" 39 | } 40 | }, 41 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": { 42 | "keytype": "ecdsa-sha2-nistp256", 43 | "scheme": "ecdsa-sha2-nistp256", 44 | "keyid_hash_algorithms": [ 45 | "sha256", 46 | "sha512" 47 | ], 48 | "keyval": { 49 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" 50 | } 51 | }, 52 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": { 53 | "keytype": "ecdsa-sha2-nistp256", 54 | "scheme": "ecdsa-sha2-nistp256", 55 | "keyid_hash_algorithms": [ 56 | "sha256", 57 | "sha512" 58 | ], 59 | "keyval": { 60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" 61 | } 62 | }, 63 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": { 64 | "keytype": "ecdsa-sha2-nistp256", 65 | "scheme": "ecdsa-sha2-nistp256", 66 | "keyid_hash_algorithms": [ 67 | "sha256", 68 | "sha512" 69 | ], 70 | "keyval": { 71 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" 72 | } 73 | }, 74 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": { 75 | "keytype": "ecdsa-sha2-nistp256", 76 | "scheme": "ecdsa-sha2-nistp256", 77 | "keyid_hash_algorithms": [ 78 | "sha256", 79 | "sha512" 80 | ], 81 | "keyval": { 82 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" 83 | } 84 | } 85 | }, 86 | "roles": { 87 | "root": { 88 | "keyids": [ 89 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 90 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 91 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", 92 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 93 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" 94 | ], 95 | "threshold": 3 96 | }, 97 | "snapshot": { 98 | "keyids": [ 99 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b" 100 | ], 101 | "threshold": 1 102 | }, 103 | "targets": { 104 | "keyids": [ 105 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 106 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 107 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", 108 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 109 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" 110 | ], 111 | "threshold": 3 112 | }, 113 | "timestamp": { 114 | "keyids": [ 115 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a" 116 | ], 117 | "threshold": 1 118 | } 119 | }, 120 | "consistent_snapshot": true 121 | }, 122 | "signatures": [ 123 | { 124 | "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 125 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" 126 | }, 127 | { 128 | "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 129 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" 130 | }, 131 | { 132 | "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 133 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" 134 | }, 135 | { 136 | "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", 137 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" 138 | }, 139 | { 140 | "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", 141 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" 142 | }, 143 | { 144 | "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", 145 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" 146 | }, 147 | { 148 | "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", 149 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" 150 | }, 151 | { 152 | "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90", 153 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" 154 | } 155 | ] 156 | } -------------------------------------------------------------------------------- /examples/multirepo/repository/targets/sigstore-tuf-root/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "root", 4 | "spec_version": "1.0", 5 | "version": 5, 6 | "expires": "2023-04-18T18:13:43Z", 7 | "keys": { 8 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { 9 | "keytype": "ecdsa-sha2-nistp256", 10 | "scheme": "ecdsa-sha2-nistp256", 11 | "keyid_hash_algorithms": [ 12 | "sha256", 13 | "sha512" 14 | ], 15 | "keyval": { 16 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" 17 | } 18 | }, 19 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": { 20 | "keytype": "ecdsa-sha2-nistp256", 21 | "scheme": "ecdsa-sha2-nistp256", 22 | "keyid_hash_algorithms": [ 23 | "sha256", 24 | "sha512" 25 | ], 26 | "keyval": { 27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" 28 | } 29 | }, 30 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": { 31 | "keytype": "ecdsa-sha2-nistp256", 32 | "scheme": "ecdsa-sha2-nistp256", 33 | "keyid_hash_algorithms": [ 34 | "sha256", 35 | "sha512" 36 | ], 37 | "keyval": { 38 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n" 39 | } 40 | }, 41 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": { 42 | "keytype": "ecdsa-sha2-nistp256", 43 | "scheme": "ecdsa-sha2-nistp256", 44 | "keyid_hash_algorithms": [ 45 | "sha256", 46 | "sha512" 47 | ], 48 | "keyval": { 49 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" 50 | } 51 | }, 52 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": { 53 | "keytype": "ecdsa-sha2-nistp256", 54 | "scheme": "ecdsa-sha2-nistp256", 55 | "keyid_hash_algorithms": [ 56 | "sha256", 57 | "sha512" 58 | ], 59 | "keyval": { 60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" 61 | } 62 | }, 63 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": { 64 | "keytype": "ecdsa-sha2-nistp256", 65 | "scheme": "ecdsa-sha2-nistp256", 66 | "keyid_hash_algorithms": [ 67 | "sha256", 68 | "sha512" 69 | ], 70 | "keyval": { 71 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" 72 | } 73 | }, 74 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": { 75 | "keytype": "ecdsa-sha2-nistp256", 76 | "scheme": "ecdsa-sha2-nistp256", 77 | "keyid_hash_algorithms": [ 78 | "sha256", 79 | "sha512" 80 | ], 81 | "keyval": { 82 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" 83 | } 84 | } 85 | }, 86 | "roles": { 87 | "root": { 88 | "keyids": [ 89 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 90 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 91 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", 92 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 93 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" 94 | ], 95 | "threshold": 3 96 | }, 97 | "snapshot": { 98 | "keyids": [ 99 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b" 100 | ], 101 | "threshold": 1 102 | }, 103 | "targets": { 104 | "keyids": [ 105 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 106 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 107 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", 108 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 109 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" 110 | ], 111 | "threshold": 3 112 | }, 113 | "timestamp": { 114 | "keyids": [ 115 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a" 116 | ], 117 | "threshold": 1 118 | } 119 | }, 120 | "consistent_snapshot": true 121 | }, 122 | "signatures": [ 123 | { 124 | "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 125 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" 126 | }, 127 | { 128 | "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 129 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" 130 | }, 131 | { 132 | "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 133 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" 134 | }, 135 | { 136 | "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", 137 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" 138 | }, 139 | { 140 | "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", 141 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" 142 | }, 143 | { 144 | "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", 145 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" 146 | }, 147 | { 148 | "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", 149 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" 150 | }, 151 | { 152 | "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90", 153 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" 154 | } 155 | ] 156 | } -------------------------------------------------------------------------------- /examples/multirepo/repository/targets/staging/e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "root", 4 | "spec_version": "1.0", 5 | "version": 5, 6 | "expires": "2023-04-18T18:13:43Z", 7 | "keys": { 8 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { 9 | "keytype": "ecdsa-sha2-nistp256", 10 | "scheme": "ecdsa-sha2-nistp256", 11 | "keyid_hash_algorithms": [ 12 | "sha256", 13 | "sha512" 14 | ], 15 | "keyval": { 16 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" 17 | } 18 | }, 19 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": { 20 | "keytype": "ecdsa-sha2-nistp256", 21 | "scheme": "ecdsa-sha2-nistp256", 22 | "keyid_hash_algorithms": [ 23 | "sha256", 24 | "sha512" 25 | ], 26 | "keyval": { 27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" 28 | } 29 | }, 30 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": { 31 | "keytype": "ecdsa-sha2-nistp256", 32 | "scheme": "ecdsa-sha2-nistp256", 33 | "keyid_hash_algorithms": [ 34 | "sha256", 35 | "sha512" 36 | ], 37 | "keyval": { 38 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n" 39 | } 40 | }, 41 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": { 42 | "keytype": "ecdsa-sha2-nistp256", 43 | "scheme": "ecdsa-sha2-nistp256", 44 | "keyid_hash_algorithms": [ 45 | "sha256", 46 | "sha512" 47 | ], 48 | "keyval": { 49 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" 50 | } 51 | }, 52 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": { 53 | "keytype": "ecdsa-sha2-nistp256", 54 | "scheme": "ecdsa-sha2-nistp256", 55 | "keyid_hash_algorithms": [ 56 | "sha256", 57 | "sha512" 58 | ], 59 | "keyval": { 60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" 61 | } 62 | }, 63 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": { 64 | "keytype": "ecdsa-sha2-nistp256", 65 | "scheme": "ecdsa-sha2-nistp256", 66 | "keyid_hash_algorithms": [ 67 | "sha256", 68 | "sha512" 69 | ], 70 | "keyval": { 71 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" 72 | } 73 | }, 74 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": { 75 | "keytype": "ecdsa-sha2-nistp256", 76 | "scheme": "ecdsa-sha2-nistp256", 77 | "keyid_hash_algorithms": [ 78 | "sha256", 79 | "sha512" 80 | ], 81 | "keyval": { 82 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" 83 | } 84 | } 85 | }, 86 | "roles": { 87 | "root": { 88 | "keyids": [ 89 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 90 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 91 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", 92 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 93 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" 94 | ], 95 | "threshold": 3 96 | }, 97 | "snapshot": { 98 | "keyids": [ 99 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b" 100 | ], 101 | "threshold": 1 102 | }, 103 | "targets": { 104 | "keyids": [ 105 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 106 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 107 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", 108 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 109 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" 110 | ], 111 | "threshold": 3 112 | }, 113 | "timestamp": { 114 | "keyids": [ 115 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a" 116 | ], 117 | "threshold": 1 118 | } 119 | }, 120 | "consistent_snapshot": true 121 | }, 122 | "signatures": [ 123 | { 124 | "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 125 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" 126 | }, 127 | { 128 | "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 129 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" 130 | }, 131 | { 132 | "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 133 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" 134 | }, 135 | { 136 | "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", 137 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" 138 | }, 139 | { 140 | "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", 141 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" 142 | }, 143 | { 144 | "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", 145 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" 146 | }, 147 | { 148 | "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", 149 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" 150 | }, 151 | { 152 | "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90", 153 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" 154 | } 155 | ] 156 | } -------------------------------------------------------------------------------- /examples/multirepo/repository/targets/staging/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signed": { 3 | "_type": "root", 4 | "spec_version": "1.0", 5 | "version": 5, 6 | "expires": "2023-04-18T18:13:43Z", 7 | "keys": { 8 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": { 9 | "keytype": "ecdsa-sha2-nistp256", 10 | "scheme": "ecdsa-sha2-nistp256", 11 | "keyid_hash_algorithms": [ 12 | "sha256", 13 | "sha512" 14 | ], 15 | "keyval": { 16 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n" 17 | } 18 | }, 19 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": { 20 | "keytype": "ecdsa-sha2-nistp256", 21 | "scheme": "ecdsa-sha2-nistp256", 22 | "keyid_hash_algorithms": [ 23 | "sha256", 24 | "sha512" 25 | ], 26 | "keyval": { 27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n" 28 | } 29 | }, 30 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": { 31 | "keytype": "ecdsa-sha2-nistp256", 32 | "scheme": "ecdsa-sha2-nistp256", 33 | "keyid_hash_algorithms": [ 34 | "sha256", 35 | "sha512" 36 | ], 37 | "keyval": { 38 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n" 39 | } 40 | }, 41 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": { 42 | "keytype": "ecdsa-sha2-nistp256", 43 | "scheme": "ecdsa-sha2-nistp256", 44 | "keyid_hash_algorithms": [ 45 | "sha256", 46 | "sha512" 47 | ], 48 | "keyval": { 49 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n" 50 | } 51 | }, 52 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": { 53 | "keytype": "ecdsa-sha2-nistp256", 54 | "scheme": "ecdsa-sha2-nistp256", 55 | "keyid_hash_algorithms": [ 56 | "sha256", 57 | "sha512" 58 | ], 59 | "keyval": { 60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n" 61 | } 62 | }, 63 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": { 64 | "keytype": "ecdsa-sha2-nistp256", 65 | "scheme": "ecdsa-sha2-nistp256", 66 | "keyid_hash_algorithms": [ 67 | "sha256", 68 | "sha512" 69 | ], 70 | "keyval": { 71 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n" 72 | } 73 | }, 74 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": { 75 | "keytype": "ecdsa-sha2-nistp256", 76 | "scheme": "ecdsa-sha2-nistp256", 77 | "keyid_hash_algorithms": [ 78 | "sha256", 79 | "sha512" 80 | ], 81 | "keyval": { 82 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n" 83 | } 84 | } 85 | }, 86 | "roles": { 87 | "root": { 88 | "keyids": [ 89 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 90 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 91 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", 92 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 93 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" 94 | ], 95 | "threshold": 3 96 | }, 97 | "snapshot": { 98 | "keyids": [ 99 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b" 100 | ], 101 | "threshold": 1 102 | }, 103 | "targets": { 104 | "keyids": [ 105 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 106 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 107 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f", 108 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 109 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de" 110 | ], 111 | "threshold": 3 112 | }, 113 | "timestamp": { 114 | "keyids": [ 115 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a" 116 | ], 117 | "threshold": 1 118 | } 119 | }, 120 | "consistent_snapshot": true 121 | }, 122 | "signatures": [ 123 | { 124 | "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c", 125 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" 126 | }, 127 | { 128 | "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99", 129 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" 130 | }, 131 | { 132 | "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b", 133 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" 134 | }, 135 | { 136 | "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de", 137 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" 138 | }, 139 | { 140 | "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", 141 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a" 142 | }, 143 | { 144 | "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", 145 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d" 146 | }, 147 | { 148 | "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", 149 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b" 150 | }, 151 | { 152 | "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90", 153 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9" 154 | } 155 | ] 156 | } -------------------------------------------------------------------------------- /examples/repository/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package repository 19 | 20 | import ( 21 | "github.com/theupdateframework/go-tuf/v2/metadata" 22 | ) 23 | 24 | // Type struct for storing metadata 25 | type Type struct { 26 | root *metadata.Metadata[metadata.RootType] 27 | snapshot *metadata.Metadata[metadata.SnapshotType] 28 | timestamp *metadata.Metadata[metadata.TimestampType] 29 | targets map[string]*metadata.Metadata[metadata.TargetsType] 30 | } 31 | 32 | // New creates an empty repository instance 33 | func New() *Type { 34 | return &Type{ 35 | targets: map[string]*metadata.Metadata[metadata.TargetsType]{}, 36 | } 37 | } 38 | 39 | // Root returns metadata of type Root 40 | func (r *Type) Root() *metadata.Metadata[metadata.RootType] { 41 | return r.root 42 | } 43 | 44 | // SetRoot sets metadata of type Root 45 | func (r *Type) SetRoot(meta *metadata.Metadata[metadata.RootType]) { 46 | r.root = meta 47 | } 48 | 49 | // Snapshot returns metadata of type Snapshot 50 | func (r *Type) Snapshot() *metadata.Metadata[metadata.SnapshotType] { 51 | return r.snapshot 52 | } 53 | 54 | // SetSnapshot sets metadata of type Snapshot 55 | func (r *Type) SetSnapshot(meta *metadata.Metadata[metadata.SnapshotType]) { 56 | r.snapshot = meta 57 | } 58 | 59 | // Timestamp returns metadata of type Timestamp 60 | func (r *Type) Timestamp() *metadata.Metadata[metadata.TimestampType] { 61 | return r.timestamp 62 | } 63 | 64 | // SetTimestamp sets metadata of type Timestamp 65 | func (r *Type) SetTimestamp(meta *metadata.Metadata[metadata.TimestampType]) { 66 | r.timestamp = meta 67 | } 68 | 69 | // Targets returns metadata of type Targets 70 | func (r *Type) Targets(name string) *metadata.Metadata[metadata.TargetsType] { 71 | return r.targets[name] 72 | } 73 | 74 | // SetTargets sets metadata of type Targets 75 | func (r *Type) SetTargets(name string, meta *metadata.Metadata[metadata.TargetsType]) { 76 | r.targets[name] = meta 77 | } 78 | -------------------------------------------------------------------------------- /examples/repository/repository/repository_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package repository 19 | 20 | import ( 21 | "testing" 22 | "time" 23 | 24 | "github.com/stretchr/testify/assert" 25 | "github.com/theupdateframework/go-tuf/v2/metadata" 26 | ) 27 | 28 | func TestNewRepository(t *testing.T) { 29 | repo := New() 30 | 31 | now := time.Now().UTC() 32 | safeExpiry := now.Truncate(time.Second).AddDate(0, 0, 30) 33 | 34 | root := metadata.Root(safeExpiry) 35 | repo.SetRoot(root) 36 | assert.Equal(t, "root", repo.Root().Signed.Type) 37 | assert.Equal(t, int64(1), repo.Root().Signed.Version) 38 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Root().Signed.SpecVersion) 39 | 40 | targets := metadata.Targets(safeExpiry) 41 | repo.SetTargets("targets", targets) 42 | assert.Equal(t, "targets", repo.Targets("targets").Signed.Type) 43 | assert.Equal(t, int64(1), repo.Targets("targets").Signed.Version) 44 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Targets("targets").Signed.SpecVersion) 45 | 46 | timestamp := metadata.Timestamp(safeExpiry) 47 | repo.SetTimestamp(timestamp) 48 | // repo.SetRoot(root) 49 | assert.Equal(t, "timestamp", repo.Timestamp().Signed.Type) 50 | assert.Equal(t, int64(1), repo.Timestamp().Signed.Version) 51 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Timestamp().Signed.SpecVersion) 52 | 53 | snapshot := metadata.Snapshot(safeExpiry) 54 | repo.SetSnapshot(snapshot) 55 | assert.Equal(t, "snapshot", repo.Snapshot().Signed.Type) 56 | assert.Equal(t, int64(1), repo.Snapshot().Signed.Version) 57 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Snapshot().Signed.SpecVersion) 58 | } 59 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/theupdateframework/go-tuf/v2 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/cenkalti/backoff/v5 v5.0.2 7 | github.com/go-logr/stdr v1.2.2 8 | github.com/secure-systems-lab/go-securesystemslib v0.9.0 9 | github.com/sigstore/sigstore v1.8.4 10 | github.com/spf13/cobra v1.9.1 11 | github.com/stretchr/testify v1.10.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/go-logr/logr v1.3.0 // indirect 17 | github.com/google/go-containerregistry v0.19.1 // indirect 18 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 19 | github.com/kr/pretty v0.3.1 // indirect 20 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect 21 | github.com/opencontainers/go-digest v1.0.0 // indirect 22 | github.com/pmezard/go-difflib v1.0.0 // indirect 23 | github.com/spf13/pflag v1.0.6 // indirect 24 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect 25 | golang.org/x/crypto v0.35.0 // indirect 26 | golang.org/x/sys v0.30.0 // indirect 27 | golang.org/x/term v0.29.0 // indirect 28 | google.golang.org/grpc v1.56.3 // indirect 29 | gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= 4 | github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 5 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 6 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 8 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 12 | github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= 13 | github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 14 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 15 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 16 | github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= 17 | github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 18 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 19 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 20 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 21 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 22 | github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= 23 | github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= 24 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 25 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 26 | github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= 27 | github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= 28 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 29 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 30 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 31 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 32 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e h1:RLTpX495BXToqxpM90Ws4hXEo4Wfh81jr9DX1n/4WOo= 33 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e/go.mod h1:EAuqr9VFWxBi9nD5jc/EA2MT1RFty9288TF6zdtYoCU= 34 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 35 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 36 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 37 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 38 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 39 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 40 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 41 | github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= 42 | github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= 43 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 44 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 45 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= 46 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= 47 | github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= 48 | github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= 49 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 50 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 51 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 52 | github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= 53 | github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= 54 | github.com/sigstore/sigstore v1.8.4 h1:g4ICNpiENFnWxjmBzBDWUn62rNFeny/P77HUC8da32w= 55 | github.com/sigstore/sigstore v1.8.4/go.mod h1:1jIKtkTFEeISen7en+ZPWdDHazqhxco/+v9CNjc7oNg= 56 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 57 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 58 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 59 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 60 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 61 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 62 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= 63 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= 64 | go.opentelemetry.io/otel v1.15.0 h1:NIl24d4eiLJPM0vKn4HjLYM+UZf6gSfi9Z+NmCxkWbk= 65 | go.opentelemetry.io/otel v1.15.0/go.mod h1:qfwLEbWhLPk5gyWrne4XnF0lC8wtywbuJbgfAE3zbek= 66 | go.opentelemetry.io/otel/trace v1.15.0 h1:5Fwje4O2ooOxkfyqI/kJwxWotggDLix4BSAvpE1wlpo= 67 | go.opentelemetry.io/otel/trace v1.15.0/go.mod h1:CUsmE2Ht1CRkvE8OsMESvraoZrrcgD1J2W8GV1ev0Y4= 68 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 69 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 70 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 71 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 72 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 73 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 74 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= 75 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= 76 | google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= 77 | google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 78 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 79 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 80 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 81 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 82 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 83 | gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= 84 | gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= 85 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 86 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 87 | -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/delegation_key: -------------------------------------------------------------------------------- 1 | 68593a508472ad3007915379e6b1f3c0@@@@100000@@@@615986af4d1ba89aeadc2f489f89b0e8d46da133a6f75c7b162b8f99f63f86ed@@@@8319255f9856c4f40f9d71bc10e79e5d@@@@1dc7b20f1c668a1f544dc39c7a9fcb3c4a4dd34d1cc8c9d8f779bab026cf0b8e0f46e53bc5ed20bf0e5048b94a5d2ea176e79c12bcc7daa65cd55bf810deebeec5bc903ce9e5316d7dbba88f1a2b51d3f9bc782f8fa9b21dff91609ad0260e21a2039223f816d0fe97ace2e204d0025d327b38d27aa6cd87e85aa8883bfcb6d12f93155d72ffd3c7717a0570cf9811eb6d6a340baa0f27433315d83322c685fec02053ff8c173c4ebf91a258e83402f39546821e3352baa7b246e33b2a573a8ff7b289682407abbcb9184249d4304db68d3bf8e124e94377fd62dde5c4f3b7617d483776345154d047d139b1e559351577da315f54e16153c510159e1908231574bcf49c4f96cafe6530e86a09e9eee47bcff78f2fed2984754c895733938999ff085f9e3532d7174fd76dc09921506dd2137e16ec4926998f5d9df8a8ffb3e6649c71bc32571b2e24357739fa1a56be -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/delegation_key.pub: -------------------------------------------------------------------------------- 1 | {"keyval": {"public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/root_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDydf/VEpxBOCDoxpM6IVhq9i67P9BiVv2zwZSUO/M0RTToAvFv 3 | NgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7WIT+mwy86/fIdnllJDMw5tmL 4 | r2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91iqpdRVa3zJw5ALvLdwIDAQAB 5 | AoGBAJlhwoUVb9nmWxNGw86LV7bapDd6qCX96CL2PDsGLdWMTmrTqc5zuE5NkBZz 6 | z2THvISWIJE/l6gHQJv1uBDbMxfquhK40k+GfE/fApVODN8KeBLLRUzYyHNz7KwW 7 | aNF3jY8AbO4HzWpdaFYce5r+YqlWZoaVPR9i6LCW3sZXALyRAkEA/lSVaT0azp55 8 | 2GI4Gn+EQQFqFJWEbNwJ8i3FZ4aG+/gnw2WmxJr+2nQcUlLb2cpQCCcMyWxvCfLK 9 | +DapvvgZXwJBAPQNd+liOrKKd1gPR3S6y+D4h1ewj8ii1MHzRtAsCKCRG/e+v+hC 10 | xp77Rc/qtZXKvVTGrccnKqCVAvG7F15rzOkCQQDCswgKn6+0+5c1ssNWbcZWaXnH 11 | NktBdxXaI3Ya8d7GaEwwhtIrcqilnfvMfgg2a23nP9XHIU7EI+2EJXy/aHkrAkBH 12 | wH30u9COFW+pEDTt+M1gQzFncp2TW2w56ZB0O739lywl1osNejRzIWURD+x7MbQg 13 | bJlC6Bz8QVMwRtVECWWhAkAflD6eIJeceDhVHClHB/QwmF8mwR1o63RN7ZFlgel1 14 | kwMt6bPZZ1cyrRoj6Cdi4pyqBssDBuQmbBLWyYuijIwz 15 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/root_key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDydf/VEpxBOCDoxpM6IVhq9i67 3 | P9BiVv2zwZSUO/M0RTToAvFvNgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7 4 | WIT+mwy86/fIdnllJDMw5tmLr2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91 5 | iqpdRVa3zJw5ALvLdwIDAQAB 6 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/root_key2: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXwIBAAKBgQC9sG1iNSW+JbnSuITAuWL46YphgqOXU3DhGZp2hM/1FaaRzLlR 3 | HTyH7EvzbKqlaGBs9Caatx6KlN7e8J45vpLO1PkKnLOHdKBuy39Fzxw1dhxJWvTI 4 | 0mNDv68JbWxvgRxjEPE7wc24jYubovxiiBD8g2DbOnWEkyt7owECPRlXAwIDAQAB 5 | AoGBAKquh54oqG9yTsRXF8y6g13p9oRLIpxVjmpduWkPlHe5JYpnphBguEitzKGa 6 | k+oGA03GWr44K5kS33/HDvhyjHFXCqniittlVUwbaa4kbJiWi4lQ3K/m8F2DzHJP 7 | s4YaqmzG30v9j9z3nOgLhM7iye65beala72zJnGOXivUAuhRAkEA3KnfY/SFjTbo 8 | rsluVa03hC5KVQm/yDf4wShDnl8drYHnJ1XFkSC0UbBruRyu8JeWE93dAKu9IxdK 9 | WEdHOtxR3wJBANwQwX/wPJ8/+yo4lToyUuN0omx94sK/JuRmvi9dzCbdfQbkgvDa 10 | MEyWc0LNwxDBPYJ2bej/ORGmD+nOJo59h10CQQCCj/x+jvrKcFfCu6qOBRyZGC6h 11 | HFCebgfAektwFIVh2T/lNUndsgUfZIyIjeEwt/Bzts2CDRuu/KPfkeUifaPvAkEA 12 | m9iR8FTl2bGp4cCojcpNwR88V7DfAiP1GxNX5Jt8lJmOjW8O/BrI0bRKdCjb1+XB 13 | 9b6BH9x/QexkoKOJ0qc7UQJBAINLHep9QG2b3AEGZ692Z+iyU1Lub7rWNBKsmodh 14 | 0x9rwYs0D0EJo0BYozYhExz7ugaSzXW61H26IWbHtsg+5a0= 15 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/root_key2.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9sG1iNSW+JbnSuITAuWL46Yph 3 | gqOXU3DhGZp2hM/1FaaRzLlRHTyH7EvzbKqlaGBs9Caatx6KlN7e8J45vpLO1PkK 4 | nLOHdKBuy39Fzxw1dhxJWvTI0mNDv68JbWxvgRxjEPE7wc24jYubovxiiBD8g2Db 5 | OnWEkyt7owECPRlXAwIDAQAB 6 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/snapshot_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQCPQoHresXRRRGoinN3bNn+BI23KolXdXLGqYkTvr9AjemUQJxb 3 | qmvZXHboQMAYw8OuBrRNt5Fz20wjsrJwOBEU5U3nHSJI4zYPGckYci0/0Eo2Kjws 4 | 5BmIj38qgIfhsH4zyZ4FZZ+GLRn+W3i3wl6SfRMC/HCg0DDwi75faC0vGQIDAQAB 5 | AoGAbPFYt2hf8qqhqRfQgysmA4QW+QnB895+8BCRC5DtA/xnerQ/s33AEkW8rxY+ 6 | fxawQjEbAFbup7pHBoaoJ6qbYbKDBSGgZFSEbh40nriX1V0oYb9E+BCAFHE+42Rj 7 | WYYNxXRp7LGoUQqisTsfoR1bvmrLC+9I/tDArHuMudm1slkCQQDOVn9AKTcaBGuQ 8 | Y+JQqoRmi9eMN6XztKIAKQ+P/57BofwlKJDFnwttsvMxRud6rvN1FCnCDM638HNb 9 | I0JDY0JXAkEAsb10uNV+SaWsHJOxfHzwK+uZJV1SkYzpBMizUREHuIyKT4MfpYNw 10 | kn00KpyCvhIp6buwNyYo76TssejYN86UDwJAGi3ZSU+xYQisiQ5TOX7Y+5XEjFLH 11 | KGuDnleXVOLOxqyBrElATQKH1aw9tMPVPLiTxQgA4FD1rVrBmA+aKaifUwJALBp8 12 | yhh/u7qWWIj1c5R07BEL8U+U23UBpSRACo+VQN/uuggpZCKXXmIe/avUbWGIcO0X 13 | rreTVNOxv/utGzvxVQJBAL7Kpqt9d50SL1ndLr2EdqGw8ZB/B2dKMlZf7AWwbk0k 14 | HHdvWfSDYhtvGo3ilLibHLesE/Tq1fm/2aEOds95/Eo= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/snapshot_key.pub: -------------------------------------------------------------------------------- 1 | {"keyval": {"public": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCG5DWxCcw4FW2G21RwTmuR7gdkv+ZrjZVOx0KsvJc/51QBxo/Y9xPVeoFF7YrhE8EV6A6b0qsLufIo1E63sQ6kjLOPfIMjag6dYPlmEyGcbxNDokv2elxZk7jS98iBQLxEmJLicrdERmxC2t2OOEQ6ELi5dt+C13QvNJFg4+OaTwIDAQAB"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} 2 | 3 | -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/targets_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQCjm6HPktvTGsygQ8Gvmu+zydTNe1zqoxLxV7mVRbmsCI4kn7JT 3 | Hc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3w8wqv8cuPxbmsFSxMoHfzBBI 4 | uJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNMDn+9T55B0WJYT/VPxwIDAQAB 5 | AoGANYaYRLHWS1WMNq6UMmBtJZPVlDhU6MrbSqwZojWCjj7qSh8ZF0o8AmiMdDxT 6 | wAJGZ17PyiQY1cQTEVvmaqWIfJKvipAcTvkiXFrAxeIf/HYIVfCP9UB8RqhJufsc 7 | XzDQyvZTmJdatHfKe2JV+q42GrsN4VN61wFEed3NuF8NGjECQQDSA5b+N1wMn5X4 8 | G5fxPYjhlwQmK3tlBHIPIVcVAsGOxU9Ry55xLQ8LpfKwJZIt2+LvgBIXf4DZY2u6 9 | GEnyR7epAkEAx267l7XX+9Dh8bHPluQSgH/tDrCp1hUNmyV4XzZCwavI/FaucANa 10 | h8ChpUOSZTq5mR76YaUL7O3Sx8N7L/2x7wJAZDvgYf6sCT5VhnAtCa+T2A+KpGkW 11 | YLVJdt0zwcxp8ylK3UAwo9Wcm7Oda+LSrN6IpkRa3io1pguki9Ix4NfH2QJATsXA 12 | NxZOb1p8RFk1Y6ZGYJcm7Wx+SN8b9rIAL6thBtpxkqoyUHAirAg8UOi1xGJDuOVx 13 | hGwKn9T4MotV9wi/5QJAB+1/2TaUMKjyL5Ca8Fh5SMigrwHp8SnX2vl7HV4hiBXi 14 | 0FaVxMPGH94tuFqHQ+q53tiTT1cp6YwcMMgpezTRRA== 15 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/targets_key.pub: -------------------------------------------------------------------------------- 1 | {"keyval": {"public": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjm6HPktvTGsygQ8Gvmu+zydTNe1zqoxLxV7mVRbmsCI4kn7JTHc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3w8wqv8cuPxbmsFSxMoHfzBBIuJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNMDn+9T55B0WJYT/VPxwIDAQAB"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/timestamp_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWgIBAAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHPTbyu7OImP6O+uHg8hui8C1nY 3 | /mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVstWpeCalZYWi55lZSLe9KixQIA 4 | yg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C5yqvqzcOoCTQMeWbAgMBAAEC 5 | gYAMlDvAUKS7NZOwCIj62FPDTADW2/juhjfOlcg6n7ItWkAG+3G2n5ndwruATSeY 6 | pNCA3H5+DmVeknlGU9LFvgx7dhJMw3WSkq7rImOGbwLN1jCVfwKP0AEEqb7GrtCU 7 | a9lvm2ZFvKj+2VVFS2yifeluDG1Xm10ygq+RDd2lL2g6eQJBAMZrMTUwxWT/Cc0j 8 | Yi7CFPl9V8GkYzLCKRQGR3x4QiNuXpNtQ3D+ivxHieBMEtw6M244PMDC+GpLxAfc 9 | DtiGEl8CQQCYGXeycwkgn2YfH3w1/Mw6TWsdv4rVLPOieiQPrhZbVsBc6NT24MYW 10 | b3c7osW5ypf7lo+xU8E6ylFUyeeVSk5FAkADTAqwSJQvHnHKP9lEz6LLloKbzCB9 11 | 2m4WUBhmABWRQyc9Keah/QjQMlwfJwR1Nl5eaX7Q8Sxxj7q9KrHwdSHfAkAS1yTC 12 | kAlTZytJM6c5MMVDe4+HMdDKszTCrYqF/rR6P/a4C4dFxXYEFW6ZjoIbj4LgAThv 13 | aMaIt8L3U8NB9OBZAkA3ke4kilnVnjEyB9ibJ/SbDiUgh7e7M/XDbNQuXwSipFft 14 | keBYEwL4Njms9uwMT4Gl59HyQls7BE2XEoiFjsY1 15 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /internal/testutils/repository_data/keystore/timestamp_key.pub: -------------------------------------------------------------------------------- 1 | {"keyval": {"public": "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHPTbyu7OImP6O+uHg8hui8C1nY/mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVstWpeCalZYWi55lZSLe9KixQIAyg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C5yqvqzcOoCTQMeWbAgMBAAE="}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/metadata/1.root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7", 5 | "sig": "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "root", 10 | "consistent_snapshot": true, 11 | "expires": "2030-08-15T14:30:45.0000001Z", 12 | "keys": { 13 | "0a5842e65e9c8c428354f40708435de6793ac379a275effe40d6358be2de835c": { 14 | "keytype": "ed25519", 15 | "keyval": { 16 | "public": "4e10fe156f07e6f6e1f6fb1579105b7d3e62790b6a62dbf7727b91f82d2bc9db" 17 | }, 18 | "scheme": "ed25519" 19 | }, 20 | "409fb816e403e0c00646665eac21cb8adfab8e318272ca7589b2d1fc0bccb255": { 21 | "keytype": "ed25519", 22 | "keyval": { 23 | "public": "23e5dc4eb18d5c116e76a92b02e44a7d7279622574457050b85fb8fd9260422c" 24 | }, 25 | "scheme": "ed25519" 26 | }, 27 | "700464ea12f4cb5f06a7512c75b73c0b6eeb2cd42854b085eed5b3c993607cba": { 28 | "keytype": "ed25519", 29 | "keyval": { 30 | "public": "1603f99998ca46c35c238a2c1a2a015e0f32b38771e4fa5401348ce0a677d63f" 31 | }, 32 | "scheme": "ed25519" 33 | }, 34 | "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7": { 35 | "keytype": "ed25519", 36 | "keyval": { 37 | "public": "17454b5e7a6594e7f00ceadda10d0267b94d0118b82f541f4f69f0d327c5a41a" 38 | }, 39 | "scheme": "ed25519" 40 | } 41 | }, 42 | "roles": { 43 | "root": { 44 | "keyids": [ 45 | "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7" 46 | ], 47 | "threshold": 1 48 | }, 49 | "snapshot": { 50 | "keyids": [ 51 | "700464ea12f4cb5f06a7512c75b73c0b6eeb2cd42854b085eed5b3c993607cba" 52 | ], 53 | "threshold": 1 54 | }, 55 | "targets": { 56 | "keyids": [ 57 | "409fb816e403e0c00646665eac21cb8adfab8e318272ca7589b2d1fc0bccb255" 58 | ], 59 | "threshold": 1 60 | }, 61 | "timestamp": { 62 | "keyids": [ 63 | "0a5842e65e9c8c428354f40708435de6793ac379a275effe40d6358be2de835c" 64 | ], 65 | "threshold": 1 66 | } 67 | }, 68 | "spec_version": "1.0.31", 69 | "version": 1, 70 | "test": "true" 71 | } 72 | } -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/metadata/role1.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", 5 | "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "targets", 10 | "delegations": { 11 | "keys": { 12 | "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { 13 | "keyid_hash_algorithms": [ 14 | "sha256", 15 | "sha512" 16 | ], 17 | "keytype": "ed25519", 18 | "keyval": { 19 | "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" 20 | }, 21 | "scheme": "ed25519" 22 | } 23 | }, 24 | "roles": [ 25 | { 26 | "keyids": [ 27 | "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" 28 | ], 29 | "name": "role2", 30 | "paths": [], 31 | "terminating": false, 32 | "threshold": 1 33 | } 34 | ] 35 | }, 36 | "expires": "2030-01-01T00:00:00Z", 37 | "spec_version": "1.0.0", 38 | "targets": { 39 | "file3.txt": { 40 | "hashes": { 41 | "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b", 42 | "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0" 43 | }, 44 | "length": 28 45 | } 46 | }, 47 | "version": 1 48 | } 49 | } -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/metadata/role2.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a", 5 | "sig": "75b196a224fd200e46e738b1216b3316c5384f61083872f8d14b8b0a378b2344e64b1a6f1a89a711206a66a0b199d65ac0e30fe15ddbc4de89fa8ff645f99403" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "targets", 10 | "expires": "2030-01-01T00:00:00Z", 11 | "spec_version": "1.0.0", 12 | "targets": {}, 13 | "version": 1 14 | } 15 | } -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/metadata/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2", 5 | "sig": "60d502a798f44577a76a1d7656a60099cd3a995d3d71a0a234aadbd16e38c14611920b8aef9ed78ca4ac0c02277cd72a6fc5ef484a3d66a6c70a61199e462681eb2e667046d4fbc2be1e50cf9fe00fda8fcd6534599eddc91716bf38e7fbbf375524fdb702c74076fd37dcd401d5263783150e851bdba9ef6f9c9a08adf6b289" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "root", 10 | "consistent_snapshot": true, 11 | "expires": "2030-08-15T14:30:45.0000001Z", 12 | "keys": { 13 | "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946": { 14 | "keytype": "rsa", 15 | "keyval": { 16 | "public": "-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHP\nTbyu7OImP6O+uHg8hui8C1nY/mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVst\nWpeCalZYWi55lZSLe9KixQIAyg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C\n5yqvqzcOoCTQMeWbAgMBAAE=\n-----END PUBLIC KEY-----\n" 17 | }, 18 | "scheme": "rsassa-pss-sha256" 19 | }, 20 | "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d": { 21 | "keytype": "rsa", 22 | "keyval": { 23 | "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjm6HPktvTGsygQ8Gvmu+zydTN\ne1zqoxLxV7mVRbmsCI4kn7JTHc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3\nw8wqv8cuPxbmsFSxMoHfzBBIuJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNM\nDn+9T55B0WJYT/VPxwIDAQAB\n-----END PUBLIC KEY-----\n" 24 | }, 25 | "scheme": "rsassa-pss-sha256" 26 | }, 27 | "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2": { 28 | "keytype": "rsa", 29 | "keyval": { 30 | "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDydf/VEpxBOCDoxpM6IVhq9i67\nP9BiVv2zwZSUO/M0RTToAvFvNgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7\nWIT+mwy86/fIdnllJDMw5tmLr2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91\niqpdRVa3zJw5ALvLdwIDAQAB\n-----END PUBLIC KEY-----\n" 31 | }, 32 | "scheme": "rsassa-pss-sha256" 33 | }, 34 | "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6": { 35 | "keytype": "rsa", 36 | "keyval": { 37 | "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCPQoHresXRRRGoinN3bNn+BI23\nKolXdXLGqYkTvr9AjemUQJxbqmvZXHboQMAYw8OuBrRNt5Fz20wjsrJwOBEU5U3n\nHSJI4zYPGckYci0/0Eo2Kjws5BmIj38qgIfhsH4zyZ4FZZ+GLRn+W3i3wl6SfRMC\n/HCg0DDwi75faC0vGQIDAQAB\n-----END PUBLIC KEY-----\n" 38 | }, 39 | "scheme": "rsassa-pss-sha256" 40 | } 41 | }, 42 | "roles": { 43 | "root": { 44 | "keyids": [ 45 | "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2" 46 | ], 47 | "threshold": 1 48 | }, 49 | "snapshot": { 50 | "keyids": [ 51 | "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6" 52 | ], 53 | "threshold": 1 54 | }, 55 | "targets": { 56 | "keyids": [ 57 | "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d" 58 | ], 59 | "threshold": 1 60 | }, 61 | "timestamp": { 62 | "keyids": [ 63 | "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946" 64 | ], 65 | "threshold": 1 66 | } 67 | }, 68 | "spec_version": "1.0.31", 69 | "version": 1 70 | } 71 | } -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/metadata/snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6", 5 | "sig": "1fdc41488f58482d8af1dad681b9076d54c61b3f5e11bd6cf3c8102b2863471f82c0be2cccd978a9bb6afb43e07dd7e806028a883eaeafd32d5f5277d6419363ecb1475286a61996a4bb4b325b703d3bd60381227af0a3826f7f119a451086bcb5b13a525184d1b2a941ab9a270d2c9c8e584162c5857b138a38c33892e2a921" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "snapshot", 10 | "expires": "2030-08-15T14:30:45.0000001Z", 11 | "meta": { 12 | "role1.json": { 13 | "version": 1 14 | }, 15 | "role2.json": { 16 | "version": 1 17 | }, 18 | "targets.json": { 19 | "version": 1 20 | } 21 | }, 22 | "spec_version": "1.0.31", 23 | "version": 1 24 | } 25 | } -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/metadata/targets.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d", 5 | "sig": "699c0c762032f6471fceabfd2d61b80f352ad6bf2ba5fd7b114fd0b9b1ab8d94f1482776b3fef43c53183a9c9cd7f5de671cbdafdd5032bbe2c42273e953bf3ce9f99c2d46dac8802d6155082e10313e22c4886af2be113b626f2a8af930e01ed41df50a5dbe6ca4cedf5f5d2a7f3b7e7090abacc8aebd6e021ad021d3580cad" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "targets", 10 | "delegations": { 11 | "keys": { 12 | "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": { 13 | "keyid_hash_algorithms": [ 14 | "sha256", 15 | "sha512" 16 | ], 17 | "keytype": "ed25519", 18 | "keyval": { 19 | "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9" 20 | }, 21 | "scheme": "ed25519" 22 | } 23 | }, 24 | "roles": [ 25 | { 26 | "keyids": [ 27 | "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a" 28 | ], 29 | "name": "role1", 30 | "paths": [ 31 | "file3.txt" 32 | ], 33 | "terminating": false, 34 | "threshold": 1 35 | } 36 | ] 37 | }, 38 | "expires": "2030-08-15T14:30:45.0000001Z", 39 | "spec_version": "1.0.31", 40 | "targets": { 41 | "file1.txt": { 42 | "hashes": { 43 | "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da" 44 | }, 45 | "length": 31 46 | } 47 | }, 48 | "version": 1 49 | } 50 | } -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/metadata/timestamp.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946", 5 | "sig": "043b312da9ee1444e3ef539d943891b2690a8d75624c0f05ec148790fd698a7eb1501428167872794857e9669a451619cc796658782b1d46ecc59d1aca0db7233416e81074ef4f54fd845ad8e4216b4cd5163d815be9ecbf73f34aacd25b60c99da88cf641ba5715c37f34a6bc036061c05a42066f554714ee8647c47ae5c16e" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "timestamp", 10 | "expires": "2030-08-15T14:30:45.0000001Z", 11 | "meta": { 12 | "snapshot.json": { 13 | "version": 1 14 | } 15 | }, 16 | "spec_version": "1.0.31", 17 | "version": 1 18 | } 19 | } -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/targets/file1.txt: -------------------------------------------------------------------------------- 1 | This is an example target file. -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/targets/file2.txt: -------------------------------------------------------------------------------- 1 | This is an another example target file. -------------------------------------------------------------------------------- /internal/testutils/repository_data/repository/targets/file3.txt: -------------------------------------------------------------------------------- 1 | This is role1's target file. -------------------------------------------------------------------------------- /internal/testutils/rsapss/signer.go: -------------------------------------------------------------------------------- 1 | package rsapss 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "errors" 9 | "os" 10 | 11 | "github.com/sigstore/sigstore/pkg/signature" 12 | "github.com/sigstore/sigstore/pkg/signature/options" 13 | ) 14 | 15 | func LoadRSAPSSSignerFromPEMFile(p string) (signature.Signer, error) { 16 | var b []byte 17 | var block *pem.Block 18 | var pk any 19 | var err error 20 | 21 | if b, err = os.ReadFile(p); err != nil { 22 | return nil, err 23 | } 24 | 25 | if block, _ = pem.Decode(b); len(block.Bytes) == 0 { 26 | return nil, errors.New("empty PEM block") 27 | } 28 | 29 | if pk, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { 30 | return nil, err 31 | } 32 | var pssOpt = rsa.PSSOptions{Hash: crypto.SHA256} 33 | 34 | return signature.LoadSignerWithOpts(pk, options.WithRSAPSS(&pssOpt)) 35 | } 36 | -------------------------------------------------------------------------------- /internal/testutils/setup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package testutils 19 | 20 | import ( 21 | "fmt" 22 | "log" 23 | "os" 24 | "path/filepath" 25 | ) 26 | 27 | var ( 28 | TempDir string 29 | RepoDir string 30 | TargetsDir string 31 | KeystoreDir string 32 | ) 33 | 34 | func SetupTestDirs(repoPath string, targetsPath string, keystorePath string) error { 35 | tmp := os.TempDir() 36 | var err error 37 | TempDir, err = os.MkdirTemp(tmp, "0750") 38 | if err != nil { 39 | return fmt.Errorf("failed to create temporary directory: %w", err) 40 | } 41 | 42 | RepoDir = filepath.Join(TempDir, "repository_data", "repository") 43 | absPath, err := filepath.Abs(repoPath) 44 | if err != nil { 45 | return fmt.Errorf("failed to get absolute path: %w", err) 46 | } 47 | err = Copy(absPath, RepoDir) 48 | if err != nil { 49 | return fmt.Errorf("failed to copy metadata to %s: %w", RepoDir, err) 50 | } 51 | 52 | TargetsDir = filepath.Join(TempDir, "repository_data", "repository", "targets") 53 | targetsAbsPath, err := filepath.Abs(targetsPath) 54 | if err != nil { 55 | return fmt.Errorf("failed to get absolute targets path: %w", err) 56 | } 57 | err = Copy(targetsAbsPath, TargetsDir) 58 | if err != nil { 59 | return fmt.Errorf("failed to copy metadata to %s: %w", RepoDir, err) 60 | } 61 | 62 | KeystoreDir = filepath.Join(TempDir, "keystore") 63 | err = os.Mkdir(KeystoreDir, 0750) 64 | if err != nil { 65 | return fmt.Errorf("failed to create keystore dir %s: %w", KeystoreDir, err) 66 | } 67 | absPath, err = filepath.Abs(keystorePath) 68 | if err != nil { 69 | return fmt.Errorf("failed to get absolute path: %w", err) 70 | } 71 | err = Copy(absPath, KeystoreDir) 72 | if err != nil { 73 | return fmt.Errorf("failed to copy keystore to %s: %w", KeystoreDir, err) 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func Copy(fromPath string, toPath string) error { 80 | err := os.MkdirAll(toPath, 0750) 81 | if err != nil { 82 | return fmt.Errorf("failed to create directory %s: %w", toPath, err) 83 | } 84 | files, err := os.ReadDir(fromPath) 85 | if err != nil { 86 | return fmt.Errorf("failed to read path %s: %w", fromPath, err) 87 | } 88 | for _, file := range files { 89 | data, err := os.ReadFile(filepath.Join(fromPath, file.Name())) 90 | if err != nil { 91 | return fmt.Errorf("failed to read file %s: %w", file.Name(), err) 92 | } 93 | filePath := filepath.Join(toPath, file.Name()) 94 | err = os.WriteFile(filePath, data, 0750) 95 | if err != nil { 96 | return fmt.Errorf("failed to write file %s: %w", filePath, err) 97 | } 98 | } 99 | return nil 100 | } 101 | 102 | func Cleanup() { 103 | log.Printf("cleaning temporary directory: %s\n", TempDir) 104 | err := os.RemoveAll(TempDir) 105 | if err != nil { 106 | log.Fatalf("failed to cleanup test directories: %v", err) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /internal/testutils/signer/signer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package main 19 | 20 | import ( 21 | "crypto" 22 | "crypto/rsa" 23 | "crypto/x509" 24 | "encoding/json" 25 | "encoding/pem" 26 | "errors" 27 | "flag" 28 | "fmt" 29 | "os" 30 | 31 | "github.com/sigstore/sigstore/pkg/signature" 32 | "github.com/sigstore/sigstore/pkg/signature/options" 33 | "github.com/theupdateframework/go-tuf/v2/metadata" 34 | ) 35 | 36 | type tufSigner interface { 37 | Sign(s signature.Signer) (*metadata.Signature, error) 38 | } 39 | 40 | /* 41 | Run this to sign test data, like this: 42 | ~/git/go-tuf/internal/testutils $ go run \ 43 | signer/signer.go \ 44 | -k repository_data/keystore/timestamp_key \ 45 | -s rsassa-pss-sha256 \ 46 | -f repository_data/repository/metadata/timestamp.json 47 | */ 48 | 49 | func main() { 50 | var scheme = flag.String("s", "", "set scheme to use for key") 51 | var key = flag.String("k", "", "key file to load") 52 | var f = flag.String("f", "", "file to sign") 53 | 54 | flag.Parse() 55 | 56 | if *scheme == "" { 57 | fmt.Println("no scheme is set") 58 | os.Exit(1) 59 | } 60 | if *key == "" { 61 | fmt.Println("no key provided") 62 | os.Exit(1) 63 | } 64 | if *f == "" { 65 | fmt.Println("no metadata file provided") 66 | os.Exit(1) 67 | } 68 | 69 | t, err := getTUFMDRole(*f) 70 | if err != nil { 71 | fmt.Println(err) 72 | os.Exit(1) 73 | } 74 | 75 | s, err := loadSigner(*key, *scheme) 76 | if err != nil { 77 | fmt.Println(err) 78 | os.Exit(1) 79 | } 80 | 81 | var ts tufSigner 82 | 83 | switch t { 84 | case metadata.ROOT: 85 | var rmd metadata.Metadata[metadata.RootType] 86 | if _, err = rmd.FromFile(*f); err != nil { 87 | fmt.Println(err) 88 | os.Exit(1) 89 | } 90 | rmd.Signatures = []metadata.Signature{} 91 | ts = &rmd 92 | case metadata.TARGETS: 93 | var tmd metadata.Metadata[metadata.TargetsType] 94 | if _, err = tmd.FromFile(*f); err != nil { 95 | fmt.Println(err) 96 | os.Exit(1) 97 | } 98 | tmd.Signatures = []metadata.Signature{} 99 | ts = &tmd 100 | case metadata.SNAPSHOT: 101 | var smd metadata.Metadata[metadata.SnapshotType] 102 | if _, err = smd.FromFile(*f); err != nil { 103 | fmt.Println(err) 104 | os.Exit(1) 105 | } 106 | smd.Signatures = []metadata.Signature{} 107 | ts = &smd 108 | case metadata.TIMESTAMP: 109 | var tsmd metadata.Metadata[metadata.TimestampType] 110 | if _, err = tsmd.FromFile(*f); err != nil { 111 | fmt.Println(err) 112 | os.Exit(1) 113 | } 114 | tsmd.Signatures = []metadata.Signature{} 115 | ts = &tsmd 116 | } 117 | 118 | if _, err = ts.Sign(s); err != nil { 119 | fmt.Printf("failed to sign metadata of type %s: %s\n", 120 | t, err) 121 | os.Exit(1) 122 | } 123 | 124 | if err = persist(*f, ts); err != nil { 125 | fmt.Printf("failed to persist updated metadata %s: %s\n", 126 | *f, err) 127 | } 128 | } 129 | 130 | func persist(p string, md any) error { 131 | jsonBytes, err := json.MarshalIndent(md, "", " ") 132 | if err != nil { 133 | return err 134 | } 135 | 136 | err = os.WriteFile(p, jsonBytes, 0600) 137 | return err 138 | } 139 | 140 | func loadSigner(k, s string) (signature.Signer, error) { 141 | var pk any 142 | var err error 143 | var opts []signature.LoadOption 144 | var rawKey []byte 145 | 146 | switch s { 147 | case metadata.KeySchemeRSASSA_PSS_SHA256: 148 | if rawKey, err = getPemBytes(k); err != nil { 149 | return nil, err 150 | } 151 | if pk, err = x509.ParsePKCS1PrivateKey(rawKey); err != nil { 152 | return nil, err 153 | } 154 | var pssOpt = rsa.PSSOptions{Hash: crypto.SHA256} 155 | opts = append(opts, options.WithRSAPSS(&pssOpt)) 156 | default: 157 | return nil, fmt.Errorf("unsupported key scheme %s", s) 158 | } 159 | 160 | return signature.LoadSignerWithOpts(pk, opts...) 161 | } 162 | 163 | func getPemBytes(p string) ([]byte, error) { 164 | var b []byte 165 | var block *pem.Block 166 | var err error 167 | 168 | if b, err = os.ReadFile(p); err != nil { 169 | return nil, err 170 | } 171 | 172 | if block, _ = pem.Decode(b); len(block.Bytes) == 0 { 173 | return nil, errors.New("empty PEM block") 174 | } 175 | 176 | return block.Bytes, nil 177 | } 178 | 179 | func getTUFMDRole(p string) (string, error) { 180 | var m map[string]any 181 | 182 | mdBytes, err := os.ReadFile(p) 183 | if err != nil { 184 | return "", fmt.Errorf("failed to read file %s: %w", p, err) 185 | } 186 | 187 | if err := json.Unmarshal(mdBytes, &m); err != nil { 188 | return "", fmt.Errorf("failed to parse TUF metadata: %w", err) 189 | } 190 | signedType := m["signed"].(map[string]any)["_type"].(string) 191 | switch signedType { 192 | case metadata.ROOT: 193 | fallthrough 194 | case metadata.TARGETS: 195 | fallthrough 196 | case metadata.SNAPSHOT: 197 | fallthrough 198 | case metadata.TIMESTAMP: 199 | return signedType, nil 200 | default: 201 | return "", fmt.Errorf("unsupported role '%s'", signedType) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /internal/testutils/simulator/repository_simulator_setup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package simulator 19 | 20 | import ( 21 | "log/slog" 22 | "os" 23 | "path/filepath" 24 | "time" 25 | ) 26 | 27 | var ( 28 | MetadataURL = "https://jku.github.io/tuf-demo/metadata" 29 | TargetsURL = "https://jku.github.io/tuf-demo/targets" 30 | 31 | MetadataDir string 32 | RootBytes []byte 33 | PastDateTime time.Time 34 | Sim *RepositorySimulator 35 | 36 | metadataPath = "/metadata" 37 | targetsPath = "/targets" 38 | LocalDir string 39 | DumpDir string 40 | ) 41 | 42 | func InitLocalEnv() error { 43 | tmp := os.TempDir() 44 | 45 | tmpDir, err := os.MkdirTemp(tmp, "0750") 46 | if err != nil { 47 | slog.Error("Failed to create temporary directory", "err", err) 48 | os.Exit(1) 49 | } 50 | 51 | if err = os.Mkdir(filepath.Join(tmpDir, metadataPath), 0750); err != nil { 52 | slog.Error("Repository simulator: failed to create dir", "err", err) 53 | } 54 | 55 | if err = os.Mkdir(filepath.Join(tmpDir, targetsPath), 0750); err != nil { 56 | slog.Error("Repository simulator: failed to create dir", "err", err) 57 | } 58 | 59 | LocalDir = tmpDir 60 | 61 | return nil 62 | } 63 | 64 | func InitMetadataDir() (*RepositorySimulator, string, string, error) { 65 | if err := InitLocalEnv(); err != nil { 66 | slog.Error("Failed to initialize environment", "err", err) 67 | os.Exit(1) 68 | } 69 | 70 | metadataDir := filepath.Join(LocalDir, metadataPath) 71 | 72 | sim := NewRepository() 73 | 74 | f, err := os.Create(filepath.Join(metadataDir, "root.json")) 75 | if err != nil { 76 | slog.Error("Failed to create root", "err", err) 77 | os.Exit(1) 78 | } 79 | defer f.Close() 80 | 81 | if _, err = f.Write(sim.SignedRoots[0]); err != nil { 82 | slog.Error("Repository simulator setup: failed to write signed roots", "err", err) 83 | } 84 | 85 | targetsDir := filepath.Join(LocalDir, targetsPath) 86 | sim.LocalDir = LocalDir 87 | return sim, metadataDir, targetsDir, err 88 | } 89 | 90 | func GetRootBytes(localMetadataDir string) ([]byte, error) { 91 | return os.ReadFile(filepath.Join(localMetadataDir, "root.json")) 92 | } 93 | 94 | func RepositoryCleanup(tmpDir string) { 95 | slog.Info("Cleaning temporary directory", "dir", tmpDir) 96 | os.RemoveAll(tmpDir) 97 | } 98 | -------------------------------------------------------------------------------- /metadata/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package config 19 | 20 | import ( 21 | "fmt" 22 | "net/http" 23 | "net/url" 24 | "os" 25 | "time" 26 | 27 | "github.com/cenkalti/backoff/v5" 28 | "github.com/theupdateframework/go-tuf/v2/metadata/fetcher" 29 | ) 30 | 31 | type UpdaterConfig struct { 32 | // TUF configuration 33 | MaxRootRotations int64 34 | MaxDelegations int 35 | RootMaxLength int64 36 | TimestampMaxLength int64 37 | SnapshotMaxLength int64 38 | TargetsMaxLength int64 39 | // Updater configuration 40 | Fetcher fetcher.Fetcher 41 | LocalTrustedRoot []byte 42 | LocalMetadataDir string 43 | LocalTargetsDir string 44 | RemoteMetadataURL string 45 | RemoteTargetsURL string 46 | DisableLocalCache bool 47 | PrefixTargetsWithHash bool 48 | // UnsafeLocalMode only uses the metadata as written on disk 49 | // if the metadata is incomplete, calling updater.Refresh will fail 50 | UnsafeLocalMode bool 51 | } 52 | 53 | // New creates a new UpdaterConfig instance used by the Updater to 54 | // store configuration 55 | func New(remoteURL string, rootBytes []byte) (*UpdaterConfig, error) { 56 | // Default URL for target files - /targets 57 | targetsURL, err := url.JoinPath(remoteURL, "targets") 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | return &UpdaterConfig{ 63 | // TUF configuration 64 | MaxRootRotations: 256, 65 | MaxDelegations: 32, 66 | RootMaxLength: 512000, // bytes 67 | TimestampMaxLength: 16384, // bytes 68 | SnapshotMaxLength: 2000000, // bytes 69 | TargetsMaxLength: 5000000, // bytes 70 | // Updater configuration 71 | Fetcher: fetcher.NewDefaultFetcher(), // use the default built-in download fetcher 72 | LocalTrustedRoot: rootBytes, // trusted root.json 73 | RemoteMetadataURL: remoteURL, // URL of where the TUF metadata is 74 | RemoteTargetsURL: targetsURL, // URL of where the target files should be downloaded from 75 | DisableLocalCache: false, // enable local caching of trusted metadata 76 | PrefixTargetsWithHash: true, // use hash-prefixed target files with consistent snapshots 77 | UnsafeLocalMode: false, 78 | }, nil 79 | } 80 | 81 | func (cfg *UpdaterConfig) EnsurePathsExist() error { 82 | if cfg.DisableLocalCache { 83 | return nil 84 | } 85 | 86 | for _, path := range []string{cfg.LocalMetadataDir, cfg.LocalTargetsDir} { 87 | if err := os.MkdirAll(path, os.ModePerm); err != nil { 88 | return err 89 | } 90 | } 91 | 92 | return nil 93 | } 94 | 95 | func (cfg *UpdaterConfig) SetDefaultFetcherHTTPClient(client *http.Client) error { 96 | // Check if the configured fetcher is the default fetcher 97 | // since we are only configuring a http.Client value for the default fetcher 98 | df, ok := cfg.Fetcher.(*fetcher.DefaultFetcher) 99 | if !ok { 100 | return fmt.Errorf("fetcher is not type fetcher.DefaultFetcher") 101 | } 102 | df.SetHTTPClient(client) 103 | cfg.Fetcher = df 104 | return nil 105 | } 106 | 107 | func (cfg *UpdaterConfig) SetDefaultFetcherTransport(rt http.RoundTripper) error { 108 | // Check if the configured fetcher is the default fetcher 109 | // since we are only configuring a Transport value for the default fetcher 110 | df, ok := cfg.Fetcher.(*fetcher.DefaultFetcher) 111 | if !ok { 112 | return fmt.Errorf("fetcher is not type fetcher.DefaultFetcher") 113 | } 114 | if err := df.SetTransport(rt); err != nil { 115 | return err 116 | } 117 | cfg.Fetcher = df 118 | return nil 119 | } 120 | 121 | // SetDefaultFetcherRetry sets the constant retry interval and count for the default fetcher 122 | func (cfg *UpdaterConfig) SetDefaultFetcherRetry(retryInterval time.Duration, retryCount uint) error { 123 | // Check if the configured fetcher is the default fetcher 124 | // since we are only configuring the retry interval and count for the default fetcher 125 | df, ok := cfg.Fetcher.(*fetcher.DefaultFetcher) 126 | if !ok { 127 | return fmt.Errorf("fetcher is not type fetcher.DefaultFetcher") 128 | } 129 | df.SetRetry(retryInterval, retryCount) 130 | cfg.Fetcher = df 131 | return nil 132 | } 133 | 134 | func (cfg *UpdaterConfig) SetRetryOptions(retryOptions ...backoff.RetryOption) error { 135 | // Check if the configured fetcher is the default fetcher 136 | // since we are only configuring retry options for the default fetcher 137 | df, ok := cfg.Fetcher.(*fetcher.DefaultFetcher) 138 | if !ok { 139 | return fmt.Errorf("fetcher is not type fetcher.DefaultFetcher") 140 | } 141 | df.SetRetryOptions(retryOptions...) 142 | cfg.Fetcher = df 143 | return nil 144 | } 145 | -------------------------------------------------------------------------------- /metadata/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package config 19 | 20 | import ( 21 | "net/url" 22 | "os" 23 | "path/filepath" 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | "github.com/theupdateframework/go-tuf/v2/metadata/fetcher" 28 | ) 29 | 30 | func TestNewUpdaterConfig(t *testing.T) { 31 | // setup testing table (tt) and create subtest for each entry 32 | for _, tt := range []struct { 33 | name string 34 | desc string 35 | remoteURL string 36 | rootBytes []byte 37 | config *UpdaterConfig 38 | wantErr error 39 | }{ 40 | { 41 | name: "success", 42 | desc: "No errors expected", 43 | remoteURL: "somepath", 44 | rootBytes: []byte("somerootbytes"), 45 | config: &UpdaterConfig{ 46 | MaxRootRotations: 256, 47 | MaxDelegations: 32, 48 | RootMaxLength: 512000, 49 | TimestampMaxLength: 16384, 50 | SnapshotMaxLength: 2000000, 51 | TargetsMaxLength: 5000000, 52 | Fetcher: &fetcher.DefaultFetcher{}, 53 | LocalTrustedRoot: []byte("somerootbytes"), 54 | RemoteMetadataURL: "somepath", 55 | RemoteTargetsURL: "somepath/targets", 56 | DisableLocalCache: false, 57 | PrefixTargetsWithHash: true, 58 | }, 59 | wantErr: nil, 60 | }, 61 | { 62 | name: "invalid character in URL", 63 | desc: "Invalid ASCII control sequence in input", 64 | remoteURL: string([]byte{0x7f}), 65 | rootBytes: []byte("somerootbytes"), 66 | config: nil, 67 | wantErr: &url.Error{}, // just make sure this is non-nil, url pkg has no exported errors 68 | }, 69 | } { 70 | t.Run(tt.name, func(t *testing.T) { 71 | // this will only be printed if run in verbose mode or if test fails 72 | t.Logf("Desc: %s", tt.desc) 73 | // run the function under test 74 | updaterConfig, err := New(tt.remoteURL, tt.rootBytes) 75 | // special case if we expect no error 76 | if tt.wantErr == nil { 77 | assert.NoErrorf(t, err, "expected no error but got %v", err) 78 | assert.EqualExportedValuesf(t, *tt.config, *updaterConfig, "expected %#+v but got %#+v", tt.config, updaterConfig) 79 | return 80 | } 81 | // compare the error with our expected error 82 | assert.Nilf(t, updaterConfig, "expected nil but got %#+v", updaterConfig) 83 | assert.Errorf(t, err, "expected %v but got %v", tt.wantErr, err) 84 | }) 85 | } 86 | } 87 | 88 | func TestEnsurePathsExist(t *testing.T) { 89 | // setup testing table (tt) and create subtest for each entry 90 | for _, tt := range []struct { 91 | name string 92 | desc string 93 | config *UpdaterConfig 94 | setup func(t *testing.T, cfg *UpdaterConfig) 95 | wantErr error 96 | }{ 97 | { 98 | name: "success", 99 | desc: "No errors expected", 100 | config: &UpdaterConfig{ 101 | DisableLocalCache: false, 102 | }, 103 | setup: func(t *testing.T, cfg *UpdaterConfig) { 104 | t.Helper() 105 | tmp := t.TempDir() 106 | cfg.LocalTargetsDir = filepath.Join(tmp, "targets") 107 | cfg.LocalMetadataDir = filepath.Join(tmp, "metadata") 108 | }, 109 | wantErr: nil, 110 | }, 111 | { 112 | name: "path not exist", 113 | desc: "No local directories error", 114 | config: &UpdaterConfig{ 115 | DisableLocalCache: false, 116 | }, 117 | setup: func(t *testing.T, cfg *UpdaterConfig) { 118 | t.Helper() 119 | }, 120 | wantErr: os.ErrNotExist, 121 | }, 122 | { 123 | name: "no local cache", 124 | desc: "Test if method no-op works", 125 | config: &UpdaterConfig{ 126 | DisableLocalCache: true, 127 | }, 128 | setup: func(t *testing.T, cfg *UpdaterConfig) { 129 | t.Helper() 130 | }, 131 | wantErr: nil, 132 | }, 133 | } { 134 | t.Run(tt.name, func(t *testing.T) { 135 | // this will only be printed if run in verbose mode or if test fails 136 | t.Logf("Desc: %s", tt.desc) 137 | // run special test setup in case it is needed for any subtest 138 | tt.setup(t, tt.config) 139 | // run the method under test 140 | err := tt.config.EnsurePathsExist() 141 | // special case if we expect no error 142 | if tt.wantErr == nil { 143 | assert.NoErrorf(t, err, "expected no error but got %v", err) 144 | return 145 | } 146 | // compare the error with our expected error 147 | assert.ErrorIsf(t, err, tt.wantErr, "expected %v but got %v", tt.wantErr, err) 148 | }) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /metadata/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package metadata 19 | 20 | import ( 21 | "fmt" 22 | ) 23 | 24 | // Define TUF error types used inside the new modern implementation. 25 | // The names chosen for TUF error types should start in 'Err' except where 26 | // there is a good reason not to, and provide that reason in those cases. 27 | 28 | // Repository errors 29 | 30 | // ErrRepository - an error with a repository's state, such as a missing file. 31 | // It covers all exceptions that come from the repository side when 32 | // looking from the perspective of users of metadata API or client 33 | type ErrRepository struct { 34 | Msg string 35 | } 36 | 37 | func (e *ErrRepository) Error() string { 38 | return fmt.Sprintf("repository error: %s", e.Msg) 39 | } 40 | 41 | func (e *ErrRepository) Is(target error) bool { 42 | _, ok := target.(*ErrRepository) 43 | return ok 44 | } 45 | 46 | // ErrUnsignedMetadata - An error about metadata object with insufficient threshold of signatures 47 | type ErrUnsignedMetadata struct { 48 | Msg string 49 | } 50 | 51 | func (e *ErrUnsignedMetadata) Error() string { 52 | return fmt.Sprintf("unsigned metadata error: %s", e.Msg) 53 | } 54 | 55 | // ErrUnsignedMetadata is a subset of ErrRepository 56 | func (e *ErrUnsignedMetadata) Is(target error) bool { 57 | if _, ok := target.(*ErrUnsignedMetadata); ok { 58 | return true 59 | } 60 | if _, ok := target.(*ErrRepository); ok { 61 | return true 62 | } 63 | return false 64 | } 65 | 66 | // ErrBadVersionNumber - An error for metadata that contains an invalid version number 67 | type ErrBadVersionNumber struct { 68 | Msg string 69 | } 70 | 71 | func (e *ErrBadVersionNumber) Error() string { 72 | return fmt.Sprintf("bad version number error: %s", e.Msg) 73 | } 74 | 75 | // ErrBadVersionNumber is a subset of ErrRepository 76 | func (e *ErrBadVersionNumber) Is(target error) bool { 77 | if _, ok := target.(*ErrBadVersionNumber); ok { 78 | return true 79 | } 80 | if _, ok := target.(*ErrRepository); ok { 81 | return true 82 | } 83 | return false 84 | } 85 | 86 | // ErrEqualVersionNumber - An error for metadata containing a previously verified version number 87 | type ErrEqualVersionNumber struct { 88 | Msg string 89 | } 90 | 91 | func (e *ErrEqualVersionNumber) Error() string { 92 | return fmt.Sprintf("equal version number error: %s", e.Msg) 93 | } 94 | 95 | // ErrEqualVersionNumber is a subset of both ErrRepository and ErrBadVersionNumber 96 | func (e *ErrEqualVersionNumber) Is(target error) bool { 97 | if _, ok := target.(*ErrEqualVersionNumber); ok { 98 | return true 99 | } 100 | if _, ok := target.(*ErrBadVersionNumber); ok { 101 | return true 102 | } 103 | if _, ok := target.(*ErrRepository); ok { 104 | return true 105 | } 106 | return false 107 | } 108 | 109 | // ErrExpiredMetadata - Indicate that a TUF Metadata file has expired 110 | type ErrExpiredMetadata struct { 111 | Msg string 112 | } 113 | 114 | func (e *ErrExpiredMetadata) Error() string { 115 | return fmt.Sprintf("expired metadata error: %s", e.Msg) 116 | } 117 | 118 | // ErrExpiredMetadata is a subset of ErrRepository 119 | func (e *ErrExpiredMetadata) Is(target error) bool { 120 | if _, ok := target.(*ErrExpiredMetadata); ok { 121 | return true 122 | } 123 | if _, ok := target.(*ErrRepository); ok { 124 | return true 125 | } 126 | return false 127 | } 128 | 129 | // ErrLengthOrHashMismatch - An error while checking the length and hash values of an object 130 | type ErrLengthOrHashMismatch struct { 131 | Msg string 132 | } 133 | 134 | func (e *ErrLengthOrHashMismatch) Error() string { 135 | return fmt.Sprintf("length/hash verification error: %s", e.Msg) 136 | } 137 | 138 | // ErrLengthOrHashMismatch is a subset of ErrRepository 139 | func (e *ErrLengthOrHashMismatch) Is(target error) bool { 140 | if _, ok := target.(*ErrLengthOrHashMismatch); ok { 141 | return true 142 | } 143 | if _, ok := target.(*ErrRepository); ok { 144 | return true 145 | } 146 | return false 147 | } 148 | 149 | // Download errors 150 | 151 | // ErrDownload - An error occurred while attempting to download a file 152 | type ErrDownload struct { 153 | Msg string 154 | } 155 | 156 | func (e *ErrDownload) Error() string { 157 | return fmt.Sprintf("download error: %s", e.Msg) 158 | } 159 | 160 | func (e *ErrDownload) Is(target error) bool { 161 | _, ok := target.(*ErrDownload) 162 | return ok 163 | } 164 | 165 | // ErrDownloadLengthMismatch - Indicate that a mismatch of lengths was seen while downloading a file 166 | type ErrDownloadLengthMismatch struct { 167 | Msg string 168 | } 169 | 170 | func (e *ErrDownloadLengthMismatch) Error() string { 171 | return fmt.Sprintf("download length mismatch error: %s", e.Msg) 172 | } 173 | 174 | // ErrDownloadLengthMismatch is a subset of ErrDownload 175 | func (e *ErrDownloadLengthMismatch) Is(target error) bool { 176 | if _, ok := target.(*ErrDownloadLengthMismatch); ok { 177 | return true 178 | } 179 | if _, ok := target.(*ErrDownload); ok { 180 | return true 181 | } 182 | return false 183 | } 184 | 185 | // ErrDownloadHTTP - Returned by Fetcher interface implementations for HTTP errors 186 | type ErrDownloadHTTP struct { 187 | StatusCode int 188 | URL string 189 | } 190 | 191 | func (e *ErrDownloadHTTP) Error() string { 192 | return fmt.Sprintf("failed to download %s, http status code: %d", e.URL, e.StatusCode) 193 | } 194 | 195 | // ErrDownloadHTTP is a subset of ErrDownload 196 | func (e *ErrDownloadHTTP) Is(target error) bool { 197 | if _, ok := target.(*ErrDownloadHTTP); ok { 198 | return true 199 | } 200 | if _, ok := target.(*ErrDownload); ok { 201 | return true 202 | } 203 | return false 204 | } 205 | 206 | // ValueError 207 | type ErrValue struct { 208 | Msg string 209 | } 210 | 211 | func (e *ErrValue) Error() string { 212 | return fmt.Sprintf("value error: %s", e.Msg) 213 | } 214 | 215 | func (e *ErrValue) Is(err error) bool { 216 | _, ok := err.(*ErrValue) 217 | return ok 218 | } 219 | 220 | // TypeError 221 | type ErrType struct { 222 | Msg string 223 | } 224 | 225 | func (e *ErrType) Error() string { 226 | return fmt.Sprintf("type error: %s", e.Msg) 227 | } 228 | 229 | func (e *ErrType) Is(err error) bool { 230 | _, ok := err.(*ErrType) 231 | return ok 232 | } 233 | 234 | // RuntimeError 235 | type ErrRuntime struct { 236 | Msg string 237 | } 238 | 239 | func (e *ErrRuntime) Error() string { 240 | return fmt.Sprintf("runtime error: %s", e.Msg) 241 | } 242 | 243 | func (e *ErrRuntime) Is(err error) bool { 244 | _, ok := err.(*ErrRuntime) 245 | return ok 246 | } 247 | -------------------------------------------------------------------------------- /metadata/fetcher/fetcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package fetcher 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "io" 24 | "net/http" 25 | "strconv" 26 | "time" 27 | 28 | "github.com/cenkalti/backoff/v5" 29 | "github.com/theupdateframework/go-tuf/v2/metadata" 30 | ) 31 | 32 | // httpClient interface allows us to either provide a live http.Client 33 | // or a mock implementation for testing purposes 34 | type httpClient interface { 35 | Do(req *http.Request) (*http.Response, error) 36 | } 37 | 38 | // Fetcher interface 39 | type Fetcher interface { 40 | // DownloadFile downloads a file from the provided URL, reading 41 | // up to maxLength of bytes before it aborts. 42 | // The timeout argument is deprecated and not used. To configure 43 | // the timeout (or retries), modify the fetcher instead. For the 44 | // DefaultFetcher the underlying HTTP client can be substituted. 45 | DownloadFile(urlPath string, maxLength int64, _ time.Duration) ([]byte, error) 46 | } 47 | 48 | // DefaultFetcher implements Fetcher 49 | type DefaultFetcher struct { 50 | // httpClient configuration 51 | httpUserAgent string 52 | client httpClient 53 | // retry logic configuration 54 | retryOptions []backoff.RetryOption 55 | } 56 | 57 | func (d *DefaultFetcher) SetHTTPUserAgent(httpUserAgent string) { 58 | d.httpUserAgent = httpUserAgent 59 | } 60 | 61 | // DownloadFile downloads a file from urlPath, errors out if it failed, 62 | // its length is larger than maxLength or the timeout is reached. 63 | func (d *DefaultFetcher) DownloadFile(urlPath string, maxLength int64, _ time.Duration) ([]byte, error) { 64 | req, err := http.NewRequest("GET", urlPath, nil) 65 | if err != nil { 66 | return nil, err 67 | } 68 | // Use in case of multiple sessions. 69 | if d.httpUserAgent != "" { 70 | req.Header.Set("User-Agent", d.httpUserAgent) 71 | } 72 | 73 | // For backwards compatibility, if the client is nil, use the default client. 74 | if d.client == nil { 75 | d.client = http.DefaultClient 76 | } 77 | 78 | operation := func() ([]byte, error) { 79 | // Execute the request. 80 | res, err := d.client.Do(req) 81 | if err != nil { 82 | return nil, err 83 | } 84 | defer res.Body.Close() 85 | // Handle HTTP status codes. 86 | if res.StatusCode != http.StatusOK { 87 | return nil, &metadata.ErrDownloadHTTP{StatusCode: res.StatusCode, URL: urlPath} 88 | } 89 | var length int64 90 | // Get content length from header (might not be accurate, -1 or not set). 91 | if header := res.Header.Get("Content-Length"); header != "" { 92 | length, err = strconv.ParseInt(header, 10, 0) 93 | if err != nil { 94 | return nil, err 95 | } 96 | // Error if the reported size is greater than what is expected. 97 | if length > maxLength { 98 | return nil, &metadata.ErrDownloadLengthMismatch{Msg: fmt.Sprintf("download failed for %s, length %d is larger than expected %d", urlPath, length, maxLength)} 99 | } 100 | } 101 | // Although the size has been checked above, use a LimitReader in case 102 | // the reported size is inaccurate, or size is -1 which indicates an 103 | // unknown length. We read maxLength + 1 in order to check if the read data 104 | // surpassed our set limit. 105 | data, err := io.ReadAll(io.LimitReader(res.Body, maxLength+1)) 106 | if err != nil { 107 | return nil, err 108 | } 109 | // Error if the reported size is greater than what is expected. 110 | length = int64(len(data)) 111 | if length > maxLength { 112 | return nil, &metadata.ErrDownloadLengthMismatch{Msg: fmt.Sprintf("download failed for %s, length %d is larger than expected %d", urlPath, length, maxLength)} 113 | } 114 | 115 | return data, nil 116 | } 117 | data, err := backoff.Retry(context.Background(), operation, d.retryOptions...) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | return data, nil 123 | } 124 | 125 | func NewDefaultFetcher() *DefaultFetcher { 126 | return &DefaultFetcher{ 127 | client: http.DefaultClient, 128 | // default to attempting the HTTP request once 129 | retryOptions: []backoff.RetryOption{backoff.WithMaxTries(1)}, 130 | } 131 | } 132 | 133 | // NewFetcherWithHTTPClient creates a new DefaultFetcher with a custom httpClient 134 | func (f *DefaultFetcher) NewFetcherWithHTTPClient(hc httpClient) *DefaultFetcher { 135 | return &DefaultFetcher{ 136 | client: hc, 137 | } 138 | } 139 | 140 | // NewFetcherWithRoundTripper creates a new DefaultFetcher with a custom RoundTripper 141 | // The function will create a default http.Client and replace the transport with the provided RoundTripper implementation 142 | func (f *DefaultFetcher) NewFetcherWithRoundTripper(rt http.RoundTripper) *DefaultFetcher { 143 | client := http.DefaultClient 144 | client.Transport = rt 145 | return &DefaultFetcher{ 146 | client: client, 147 | } 148 | } 149 | 150 | func (f *DefaultFetcher) SetHTTPClient(hc httpClient) { 151 | f.client = hc 152 | } 153 | 154 | func (f *DefaultFetcher) SetTransport(rt http.RoundTripper) error { 155 | hc, ok := f.client.(*http.Client) 156 | if !ok { 157 | return fmt.Errorf("fetcher is not type fetcher.DefaultFetcher") 158 | } 159 | hc.Transport = rt 160 | f.client = hc 161 | return nil 162 | } 163 | 164 | func (f *DefaultFetcher) SetRetry(retryInterval time.Duration, retryCount uint) { 165 | constantBackOff := backoff.WithBackOff(backoff.NewConstantBackOff(retryInterval)) 166 | maxTryCount := backoff.WithMaxTries(retryCount) 167 | f.SetRetryOptions(constantBackOff, maxTryCount) 168 | } 169 | 170 | func (f *DefaultFetcher) SetRetryOptions(retryOptions ...backoff.RetryOption) { 171 | f.retryOptions = retryOptions 172 | } 173 | -------------------------------------------------------------------------------- /metadata/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package metadata 19 | 20 | import ( 21 | "crypto" 22 | "crypto/ecdsa" 23 | "crypto/ed25519" 24 | "crypto/rsa" 25 | "crypto/sha256" 26 | "crypto/x509" 27 | "encoding/hex" 28 | "fmt" 29 | 30 | "github.com/secure-systems-lab/go-securesystemslib/cjson" 31 | "github.com/sigstore/sigstore/pkg/cryptoutils" 32 | ) 33 | 34 | const ( 35 | KeyTypeEd25519 = "ed25519" 36 | KeyTypeECDSA_SHA2_P256_COMPAT = "ecdsa-sha2-nistp256" 37 | KeyTypeECDSA_SHA2_P256 = "ecdsa" 38 | KeyTypeRSASSA_PSS_SHA256 = "rsa" 39 | KeySchemeEd25519 = "ed25519" 40 | KeySchemeECDSA_SHA2_P256 = "ecdsa-sha2-nistp256" 41 | KeySchemeECDSA_SHA2_P384 = "ecdsa-sha2-nistp384" 42 | KeySchemeRSASSA_PSS_SHA256 = "rsassa-pss-sha256" 43 | ) 44 | 45 | // ToPublicKey generate crypto.PublicKey from metadata type Key 46 | func (k *Key) ToPublicKey() (crypto.PublicKey, error) { 47 | switch k.Type { 48 | case KeyTypeRSASSA_PSS_SHA256: 49 | publicKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(k.Value.PublicKey)) 50 | if err != nil { 51 | return nil, err 52 | } 53 | rsaKey, ok := publicKey.(*rsa.PublicKey) 54 | if !ok { 55 | return nil, fmt.Errorf("invalid rsa public key") 56 | } 57 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357 58 | if _, err := x509.MarshalPKIXPublicKey(rsaKey); err != nil { 59 | return nil, err 60 | } 61 | return rsaKey, nil 62 | case KeyTypeECDSA_SHA2_P256, KeyTypeECDSA_SHA2_P256_COMPAT: // handle "ecdsa" too as python-tuf/sslib keys are using it for keytype instead of https://theupdateframework.github.io/specification/latest/index.html#keytype-ecdsa-sha2-nistp256 63 | publicKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(k.Value.PublicKey)) 64 | if err != nil { 65 | return nil, err 66 | } 67 | ecdsaKey, ok := publicKey.(*ecdsa.PublicKey) 68 | if !ok { 69 | return nil, fmt.Errorf("invalid ecdsa public key") 70 | } 71 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357 72 | if _, err := x509.MarshalPKIXPublicKey(ecdsaKey); err != nil { 73 | return nil, err 74 | } 75 | return ecdsaKey, nil 76 | case KeyTypeEd25519: 77 | publicKey, err := hex.DecodeString(k.Value.PublicKey) 78 | if err != nil { 79 | return nil, err 80 | } 81 | ed25519Key := ed25519.PublicKey(publicKey) 82 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357 83 | if _, err := x509.MarshalPKIXPublicKey(ed25519Key); err != nil { 84 | return nil, err 85 | } 86 | return ed25519Key, nil 87 | } 88 | return nil, fmt.Errorf("unsupported public key type") 89 | } 90 | 91 | // KeyFromPublicKey generate metadata type Key from crypto.PublicKey 92 | func KeyFromPublicKey(k crypto.PublicKey) (*Key, error) { 93 | key := &Key{} 94 | switch k := k.(type) { 95 | case *rsa.PublicKey: 96 | key.Type = KeyTypeRSASSA_PSS_SHA256 97 | key.Scheme = KeySchemeRSASSA_PSS_SHA256 98 | pemKey, err := cryptoutils.MarshalPublicKeyToPEM(k) 99 | if err != nil { 100 | return nil, err 101 | } 102 | key.Value.PublicKey = string(pemKey) 103 | case *ecdsa.PublicKey: 104 | key.Type = KeyTypeECDSA_SHA2_P256 105 | key.Scheme = KeySchemeECDSA_SHA2_P256 106 | pemKey, err := cryptoutils.MarshalPublicKeyToPEM(k) 107 | if err != nil { 108 | return nil, err 109 | } 110 | key.Value.PublicKey = string(pemKey) 111 | case ed25519.PublicKey: 112 | key.Type = KeyTypeEd25519 113 | key.Scheme = KeySchemeEd25519 114 | key.Value.PublicKey = hex.EncodeToString(k) 115 | default: 116 | return nil, fmt.Errorf("unsupported public key type") 117 | } 118 | return key, nil 119 | } 120 | 121 | // ID returns the keyID value for the given Key 122 | func (k *Key) ID() string { 123 | // the identifier is a hexdigest of the SHA-256 hash of the canonical form of the key 124 | if k.id == "" { 125 | data, err := cjson.EncodeCanonical(k) 126 | if err != nil { 127 | panic(fmt.Errorf("error creating key ID: %w", err)) 128 | } 129 | digest := sha256.Sum256(data) 130 | k.id = hex.EncodeToString(digest[:]) 131 | } 132 | return k.id 133 | } 134 | -------------------------------------------------------------------------------- /metadata/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package metadata 19 | 20 | var log Logger = DiscardLogger{} 21 | 22 | // Logger partially implements the go-log/logr's interface: 23 | // https://github.com/go-logr/logr/blob/master/logr.go 24 | type Logger interface { 25 | // Info logs a non-error message with key/value pairs 26 | Info(msg string, kv ...any) 27 | // Error logs an error with a given message and key/value pairs. 28 | Error(err error, msg string, kv ...any) 29 | } 30 | 31 | type DiscardLogger struct{} 32 | 33 | func (d DiscardLogger) Info(msg string, kv ...any) { 34 | } 35 | 36 | func (d DiscardLogger) Error(err error, msg string, kv ...any) { 37 | } 38 | 39 | func SetLogger(logger Logger) { 40 | log = logger 41 | } 42 | 43 | func GetLogger() Logger { 44 | return log 45 | } 46 | -------------------------------------------------------------------------------- /metadata/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package metadata 19 | 20 | import ( 21 | stdlog "log" 22 | "os" 23 | "testing" 24 | 25 | "github.com/go-logr/stdr" 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestSetLogger(t *testing.T) { 30 | // This function is just a simple setter, no need for testing table 31 | testLogger := stdr.New(stdlog.New(os.Stdout, "test", stdlog.LstdFlags)) 32 | SetLogger(testLogger) 33 | assert.Equal(t, testLogger, log, "setting package global logger was unsuccessful") 34 | } 35 | 36 | func TestGetLogger(t *testing.T) { 37 | // This function is just a simple getter, no need for testing table 38 | testLogger := GetLogger() 39 | assert.Equal(t, log, testLogger, "function did not return current logger") 40 | } 41 | -------------------------------------------------------------------------------- /metadata/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package metadata 19 | 20 | import ( 21 | "encoding/json" 22 | "time" 23 | ) 24 | 25 | // Generic type constraint 26 | type Roles interface { 27 | RootType | SnapshotType | TimestampType | TargetsType 28 | } 29 | 30 | // Define version of the TUF specification 31 | const ( 32 | SPECIFICATION_VERSION = "1.0.31" 33 | ) 34 | 35 | // Define top level role names 36 | const ( 37 | ROOT = "root" 38 | SNAPSHOT = "snapshot" 39 | TARGETS = "targets" 40 | TIMESTAMP = "timestamp" 41 | ) 42 | 43 | var TOP_LEVEL_ROLE_NAMES = [...]string{ROOT, TIMESTAMP, SNAPSHOT, TARGETS} 44 | 45 | // Metadata[T Roles] represents a TUF metadata. 46 | // Provides methods to read and write to and 47 | // from file and bytes, also to create, verify and clear metadata signatures. 48 | type Metadata[T Roles] struct { 49 | Signed T `json:"signed"` 50 | Signatures []Signature `json:"signatures"` 51 | UnrecognizedFields map[string]any `json:"-"` 52 | } 53 | 54 | // Signature represents the Signature part of a TUF metadata 55 | type Signature struct { 56 | KeyID string `json:"keyid"` 57 | Signature HexBytes `json:"sig"` 58 | UnrecognizedFields map[string]any `json:"-"` 59 | } 60 | 61 | // RootType represents the Signed portion of a root metadata 62 | type RootType struct { 63 | Type string `json:"_type"` 64 | SpecVersion string `json:"spec_version"` 65 | ConsistentSnapshot bool `json:"consistent_snapshot"` 66 | Version int64 `json:"version"` 67 | Expires time.Time `json:"expires"` 68 | Keys map[string]*Key `json:"keys"` 69 | Roles map[string]*Role `json:"roles"` 70 | UnrecognizedFields map[string]any `json:"-"` 71 | } 72 | 73 | // SnapshotType represents the Signed portion of a snapshot metadata 74 | type SnapshotType struct { 75 | Type string `json:"_type"` 76 | SpecVersion string `json:"spec_version"` 77 | Version int64 `json:"version"` 78 | Expires time.Time `json:"expires"` 79 | Meta map[string]*MetaFiles `json:"meta"` 80 | UnrecognizedFields map[string]any `json:"-"` 81 | } 82 | 83 | // TargetsType represents the Signed portion of a targets metadata 84 | type TargetsType struct { 85 | Type string `json:"_type"` 86 | SpecVersion string `json:"spec_version"` 87 | Version int64 `json:"version"` 88 | Expires time.Time `json:"expires"` 89 | Targets map[string]*TargetFiles `json:"targets"` 90 | Delegations *Delegations `json:"delegations,omitempty"` 91 | UnrecognizedFields map[string]any `json:"-"` 92 | } 93 | 94 | // TimestampType represents the Signed portion of a timestamp metadata 95 | type TimestampType struct { 96 | Type string `json:"_type"` 97 | SpecVersion string `json:"spec_version"` 98 | Version int64 `json:"version"` 99 | Expires time.Time `json:"expires"` 100 | Meta map[string]*MetaFiles `json:"meta"` 101 | UnrecognizedFields map[string]any `json:"-"` 102 | } 103 | 104 | // Key represents a key in TUF 105 | type Key struct { 106 | Type string `json:"keytype"` 107 | Scheme string `json:"scheme"` 108 | Value KeyVal `json:"keyval"` 109 | id string `json:"-"` 110 | UnrecognizedFields map[string]any `json:"-"` 111 | } 112 | 113 | type KeyVal struct { 114 | PublicKey string `json:"public"` 115 | UnrecognizedFields map[string]any `json:"-"` 116 | } 117 | 118 | // Role represents one of the top-level roles in TUF 119 | type Role struct { 120 | KeyIDs []string `json:"keyids"` 121 | Threshold int `json:"threshold"` 122 | UnrecognizedFields map[string]any `json:"-"` 123 | } 124 | 125 | type HexBytes []byte 126 | 127 | type Hashes map[string]HexBytes 128 | 129 | // MetaFiles represents the value portion of METAFILES in TUF (used in Snapshot and Timestamp metadata). Used to store information about a particular meta file. 130 | type MetaFiles struct { 131 | Length int64 `json:"length,omitempty"` 132 | Hashes Hashes `json:"hashes,omitempty"` 133 | Version int64 `json:"version"` 134 | UnrecognizedFields map[string]any `json:"-"` 135 | } 136 | 137 | // TargetFiles represents the value portion of TARGETS in TUF (used Targets metadata). Used to store information about a particular target file. 138 | type TargetFiles struct { 139 | Length int64 `json:"length"` 140 | Hashes Hashes `json:"hashes"` 141 | Custom *json.RawMessage `json:"custom,omitempty"` 142 | Path string `json:"-"` 143 | UnrecognizedFields map[string]any `json:"-"` 144 | } 145 | 146 | // Delegations is an optional object which represents delegation roles and their corresponding keys 147 | type Delegations struct { 148 | Keys map[string]*Key `json:"keys"` 149 | Roles []DelegatedRole `json:"roles,omitempty"` 150 | SuccinctRoles *SuccinctRoles `json:"succinct_roles,omitempty"` 151 | UnrecognizedFields map[string]any `json:"-"` 152 | } 153 | 154 | // DelegatedRole represents a delegated role in TUF 155 | type DelegatedRole struct { 156 | Name string `json:"name"` 157 | KeyIDs []string `json:"keyids"` 158 | Threshold int `json:"threshold"` 159 | Terminating bool `json:"terminating"` 160 | PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"` 161 | Paths []string `json:"paths,omitempty"` 162 | UnrecognizedFields map[string]any `json:"-"` 163 | } 164 | 165 | // SuccinctRoles represents a delegation graph that covers all targets, 166 | // distributing them uniformly over the delegated roles (i.e. bins) in the graph. 167 | type SuccinctRoles struct { 168 | KeyIDs []string `json:"keyids"` 169 | Threshold int `json:"threshold"` 170 | BitLength int `json:"bit_length"` 171 | NamePrefix string `json:"name_prefix"` 172 | UnrecognizedFields map[string]any `json:"-"` 173 | } 174 | 175 | // RoleResult represents the name and terminating status of a delegated role that is responsible for targetFilepath 176 | type RoleResult struct { 177 | Name string 178 | Terminating bool 179 | } 180 | -------------------------------------------------------------------------------- /metadata/updater/updater_consistent_snapshot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Update Framework Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License 14 | // 15 | // SPDX-License-Identifier: Apache-2.0 16 | // 17 | 18 | package updater 19 | 20 | import ( 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | 25 | "github.com/theupdateframework/go-tuf/v2/internal/testutils/simulator" 26 | "github.com/theupdateframework/go-tuf/v2/metadata" 27 | ) 28 | 29 | func TestTopLevelRolesUpdateWithConsistentSnapshotDisabled(t *testing.T) { 30 | // Test if the client fetches and stores metadata files with the 31 | // correct version prefix when ConsistentSnapshot is false 32 | 33 | err := loadOrResetTrustedRootMetadata() 34 | assert.NoError(t, err) 35 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = false 36 | simulator.Sim.MDRoot.Signed.Version += 1 37 | simulator.Sim.PublishRoot() 38 | 39 | updaterConfig, err := loadUpdaterConfig() 40 | assert.NoError(t, err) 41 | updater := initUpdater(updaterConfig) 42 | // cleanup fetch tracker metadata 43 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{} 44 | err = updater.Refresh() 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | assert.NoError(t, err) 49 | 50 | // metadata files are fetched with the expected version (or None) 51 | expectedsnapshotEnabled := []simulator.FTMetadata{ 52 | {Name: "root", Value: 2}, 53 | {Name: "root", Value: 3}, 54 | {Name: "timestamp", Value: -1}, 55 | {Name: "snapshot", Value: -1}, 56 | {Name: "targets", Value: -1}, 57 | } 58 | assert.EqualValues(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata) 59 | // metadata files are always persisted without a version prefix 60 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:]) 61 | } 62 | 63 | func TestTopLevelRolesUpdateWithConsistentSnapshotEnabled(t *testing.T) { 64 | // Test if the client fetches and stores metadata files with the 65 | // correct version prefix when ConsistentSnapshot is true 66 | 67 | err := loadOrResetTrustedRootMetadata() 68 | assert.NoError(t, err) 69 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = true 70 | simulator.Sim.MDRoot.Signed.Version += 1 71 | simulator.Sim.PublishRoot() 72 | 73 | updaterConfig, err := loadUpdaterConfig() 74 | assert.NoError(t, err) 75 | updater := initUpdater(updaterConfig) 76 | if updater == nil { 77 | t.Fatal("updater is nil") 78 | } 79 | // cleanup fetch tracker metadata 80 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{} 81 | err = updater.Refresh() 82 | assert.NoError(t, err) 83 | 84 | // metadata files are fetched with the expected version (or None) 85 | expectedSnapshotDisabled := []simulator.FTMetadata{ 86 | {Name: "root", Value: 2}, 87 | {Name: "root", Value: 3}, 88 | {Name: "timestamp", Value: -1}, 89 | {Name: "snapshot", Value: 1}, 90 | {Name: "targets", Value: 1}, 91 | } 92 | assert.EqualValues(t, expectedSnapshotDisabled, simulator.Sim.FetchTracker.Metadata) 93 | // metadata files are always persisted without a version prefix 94 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:]) 95 | } 96 | 97 | func TestDelegatesRolesUpdateWithConsistentSnapshotDisabled(t *testing.T) { 98 | // Test if the client fetches and stores delegated metadata files with 99 | // the correct version prefix when ConsistentSnapshot is false 100 | 101 | err := loadOrResetTrustedRootMetadata() 102 | assert.NoError(t, err) 103 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = false 104 | simulator.Sim.MDRoot.Signed.Version += 1 105 | simulator.Sim.PublishRoot() 106 | 107 | target := metadata.Targets(simulator.Sim.SafeExpiry) 108 | 109 | delegatedRole := metadata.DelegatedRole{ 110 | Name: "role1", 111 | KeyIDs: []string{}, 112 | Threshold: 1, 113 | Terminating: false, 114 | Paths: []string{"*"}, 115 | } 116 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 117 | 118 | delegatedRole = metadata.DelegatedRole{ 119 | Name: "..", 120 | KeyIDs: []string{}, 121 | Threshold: 1, 122 | Terminating: false, 123 | Paths: []string{"*"}, 124 | } 125 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 126 | 127 | delegatedRole = metadata.DelegatedRole{ 128 | Name: ".", 129 | KeyIDs: []string{}, 130 | Threshold: 1, 131 | Terminating: false, 132 | Paths: []string{"*"}, 133 | } 134 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 135 | 136 | simulator.Sim.UpdateSnapshot() 137 | updaterConfig, err := loadUpdaterConfig() 138 | assert.NoError(t, err) 139 | updater := initUpdater(updaterConfig) 140 | if updater == nil { 141 | t.Fatal("updater is nil") 142 | } 143 | err = updater.Refresh() 144 | assert.NoError(t, err) 145 | 146 | // cleanup fetch tracker metadata 147 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{} 148 | // trigger updater to fetch the delegated metadata 149 | _, err = updater.GetTargetInfo("anything") 150 | assert.ErrorContains(t, err, "target anything not found") 151 | 152 | // metadata files are fetched with the expected version (or None) 153 | expectedsnapshotEnabled := []simulator.FTMetadata{ 154 | {Name: "role1", Value: -1}, 155 | {Name: "..", Value: -1}, 156 | {Name: ".", Value: -1}, 157 | } 158 | assert.ElementsMatch(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata) 159 | // metadata files are always persisted without a version prefix 160 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:]) 161 | } 162 | 163 | func TestDelegatesRolesUpdateWithConsistentSnapshotEnabled(t *testing.T) { 164 | // Test if the client fetches and stores delegated metadata files with 165 | // the correct version prefix when ConsistentSnapshot is true 166 | 167 | err := loadOrResetTrustedRootMetadata() 168 | assert.NoError(t, err) 169 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = true 170 | simulator.Sim.MDRoot.Signed.Version += 1 171 | simulator.Sim.PublishRoot() 172 | 173 | target := metadata.Targets(simulator.Sim.SafeExpiry) 174 | 175 | delegatedRole := metadata.DelegatedRole{ 176 | Name: "role1", 177 | KeyIDs: []string{}, 178 | Threshold: 1, 179 | Terminating: false, 180 | Paths: []string{"*"}, 181 | } 182 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 183 | 184 | delegatedRole = metadata.DelegatedRole{ 185 | Name: "..", 186 | KeyIDs: []string{}, 187 | Threshold: 1, 188 | Terminating: false, 189 | Paths: []string{"*"}, 190 | } 191 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 192 | 193 | delegatedRole = metadata.DelegatedRole{ 194 | Name: ".", 195 | KeyIDs: []string{}, 196 | Threshold: 1, 197 | Terminating: false, 198 | Paths: []string{"*"}, 199 | } 200 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 201 | 202 | simulator.Sim.UpdateSnapshot() 203 | updaterConfig, err := loadUpdaterConfig() 204 | assert.NoError(t, err) 205 | updater := initUpdater(updaterConfig) 206 | if updater == nil { 207 | t.Fatal("updater is nil") 208 | } 209 | err = updater.Refresh() 210 | assert.NoError(t, err) 211 | 212 | // cleanup fetch tracker metadata 213 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{} 214 | // trigger updater to fetch the delegated metadata 215 | _, err = updater.GetTargetInfo("anything") 216 | assert.ErrorContains(t, err, "target anything not found") 217 | 218 | // metadata files are fetched with the expected version (or None) 219 | expectedsnapshotEnabled := []simulator.FTMetadata{ 220 | {Name: "role1", Value: 1}, 221 | {Name: "..", Value: 1}, 222 | {Name: ".", Value: 1}, 223 | } 224 | assert.ElementsMatch(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata) 225 | // metadata files are always persisted without a version prefix 226 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:]) 227 | } 228 | --------------------------------------------------------------------------------