├── .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 ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.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 │ │ ├── 9bb72b3c684f4e489e7fdce1afabff64b4e3f4b7877ac8cb78a684c2c65fc6d8.map.json │ │ ├── map.json │ │ ├── sigstore-tuf-root │ │ ├── e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json │ │ └── root.json │ │ └── staging │ │ ├── e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json │ │ └── root.json └── repository │ └── basic_repository.go ├── go.mod ├── go.sum ├── 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 ├── repository │ ├── repository.go │ └── repository_test.go ├── trustedmetadata │ ├── trustedmetadata.go │ └── trustedmetadata_test.go ├── types.go └── updater │ ├── updater.go │ ├── updater_consistent_snapshot_test.go │ └── updater_top_level_update_test.go └── 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 ├── simulator ├── repository_simulator.go └── repository_simulator_setup.go └── testutils └── setup.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 | # Copyright 2022-2023 VMware, Inc. 2 | # 3 | # This product is licensed to you under the BSD-2 license (the "License"). 4 | # You may not use this product except in compliance with the BSD-2 License. 5 | # This product may include a number of subcomponents with separate copyright 6 | # notices and license terms. Your use of these subcomponents is subject to 7 | # the terms and conditions of the subcomponent's license, as noted in the 8 | # LICENSE file. 9 | # 10 | # SPDX-License-Identifier: BSD-2-Clause 11 | on: 12 | pull_request: 13 | push: 14 | branches: 15 | - "main" 16 | tags: 17 | - "v*" 18 | name: CI 19 | jobs: 20 | linting: 21 | uses: ./.github/workflows/linting.yml 22 | tests: 23 | uses: ./.github/workflows/tests.yml 24 | examples: 25 | uses: ./.github/workflows/examples.yml 26 | -------------------------------------------------------------------------------- /.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: [main] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [main] 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 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 41 | - uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f 42 | with: 43 | go-version-file: 'go.mod' 44 | cache: false 45 | # Initializes the CodeQL tools for scanning. 46 | - name: Initialize CodeQL 47 | uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 48 | with: 49 | languages: ${{ matrix.language }} 50 | # If you wish to specify custom queries, you can do so here or in a config file. 51 | # By default, queries listed here will override any specified in a config file. 52 | # Prefix the list here with "+" to use these queries and those in the config file. 53 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 https://git.io/JvXDl 62 | 63 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 64 | # and modify them (or add more) to build your code if your project 65 | # uses a compiled language 66 | 67 | #- run: | 68 | # make bootstrap 69 | # make release 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 73 | -------------------------------------------------------------------------------- /.github/workflows/examples.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 VMware, Inc. 2 | # 3 | # This product is licensed to you under the BSD-2 license (the "License"). 4 | # You may not use this product except in compliance with the BSD-2 License. 5 | # This product may include a number of subcomponents with separate copyright 6 | # notices and license terms. Your use of these subcomponents is subject to 7 | # the terms and conditions of the subcomponent's license, as noted in the 8 | # LICENSE file. 9 | # 10 | # SPDX-License-Identifier: BSD-2-Clause 11 | on: 12 | workflow_call: 13 | name: Examples # 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 being, nevertheless should be updated 14 | jobs: 15 | get-go-versions: 16 | name: Collect available Go versions 17 | runs-on: ubuntu-latest 18 | outputs: 19 | matrix: ${{ steps.versions.outputs.matrix }} 20 | steps: 21 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 22 | - uses: arnested/go-version-action@b556f8d91b644164318c709d28b9083eaf0c064d 23 | id: versions 24 | client: 25 | strategy: 26 | fail-fast: false # Keep running if one leg fails. 27 | matrix: 28 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources 29 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }} 30 | runs-on: ${{ matrix.os }} 31 | needs: get-go-versions 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 35 | - name: Setup - Go ${{ matrix.go-version }} 36 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f 37 | with: 38 | go-version: ${{ matrix.go-version }} 39 | - run: make example-client 40 | repository: 41 | strategy: 42 | fail-fast: false # Keep running if one leg fails. 43 | matrix: 44 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources 45 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }} 46 | runs-on: ${{ matrix.os }} 47 | needs: get-go-versions 48 | steps: 49 | - name: Checkout code 50 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 51 | - name: Setup - Go ${{ matrix.go-version }} 52 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f 53 | with: 54 | go-version: ${{ matrix.go-version }} 55 | - run: make example-repository 56 | multirepo: 57 | strategy: 58 | fail-fast: false # Keep running if one leg fails. 59 | matrix: 60 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources 61 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }} 62 | runs-on: ${{ matrix.os }} 63 | needs: get-go-versions 64 | steps: 65 | - name: Checkout code 66 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 67 | - name: Setup - Go ${{ matrix.go-version }} 68 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f 69 | with: 70 | go-version: ${{ matrix.go-version }} 71 | - run: make example-multirepo 72 | tuf-client-cli: 73 | strategy: 74 | fail-fast: false # Keep running if one leg fails. 75 | matrix: 76 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources 77 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }} 78 | runs-on: ${{ matrix.os }} 79 | needs: get-go-versions 80 | steps: 81 | - name: Checkout code 82 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 83 | - name: Setup - Go ${{ matrix.go-version }} 84 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f 85 | with: 86 | go-version: ${{ matrix.go-version }} 87 | - run: make example-tuf-client-cli 88 | root-signing: 89 | strategy: 90 | fail-fast: false # Keep running if one leg fails. 91 | matrix: 92 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources 93 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }} 94 | runs-on: ${{ matrix.os }} 95 | needs: get-go-versions 96 | steps: 97 | - name: Checkout code 98 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 99 | - name: Setup - Go ${{ matrix.go-version }} 100 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f 101 | with: 102 | go-version: ${{ matrix.go-version }} 103 | - run: make example-root-signing 104 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 VMware, Inc. 2 | # 3 | # This product is licensed to you under the BSD-2 license (the "License"). 4 | # You may not use this product except in compliance with the BSD-2 License. 5 | # This product may include a number of subcomponents with separate copyright 6 | # notices and license terms. Your use of these subcomponents is subject to 7 | # the terms and conditions of the subcomponent's license, as noted in the 8 | # LICENSE file. 9 | # 10 | # SPDX-License-Identifier: BSD-2-Clause 11 | on: 12 | workflow_call: 13 | name: Linting 14 | jobs: 15 | govulncheck_job: 16 | runs-on: ubuntu-latest 17 | name: govulncheck 18 | steps: 19 | - id: govulncheck 20 | uses: golang/govulncheck-action@7da72f730e37eeaad891fcff0a532d27ed737cd4 21 | with: 22 | go-version-file: 'go.mod' 23 | go-package: ./... 24 | golangci: 25 | name: golangci-lint 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 29 | - uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f 30 | with: 31 | go-version-file: 'go.mod' 32 | cache: false 33 | - name: golangci-lint 34 | uses: golangci/golangci-lint-action@v3 35 | with: 36 | # Require: The version of golangci-lint to use. 37 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. 38 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. 39 | version: v1.54 40 | args: --timeout 5m --verbose 41 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 VMware, Inc. 2 | # 3 | # This product is licensed to you under the BSD-2 license (the "License"). 4 | # You may not use this product except in compliance with the BSD-2 License. 5 | # This product may include a number of subcomponents with separate copyright 6 | # notices and license terms. Your use of these subcomponents is subject to 7 | # the terms and conditions of the subcomponent's license, as noted in the 8 | # LICENSE file. 9 | # 10 | # SPDX-License-Identifier: BSD-2-Clause 11 | on: 12 | workflow_call: 13 | name: Tests 14 | jobs: 15 | get-go-versions: 16 | name: Collect available Go versions 17 | runs-on: ubuntu-latest 18 | outputs: 19 | matrix: ${{ steps.versions.outputs.matrix }} 20 | steps: 21 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 22 | - uses: arnested/go-version-action@b556f8d91b644164318c709d28b9083eaf0c064d 23 | id: versions 24 | run: 25 | name: Run 26 | strategy: 27 | fail-fast: false # Keep running if one leg fails. 28 | matrix: 29 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources 30 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }} 31 | runs-on: ${{ matrix.os }} 32 | needs: get-go-versions 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 36 | 37 | - name: Setup - Go ${{ matrix.go-version }} 38 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f 39 | with: 40 | go-version: ${{ matrix.go-version }} 41 | 42 | - name: Run tests 43 | run: go test -race -covermode=atomic -coverpkg=./metadata/... -coverprofile=coverage.out ./... 44 | 45 | - name: Send coverage 46 | uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d 47 | with: 48 | flags: Go-${{ matrix.go-version }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | dist/ 3 | .idea/ 4 | *~ 5 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2023 VMware, Inc. 2 | # 3 | # This product is licensed to you under the BSD-2 license (the "License"). 4 | # You may not use this product except in compliance with the BSD-2 License. 5 | # This product may include a number of subcomponents with separate copyright 6 | # notices and license terms. Your use of these subcomponents is subject to 7 | # the terms and conditions of the subcomponent's license, as noted in the 8 | # LICENSE file. 9 | # 10 | # SPDX-License-Identifier: BSD-2-Clause 11 | run: 12 | linters: 13 | enable: 14 | - gofmt 15 | - bodyclose 16 | - contextcheck 17 | - errname 18 | - gocyclo 19 | - godot 20 | - godox 21 | - misspell 22 | - stylecheck 23 | - whitespace 24 | - gocritic 25 | -------------------------------------------------------------------------------- /.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: "BSD-2-Clause" 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022-2023 VMware, Inc. 2 | 3 | The BSD-2-Clause license (the "License") set forth below applies to all parts of the go-tuf-metadata project. You may not use this file except in compliance with the License. 4 | 5 | BSD-2-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 8 | 9 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 10 | 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2023 VMware, Inc. 2 | # 3 | # This product is licensed to you under the BSD-2 license (the "License"). 4 | # You may not use this product except in compliance with the BSD-2 License. 5 | # This product may include a number of subcomponents with separate copyright 6 | # notices and license terms. Your use of these subcomponents is subject to 7 | # the terms and conditions of the subcomponent's license, as noted in the 8 | # LICENSE file. 9 | # 10 | # SPDX-License-Identifier: BSD-2-Clause 11 | 12 | # We want to use bash 13 | SHELL:=/bin/bash 14 | 15 | # Set environment variables 16 | CLIS:=tuf-client # tuf 17 | 18 | # Default target 19 | .PHONY: default 20 | default: build 21 | 22 | ##################### 23 | # build section 24 | ##################### 25 | 26 | # Build 27 | .PHONY: build 28 | build: $(addprefix build-, $(CLIS)) 29 | 30 | # Target for building a Go binary 31 | .PHONY: build-% 32 | build-%: 33 | @echo "Building $*" 34 | @go build -o $* examples/cli/$*/main.go 35 | 36 | ##################### 37 | # test section 38 | ##################### 39 | 40 | # Test target 41 | .PHONY: test 42 | test: 43 | go test -race -covermode atomic ./... 44 | 45 | ##################### 46 | # lint section 47 | ##################### 48 | 49 | .PHONY: lint 50 | lint: 51 | golangci-lint run 52 | 53 | .PHONY: fmt 54 | fmt: 55 | go fmt ./... 56 | 57 | ##################### 58 | # examples section 59 | ##################### 60 | 61 | # Target for running all examples 62 | .PHONY: example-all 63 | example-all: example-client example-repository example-multirepo example-tuf-client-cli example-root-signing 64 | 65 | # Target for demoing the examples/client/client_example.go 66 | .PHONY: example-client 67 | example-client: 68 | @echo "Executing the following example - client/client_example.go" 69 | @cd examples/client/ && go run . 70 | 71 | # Target for demoing the examples/repository/basic_repository.go 72 | .PHONY: example-repository 73 | example-repository: 74 | @echo "Executing the following example - repository/basic_repository.go" 75 | @cd examples/repository/ && go run . 76 | 77 | # Target for demoing the examples/multirepo/client/client_example.go 78 | .PHONY: example-multirepo 79 | example-multirepo: 80 | @echo "Executing the following example - multirepo/client/client_example.go" 81 | @cd examples/multirepo/client/ && go run . 82 | 83 | # Target for demoing the tuf-client cli 84 | .PHONY: example-tuf-client-cli 85 | example-tuf-client-cli: build-tuf-client 86 | @echo "Clearing any leftover artifacts..." 87 | ./tuf-client reset --force 88 | @echo "Initializing the following https://jku.github.io/tuf-demo/ TUF repository" 89 | @sleep 2 90 | ./tuf-client init --url https://jku.github.io/tuf-demo/metadata 91 | @echo "Downloading the following target file - rdimitrov/artifact-example.md" 92 | @sleep 2 93 | ./tuf-client get --url https://jku.github.io/tuf-demo/metadata --turl https://jku.github.io/tuf-demo/targets rdimitrov/artifact-example.md 94 | 95 | # Target for demoing the tuf-client cli with root-signing repo 96 | .PHONY: example-root-signing 97 | example-root-signing: build-tuf-client 98 | @echo "Clearing any leftover artifacts..." 99 | ./tuf-client reset --force 100 | @echo "Downloading the initial root of trust" 101 | @curl -L "https://tuf-repo-cdn.sigstore.dev/5.root.json" > root.json 102 | @echo "Initializing the following https://tuf-repo-cdn.sigstore.dev TUF repository" 103 | @sleep 2 104 | ./tuf-client init --url https://tuf-repo-cdn.sigstore.dev --file root.json 105 | @echo "Downloading the following target file - rekor.pub" 106 | @sleep 2 107 | ./tuf-client get --url https://tuf-repo-cdn.sigstore.dev --turl https://tuf-repo-cdn.sigstore.dev/targets rekor.pub 108 | 109 | # Clean target 110 | .PHONY: clean 111 | clean: 112 | @rm -rf examples/multirepo/client/bootstrap/ 113 | @rm -rf examples/multirepo/client/download/ 114 | @rm -rf examples/multirepo/client/metadata/ 115 | @rm -rf examples/repository/tmp* 116 | @rm -rf examples/client/tmp* 117 | @rm -rf tuf_download 118 | @rm -rf tuf_metadata 119 | @rm -f tuf-client 120 | @rm -f root.json 121 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2022-2023 VMware, Inc. 2 | 3 | This product is licensed to you under the BSD-2 license (the "License"). 4 | You may not use this product except in compliance with the BSD-2 License. 5 | This product may include a number of subcomponents with separate copyright 6 | notices and license terms. Your use of these subcomponents is subject to 7 | the terms and conditions of the subcomponent's license, as noted in the 8 | LICENSE file. 9 | 10 | SPDX-License-Identifier: BSD-2-Clause 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ---------------------------- 2 | ## Deprecation notice: 3 | 4 | The code base of this project got donated to https://github.com/theupdateframework/go-tuf where it replaced the existing implementation and is now being distributed under the v2 version. 5 | 6 | That said take into account that I'll be archiving this repository in a few weeks so feel free to switch to https://github.com/theupdateframework/go-tuf/v2 7 | 8 | I'm happy to see how something that started as a PoC while being sick with COVID progressed to replacing the existing go-tuf code a year later. 9 | 10 | Thanks to everyone that helped. 11 | 12 | ---------------------------- 13 | 14 | ![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/rdimitrov/go-tuf-metadata/ci.yml?branch=main) 15 | [![codecov](https://codecov.io/github/rdimitrov/go-tuf-metadata/branch/main/graph/badge.svg?token=2ZUA68ZL13)](https://codecov.io/github/rdimitrov/go-tuf-metadata) 16 | [![Go Reference](https://pkg.go.dev/badge/github.com/rdimitrov/go-tuf-metadata.svg)](https://pkg.go.dev/github.com/rdimitrov/go-tuf-metadata) 17 | [![Go Report Card](https://goreportcard.com/badge/github.com/rdimitrov/go-tuf-metadata)](https://goreportcard.com/report/github.com/rdimitrov/go-tuf-metadata) 18 | [![License](https://img.shields.io/badge/License-BSD_2--Clause-orange.svg)](https://opensource.org/licenses/BSD-2-Clause) 19 | 20 | # TUF A Framework for Securing Software Update Systems 21 | 22 | ---------------------------- 23 | 24 | [The Update Framework (TUF)](https://theupdateframework.io/) is a framework for 25 | secure content delivery and updates. It protects against various types of 26 | supply chain attacks and provides resilience to compromise. 27 | 28 | [go-tuf-metadata](https://github.com/rdimitrov/go-tuf-metadata) started from the idea of providing a Go implementation of TUF that is heavily influenced by the 29 | design decisions made in [python-tuf](https://github.com/theupdateframework/python-tuf). 30 | 31 | ## About The Update Framework 32 | 33 | ---------------------------- 34 | The Update Framework (TUF) design helps developers maintain the security of a 35 | software update system, even against attackers that compromise the repository 36 | or signing keys. 37 | TUF provides a flexible 38 | [specification](https://github.com/theupdateframework/specification/blob/master/tuf-spec.md) 39 | defining functionality that developers can use in any software update system or 40 | re-implement to fit their needs. 41 | 42 | TUF is hosted by the [Linux Foundation](https://www.linuxfoundation.org/) as 43 | part of the [Cloud Native Computing Foundation](https://www.cncf.io/) (CNCF) 44 | and its design is [used in production](https://theupdateframework.io/adoptions/) 45 | by various tech companies and open-source organizations. 46 | 47 | Please see [TUF's website](https://theupdateframework.com/) for more information about TUF! 48 | 49 | ## Overview 50 | 51 | ---------------------------- 52 | 53 | The [go-tuf-metadata](https://github.com/rdimitrov/go-tuf-metadata) project provides the following functionality: 54 | 55 | * creation, reading, and writing of metadata 56 | * an easy object-oriented approach for interacting with metadata 57 | * consistent snapshots 58 | * signing and verifying metadata 59 | * ED25519, RSA, and ECDSA key types referenced by the latest TUF specification 60 | * top-level role delegation 61 | * target delegation via standard and hash bin delegations 62 | * support of [succinct hash bin delegations](https://github.com/theupdateframework/taps/blob/master/tap15.md) which significantly reduce the size of metadata 63 | * 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)) 64 | * TUF client API 65 | * TUF multi-repository client API (implements [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md)) 66 | 67 | ## Examples 68 | 69 | ---------------------------- 70 | 71 | * [basic_repository.go](examples/repository/basic_repository.go) example which demonstrates how to *manually* create and 72 | maintain repository metadata using the low-level Metadata API. 73 | 74 | To try it - run `make example-repository` (the artifacts will be located at `examples/repository/`). 75 | 76 | * [client_example.go](examples/client/client_example.go) which demonstrates how to implement a client using the [updater](metadata/updater/updater.go) package. 77 | 78 | To try it - run `make example-client` (the artifacts will be located at `examples/client/`) 79 | 80 | * [tuf-client CLI](examples/cli/tuf-client/) - a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification. 81 | 82 | To try it - run `make example-tuf-client-cli` 83 | 84 | * [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. 85 | 86 | To try it - run `make example-multirepo` 87 | 88 | ## Package details 89 | 90 | ---------------------------- 91 | 92 | ### The `metadata` package 93 | 94 | * The `metadata` package provides access to a Metadata file abstraction that closely 95 | follows the TUF specification’s document formats. This API handles de/serialization 96 | to and from files and bytes. It also covers the process of creating and verifying metadata 97 | signatures and makes it easier to access and modify metadata content. It is purely 98 | focused on individual pieces of Metadata and provides no concepts like “repository” 99 | or “update workflow”. 100 | 101 | ### The `trustedmetadata` package 102 | 103 | * A `TrustedMetadata` instance ensures that the collection of metadata in it is valid 104 | and trusted through the whole client update workflow. It provides easy ways to update 105 | the metadata with the caller making decisions on what is updated. 106 | 107 | ### The `config` package 108 | 109 | * The `config` package stores configuration for an ``Updater`` instance. 110 | 111 | ### The `fetcher` package 112 | 113 | * The `fetcher` package defines an interface for abstract network download. 114 | 115 | ### The `updater` package 116 | 117 | * The `updater` package provides an implementation of the TUF client workflow. 118 | It provides ways to query and download target files securely while handling the 119 | TUF update workflow behind the scenes. It is implemented on top of the Metadata API 120 | and can be used to implement various TUF clients with relatively little effort. 121 | 122 | ### The `multirepo` package 123 | 124 | * 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. 125 | 126 | ## Documentation 127 | 128 | ---------------------------- 129 | 130 | * [go-tuf-metadata documentation](https://pkg.go.dev/github.com/rdimitrov/go-tuf-metadata) 131 | 132 | * [Introduction to TUF's Design](https://theupdateframework.io/overview/) 133 | 134 | * [The TUF Specification](https://theupdateframework.github.io/specification/latest/) 135 | 136 | ## Contact 137 | 138 | ---------------------------- 139 | 140 | Questions, feedback, and suggestions are welcomed on the [#tuf](https://cloud-native.slack.com/archives/C8NMD3QJ3) channel on 141 | [CNCF Slack](https://slack.cncf.io/). 142 | 143 | We strive to make the specification easy to implement, so if you come across 144 | any inconsistencies or experience any difficulty, do let us know by sending an 145 | email, or by reporting an issue in the GitHub [specification 146 | repo](https://github.com/theupdateframework/specification/issues). 147 | -------------------------------------------------------------------------------- /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 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package cmd 13 | 14 | import ( 15 | "fmt" 16 | stdlog "log" 17 | "os" 18 | "path/filepath" 19 | 20 | "github.com/go-logr/stdr" 21 | "github.com/rdimitrov/go-tuf-metadata/metadata" 22 | "github.com/rdimitrov/go-tuf-metadata/metadata/config" 23 | "github.com/rdimitrov/go-tuf-metadata/metadata/updater" 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | var targetsURL string 28 | var useNonHashPrefixedTargetFiles bool 29 | 30 | type localConfig struct { 31 | MetadataDir string 32 | DownloadDir string 33 | MetadataURL string 34 | TargetsURL string 35 | } 36 | 37 | var getCmd = &cobra.Command{ 38 | Use: "get", 39 | Aliases: []string{"g"}, 40 | Short: "Download a target file", 41 | Args: cobra.ExactArgs(1), 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 GetCmd(args[0]) 48 | }, 49 | } 50 | 51 | func init() { 52 | getCmd.Flags().StringVarP(&targetsURL, "turl", "t", "", "URL of where the target files are hosted") 53 | getCmd.Flags().BoolVarP(&useNonHashPrefixedTargetFiles, "nonprefixed", "", false, "Do not use hash-prefixed target files with consistent snapshots") 54 | rootCmd.AddCommand(getCmd) 55 | } 56 | 57 | func GetCmd(target string) error { 58 | // set logger and debug verbosity level 59 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "get_cmd", stdlog.LstdFlags))) 60 | if Verbosity { 61 | stdr.SetVerbosity(5) 62 | } 63 | 64 | // verify the client environment was initialized and fetch path names 65 | env, err := verifyEnv() 66 | if err != nil { 67 | return err 68 | } 69 | // read the trusted root metadata 70 | rootBytes, err := os.ReadFile(filepath.Join(env.MetadataDir, "root.json")) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | // updater configuration 76 | cfg, err := config.New(env.MetadataURL, rootBytes) // default config 77 | if err != nil { 78 | return err 79 | } 80 | cfg.LocalMetadataDir = env.MetadataDir 81 | cfg.LocalTargetsDir = env.DownloadDir 82 | cfg.RemoteTargetsURL = env.TargetsURL 83 | cfg.PrefixTargetsWithHash = !useNonHashPrefixedTargetFiles 84 | 85 | // create an Updater instance 86 | up, err := updater.New(cfg) 87 | if err != nil { 88 | return fmt.Errorf("failed to create Updater instance: %w", err) 89 | } 90 | 91 | // try to build the top-level metadata 92 | err = up.Refresh() 93 | if err != nil { 94 | return fmt.Errorf("failed to refresh trusted metadata: %w", err) 95 | } 96 | 97 | // search if the desired target is available 98 | targetInfo, err := up.GetTargetInfo(target) 99 | if err != nil { 100 | return fmt.Errorf("target %s not found: %w", target, err) 101 | } 102 | 103 | // target is available, so let's see if the target is already present locally 104 | path, _, err := up.FindCachedTarget(targetInfo, "") 105 | if err != nil { 106 | return fmt.Errorf("failed while finding a cached target: %w", err) 107 | } 108 | 109 | if path != "" { 110 | fmt.Printf("Target %s is already present at - %s\n", target, path) 111 | return nil 112 | } 113 | 114 | // target is not present locally, so let's try to download it 115 | path, _, err = up.DownloadTarget(targetInfo, "", "") 116 | if err != nil { 117 | return fmt.Errorf("failed to download target file %s - %w", target, err) 118 | } 119 | 120 | fmt.Printf("Successfully downloaded target %s at - %s\n", target, path) 121 | 122 | return nil 123 | } 124 | 125 | func verifyEnv() (*localConfig, error) { 126 | // get working directory 127 | cwd, err := os.Getwd() 128 | if err != nil { 129 | return nil, err 130 | } 131 | // if no targetsURL is set, we expect that the target files are located at the same location where the metadata is 132 | if targetsURL == "" { 133 | targetsURL = RepositoryURL 134 | } 135 | // start populating what we need 136 | env := &localConfig{ 137 | MetadataDir: filepath.Join(cwd, DefaultMetadataDir), 138 | DownloadDir: filepath.Join(cwd, DefaultDownloadDir), 139 | MetadataURL: RepositoryURL, 140 | TargetsURL: targetsURL, 141 | } 142 | 143 | // verify there's local metadata folder 144 | _, err = os.Stat(env.MetadataDir) 145 | if err != nil { 146 | return nil, fmt.Errorf("no local metadata folder: %w", err) 147 | } 148 | // verify there's local download folder 149 | _, err = os.Stat(env.DownloadDir) 150 | if err != nil { 151 | return nil, fmt.Errorf("no local download folder: %w", err) 152 | } 153 | // verify there's a local root.json available for bootstrapping trust 154 | _, err = os.Stat(fmt.Sprintf("%s/%s.json", env.MetadataDir, metadata.ROOT)) 155 | if err != nil { 156 | return nil, fmt.Errorf("no local download folder: %w", err) 157 | } 158 | return env, nil 159 | } 160 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/cmd/init.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package cmd 13 | 14 | import ( 15 | "fmt" 16 | "io" 17 | stdlog "log" 18 | "net/http" 19 | "net/url" 20 | "os" 21 | "path/filepath" 22 | 23 | "github.com/go-logr/stdr" 24 | "github.com/rdimitrov/go-tuf-metadata/metadata" 25 | "github.com/rdimitrov/go-tuf-metadata/metadata/trustedmetadata" 26 | "github.com/spf13/cobra" 27 | ) 28 | 29 | var rootPath string 30 | 31 | var initCmd = &cobra.Command{ 32 | Use: "init", 33 | Aliases: []string{"i"}, 34 | Short: "Initialize the client with trusted root.json metadata", 35 | Args: cobra.ExactArgs(0), 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | if RepositoryURL == "" { 38 | fmt.Println("Error: required flag(s) \"url\" not set") 39 | os.Exit(1) 40 | } 41 | return InitializeCmd() 42 | }, 43 | } 44 | 45 | func init() { 46 | initCmd.Flags().StringVarP(&rootPath, "file", "f", "", "location of the trusted root metadata file") 47 | rootCmd.AddCommand(initCmd) 48 | } 49 | 50 | func InitializeCmd() error { 51 | copyTrusted := true 52 | // set logger and debug verbosity level 53 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "ini_cmd", stdlog.LstdFlags))) 54 | if Verbosity { 55 | stdr.SetVerbosity(5) 56 | } 57 | 58 | // prepare the local environment 59 | localMetadataDir, err := prepareEnvironment() 60 | if err != nil { 61 | return err 62 | } 63 | 64 | // if there's no root.json file passed, try to download the 1.root.json from the repository URL 65 | if rootPath == "" { 66 | 67 | fmt.Printf("No root.json file was provided. Trying to download one from %s\n", RepositoryURL) 68 | rootPath, err = fetchTrustedRoot(localMetadataDir) 69 | if err != nil { 70 | return err 71 | } 72 | rootPath = fmt.Sprintf("%s/%s.json", rootPath, metadata.ROOT) 73 | // no need to copy root.json to the metadata folder as we already download it in the expected location 74 | copyTrusted = false 75 | } 76 | 77 | // read the content of root.json 78 | rootBytes, err := ReadFile(rootPath) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | // verify the content 84 | _, err = trustedmetadata.New(rootBytes) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | // Save the trusted root.json file to the metadata folder so it is available for future operations (if we haven't downloaded it) 90 | if copyTrusted { 91 | err = os.WriteFile(filepath.Join(localMetadataDir, rootPath), rootBytes, 0644) 92 | if err != nil { 93 | return err 94 | } 95 | } 96 | 97 | fmt.Println("Initialization successful") 98 | 99 | return nil 100 | } 101 | 102 | // prepareEnvironment prepares the local environment 103 | func prepareEnvironment() (string, error) { 104 | // get working directory 105 | cwd, err := os.Getwd() 106 | if err != nil { 107 | return "", fmt.Errorf("failed to get current working directory: %w", err) 108 | } 109 | metadataPath := filepath.Join(cwd, DefaultMetadataDir) 110 | downloadPath := filepath.Join(cwd, DefaultDownloadDir) 111 | 112 | // create a folder for storing the artifacts 113 | err = os.Mkdir(metadataPath, 0750) 114 | if err != nil { 115 | return "", fmt.Errorf("failed to create local metadata folder: %w", err) 116 | } 117 | 118 | // create a destination folder for storing the downloaded target 119 | err = os.Mkdir(downloadPath, 0750) 120 | if err != nil { 121 | return "", fmt.Errorf("failed to create download folder: %w", err) 122 | } 123 | return metadataPath, nil 124 | } 125 | 126 | // fetchTrustedRoot downloads the initial root metadata 127 | func fetchTrustedRoot(metadataDir string) (string, error) { 128 | // download the initial root metadata so we can bootstrap Trust-On-First-Use 129 | rootURL, err := url.JoinPath(RepositoryURL, "1.root.json") 130 | if err != nil { 131 | return "", fmt.Errorf("failed to create URL path for 1.root.json: %w", err) 132 | } 133 | 134 | req, err := http.NewRequest("GET", rootURL, nil) 135 | if err != nil { 136 | return "", fmt.Errorf("failed to create http request: %w", err) 137 | } 138 | 139 | client := http.DefaultClient 140 | 141 | res, err := client.Do(req) 142 | if err != nil { 143 | return "", fmt.Errorf("failed to executed the http request: %w", err) 144 | } 145 | 146 | defer res.Body.Close() 147 | 148 | data, err := io.ReadAll(res.Body) 149 | if err != nil { 150 | return "", fmt.Errorf("failed to read the http request body: %w", err) 151 | } 152 | 153 | // write the downloaded root metadata to file 154 | err = os.WriteFile(filepath.Join(metadataDir, "root.json"), data, 0644) 155 | if err != nil { 156 | return "", fmt.Errorf("failed to write root.json metadata: %w", err) 157 | } 158 | return metadataDir, nil 159 | } 160 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/cmd/reset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package cmd 13 | 14 | import ( 15 | "fmt" 16 | "os" 17 | "path/filepath" 18 | "strings" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | var ForceDelete bool 24 | 25 | var resetCmd = &cobra.Command{ 26 | Use: "reset", 27 | Aliases: []string{"r"}, 28 | Short: "Resets the local environment. Warning: this deletes both the metadata and download folders and all of their contents", 29 | Args: cobra.ExactArgs(0), 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | return ResetCmd() 32 | }, 33 | } 34 | 35 | func init() { 36 | resetCmd.Flags().BoolVarP(&ForceDelete, "force", "f", false, "force delete without waiting for confirmation") 37 | rootCmd.AddCommand(resetCmd) 38 | } 39 | 40 | func ResetCmd() error { 41 | // get working directory 42 | cwd, err := os.Getwd() 43 | if err != nil { 44 | return fmt.Errorf("failed to get current working directory: %w", err) 45 | } 46 | 47 | // folders to delete 48 | metadataPath := filepath.Join(cwd, DefaultMetadataDir) 49 | downloadPath := filepath.Join(cwd, DefaultDownloadDir) 50 | 51 | // warning: deletes the metadata folder and all of its contents 52 | fmt.Printf("Warning: Are you sure you want to delete the \"%s\" folder and all of its contents? (y/n)\n", metadataPath) 53 | if ForceDelete || askForConfirmation() { 54 | os.RemoveAll(metadataPath) 55 | fmt.Printf("Folder %s was successfully deleted\n", metadataPath) 56 | } else { 57 | fmt.Printf("Folder \"%s\" was not deleted\n", metadataPath) 58 | } 59 | 60 | // warning: deletes the download folder and all of its contents 61 | fmt.Printf("Warning: Are you sure you want to delete the \"%s\" folder and all of its contents? (y/n)\n", downloadPath) 62 | if ForceDelete || askForConfirmation() { 63 | os.RemoveAll(downloadPath) 64 | fmt.Printf("Folder %s was successfully deleted\n", downloadPath) 65 | } else { 66 | fmt.Printf("Folder \"%s\" was not deleted\n", downloadPath) 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func askForConfirmation() bool { 73 | var response string 74 | _, err := fmt.Scanln(&response) 75 | if err != nil { 76 | fmt.Println(err) 77 | os.Exit(1) 78 | } 79 | switch strings.ToLower(response) { 80 | case "y", "yes": 81 | return true 82 | case "n", "no": 83 | return false 84 | default: 85 | fmt.Println("I'm sorry but I didn't get what you meant, please type (y)es or (n)o and then press enter:") 86 | return askForConfirmation() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package cmd 13 | 14 | import ( 15 | "io" 16 | "os" 17 | 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | const ( 22 | DefaultMetadataDir = "tuf_metadata" 23 | DefaultDownloadDir = "tuf_download" 24 | ) 25 | 26 | var Verbosity bool 27 | var RepositoryURL string 28 | 29 | var rootCmd = &cobra.Command{ 30 | Use: "tuf-client", 31 | Short: "tuf-client - a client-side CLI tool for The Update Framework (TUF)", 32 | Long: `tuf-client is a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification. 33 | 34 | The tuf-client can be used to query for available targets and to download them in a secure manner. 35 | 36 | All downloaded files are verified by signed metadata.`, 37 | Run: func(cmd *cobra.Command, args []string) { 38 | // show the help message if no command has been used 39 | if len(args) == 0 { 40 | _ = cmd.Help() 41 | os.Exit(0) 42 | } 43 | }, 44 | } 45 | 46 | func Execute() { 47 | rootCmd.PersistentFlags().BoolVarP(&Verbosity, "verbose", "v", false, "verbose output") 48 | rootCmd.PersistentFlags().StringVarP(&RepositoryURL, "url", "u", "", "URL of the TUF repository") 49 | 50 | if err := rootCmd.Execute(); err != nil { 51 | os.Exit(1) 52 | } 53 | } 54 | 55 | // ReadFile reads the content of a file and return its bytes 56 | func ReadFile(name string) ([]byte, error) { 57 | in, err := os.Open(name) 58 | if err != nil { 59 | return nil, err 60 | } 61 | defer in.Close() 62 | data, err := io.ReadAll(in) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return data, nil 67 | } 68 | -------------------------------------------------------------------------------- /examples/cli/tuf-client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package main 13 | 14 | import ( 15 | tufclient "github.com/rdimitrov/go-tuf-metadata/examples/cli/tuf-client/cmd" 16 | ) 17 | 18 | func main() { 19 | tufclient.Execute() 20 | } 21 | -------------------------------------------------------------------------------- /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 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package cmd 13 | 14 | import ( 15 | "fmt" 16 | stdlog "log" 17 | "os" 18 | 19 | "github.com/go-logr/stdr" 20 | "github.com/rdimitrov/go-tuf-metadata/metadata" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | var initCmd = &cobra.Command{ 25 | Use: "init", 26 | Aliases: []string{"i"}, 27 | Short: "Initialize a repository", 28 | Args: cobra.ExactArgs(0), 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | return InitializeCmd() 31 | }, 32 | } 33 | 34 | func init() { 35 | rootCmd.AddCommand(initCmd) 36 | } 37 | 38 | func InitializeCmd() error { 39 | // set logger and debug verbosity level 40 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "ini_cmd", stdlog.LstdFlags))) 41 | if Verbosity { 42 | stdr.SetVerbosity(5) 43 | } 44 | 45 | fmt.Println("Initialization successful") 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /examples/cli/tuf/cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package cmd 13 | 14 | import ( 15 | "io" 16 | "os" 17 | 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var Verbosity bool 22 | 23 | var rootCmd = &cobra.Command{ 24 | Use: "tuf", 25 | Short: "tuf - a repository-side CLI tool for The Update Framework (TUF)", 26 | Long: "tuf - a repository-side CLI tool for The Update Framework (TUF)", 27 | Run: func(cmd *cobra.Command, args []string) { 28 | 29 | }, 30 | } 31 | 32 | func Execute() { 33 | rootCmd.PersistentFlags().BoolVarP(&Verbosity, "verbose", "v", false, "verbose output") 34 | 35 | if err := rootCmd.Execute(); err != nil { 36 | os.Exit(1) 37 | } 38 | } 39 | 40 | // ReadFile reads the content of a file and return its bytes 41 | func ReadFile(name string) ([]byte, error) { 42 | in, err := os.Open(name) 43 | if err != nil { 44 | return nil, err 45 | } 46 | defer in.Close() 47 | data, err := io.ReadAll(in) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return data, nil 52 | } 53 | -------------------------------------------------------------------------------- /examples/cli/tuf/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "os" 17 | 18 | tuf "github.com/rdimitrov/go-tuf-metadata/examples/cli/tuf/cmd" 19 | ) 20 | 21 | func main() { 22 | fmt.Println("Not implemented") 23 | os.Exit(1) 24 | tuf.Execute() 25 | } 26 | -------------------------------------------------------------------------------- /examples/client/client_example.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "io" 17 | stdlog "log" 18 | "net/http" 19 | "net/url" 20 | "os" 21 | "path/filepath" 22 | 23 | "github.com/go-logr/stdr" 24 | 25 | "github.com/rdimitrov/go-tuf-metadata/metadata" 26 | "github.com/rdimitrov/go-tuf-metadata/metadata/config" 27 | "github.com/rdimitrov/go-tuf-metadata/metadata/updater" 28 | ) 29 | 30 | // The following config is used to fetch a target from Jussi's GitHub repository example 31 | const ( 32 | metadataURL = "https://jku.github.io/tuf-demo/metadata" 33 | targetsURL = "https://jku.github.io/tuf-demo/targets" 34 | targetName = "rdimitrov/artifact-example.md" 35 | verbosity = 4 36 | generateRandomFolder = false 37 | ) 38 | 39 | func main() { 40 | // set logger to stdout with info level 41 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "client_example", stdlog.LstdFlags))) 42 | stdr.SetVerbosity(verbosity) 43 | 44 | log := metadata.GetLogger() 45 | 46 | // initialize environment - temporary folders, etc. 47 | metadataDir, err := InitEnvironment() 48 | if err != nil { 49 | log.Error(err, "Failed to initialize environment") 50 | } 51 | 52 | // initialize client with Trust-On-First-Use 53 | err = InitTrustOnFirstUse(metadataDir) 54 | if err != nil { 55 | log.Error(err, "Trust-On-First-Use failed") 56 | } 57 | 58 | // download the desired target 59 | err = DownloadTarget(metadataDir, targetName) 60 | if err != nil { 61 | log.Error(err, "Download failed") 62 | } 63 | } 64 | 65 | // InitEnvironment prepares the local environment - temporary folders, etc. 66 | func InitEnvironment() (string, error) { 67 | var tmpDir string 68 | // get working directory 69 | cwd, err := os.Getwd() 70 | if err != nil { 71 | return "", fmt.Errorf("failed to get current working directory: %w", err) 72 | } 73 | if !generateRandomFolder { 74 | tmpDir = filepath.Join(cwd, "tmp") 75 | // create a temporary folder for storing the demo artifacts 76 | os.Mkdir(tmpDir, 0750) 77 | } else { 78 | // create a temporary folder for storing the demo artifacts 79 | tmpDir, err = os.MkdirTemp(cwd, "tmp") 80 | if err != nil { 81 | return "", fmt.Errorf("failed to create a temporary folder: %w", err) 82 | } 83 | } 84 | 85 | // create a destination folder for storing the downloaded target 86 | os.Mkdir(filepath.Join(tmpDir, "download"), 0750) 87 | return tmpDir, nil 88 | } 89 | 90 | // InitTrustOnFirstUse initialize local trusted metadata (Trust-On-First-Use) 91 | func InitTrustOnFirstUse(metadataDir string) error { 92 | // check if there's already a local root.json available for bootstrapping trust 93 | _, err := os.Stat(filepath.Join(metadataDir, "root.json")) 94 | if err == nil { 95 | return nil 96 | } 97 | 98 | // download the initial root metadata so we can bootstrap Trust-On-First-Use 99 | rootURL, err := url.JoinPath(metadataURL, "1.root.json") 100 | if err != nil { 101 | return fmt.Errorf("failed to create URL path for 1.root.json: %w", err) 102 | } 103 | 104 | req, err := http.NewRequest("GET", rootURL, nil) 105 | if err != nil { 106 | return fmt.Errorf("failed to create http request: %w", err) 107 | } 108 | 109 | client := http.DefaultClient 110 | 111 | res, err := client.Do(req) 112 | if err != nil { 113 | return fmt.Errorf("failed to executed the http request: %w", err) 114 | } 115 | 116 | defer res.Body.Close() 117 | 118 | data, err := io.ReadAll(res.Body) 119 | if err != nil { 120 | return fmt.Errorf("failed to read the http request body: %w", err) 121 | } 122 | 123 | // write the downloaded root metadata to file 124 | err = os.WriteFile(filepath.Join(metadataDir, "root.json"), data, 0644) 125 | if err != nil { 126 | return fmt.Errorf("failed to write root.json metadata: %w", err) 127 | } 128 | 129 | return nil 130 | } 131 | 132 | // DownloadTarget downloads the target file using Updater. The Updater refreshes the top-level metadata, 133 | // get the target information, verifies if the target is already cached, and in case it 134 | // is not cached, downloads the target file. 135 | func DownloadTarget(localMetadataDir, target string) error { 136 | log := metadata.GetLogger() 137 | 138 | rootBytes, err := os.ReadFile(filepath.Join(localMetadataDir, "root.json")) 139 | if err != nil { 140 | return err 141 | } 142 | // create updater configuration 143 | cfg, err := config.New(metadataURL, rootBytes) // default config 144 | if err != nil { 145 | return err 146 | } 147 | cfg.LocalMetadataDir = localMetadataDir 148 | cfg.LocalTargetsDir = filepath.Join(localMetadataDir, "download") 149 | cfg.RemoteTargetsURL = targetsURL 150 | cfg.PrefixTargetsWithHash = true 151 | 152 | // create a new Updater instance 153 | up, err := updater.New(cfg) 154 | if err != nil { 155 | return fmt.Errorf("failed to create Updater instance: %w", err) 156 | } 157 | 158 | // try to build the top-level metadata 159 | err = up.Refresh() 160 | if err != nil { 161 | return fmt.Errorf("failed to refresh trusted metadata: %w", err) 162 | } 163 | 164 | // search if the desired target is available 165 | targetInfo, err := up.GetTargetInfo(target) 166 | if err != nil { 167 | return fmt.Errorf("target %s not found: %w", target, err) 168 | } 169 | 170 | // target is available, so let's see if the target is already present locally 171 | path, _, err := up.FindCachedTarget(targetInfo, "") 172 | if err != nil { 173 | return fmt.Errorf("failed while finding a cached target: %w", err) 174 | } 175 | if path != "" { 176 | log.Info("Target is already present", "target", target, "path", path) 177 | } 178 | 179 | // target is not present locally, so let's try to download it 180 | path, _, err = up.DownloadTarget(targetInfo, "", "") 181 | if err != nil { 182 | return fmt.Errorf("failed to download target file %s - %w", target, err) 183 | } 184 | 185 | log.Info("Successfully downloaded target", "target", target, "path", path) 186 | 187 | return nil 188 | } 189 | -------------------------------------------------------------------------------- /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 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | stdlog "log" 17 | "os" 18 | "path/filepath" 19 | "strings" 20 | 21 | "github.com/go-logr/stdr" 22 | 23 | "github.com/rdimitrov/go-tuf-metadata/metadata" 24 | "github.com/rdimitrov/go-tuf-metadata/metadata/config" 25 | "github.com/rdimitrov/go-tuf-metadata/metadata/multirepo" 26 | "github.com/rdimitrov/go-tuf-metadata/metadata/updater" 27 | ) 28 | 29 | const ( 30 | metadataURL = "https://raw.githubusercontent.com/rdimitrov/go-tuf-metadata/main/examples/multirepo/repository/metadata" 31 | targetsURL = "https://raw.githubusercontent.com/rdimitrov/go-tuf-metadata/main/examples/multirepo/repository/targets" 32 | verbosity = 4 33 | ) 34 | 35 | func main() { 36 | // set logger to stdout with info level 37 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "multirepo_client_example", stdlog.LstdFlags))) 38 | stdr.SetVerbosity(verbosity) 39 | 40 | // Bootstrap TUF 41 | fmt.Printf("Bootstrapping the initial TUF repo - fetching map.json file and necessary trusted root files\n\n") 42 | mapBytes, trustedRoots, err := BootstrapTUF() // returns the map.json and the trusted root files 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | // Initialize the multi-repository TUF client 48 | fmt.Printf("Initializing the multi-repository TUF client with the given map.json file\n\n") 49 | client, err := InitMultiRepoTUF(mapBytes, trustedRoots) 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | // Refresh all repositories 55 | fmt.Printf("Refreshing each TUF client (updating metadata/client update workflow)\n\n") 56 | err = client.Refresh() 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | // Get target info for the given target 62 | fmt.Printf("Searching for a target using the multi-repository TUF client\n\n") 63 | targetInfo, repositories, err := client.GetTargetInfo("rekor.pub") // rekor.pub trusted_root.json fulcio_v1.crt.pem 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | // Download the target using that target info 69 | fmt.Println("Downloading a target using the multi-repository TUF client") 70 | _, _, err = client.DownloadTarget(repositories, targetInfo, "", "") 71 | if err != nil { 72 | panic(err) 73 | } 74 | } 75 | 76 | // BootstrapTUF returns the map file and the related trusted root metadata files 77 | func BootstrapTUF() ([]byte, map[string][]byte, error) { 78 | log := metadata.GetLogger() 79 | 80 | trustedRoots := map[string][]byte{} 81 | mapBytes := []byte{} 82 | // get working directory 83 | cwd, err := os.Getwd() 84 | if err != nil { 85 | return nil, nil, fmt.Errorf("failed to get current working directory: %w", err) 86 | } 87 | targetsDir := filepath.Join(cwd, "bootstrap/targets") 88 | 89 | // ensure the necessary folder layout 90 | err = os.MkdirAll(targetsDir, os.ModePerm) 91 | if err != nil { 92 | return nil, nil, err 93 | } 94 | 95 | // read the trusted root metadata 96 | rootBytes, err := os.ReadFile(filepath.Join(cwd, "root.json")) 97 | if err != nil { 98 | return nil, nil, err 99 | } 100 | 101 | // create updater configuration 102 | cfg, err := config.New(metadataURL, rootBytes) // default config 103 | if err != nil { 104 | return nil, nil, err 105 | } 106 | cfg.LocalMetadataDir = filepath.Join(cwd, "bootstrap") 107 | cfg.LocalTargetsDir = targetsDir 108 | cfg.RemoteTargetsURL = targetsURL 109 | 110 | // create a new Updater instance 111 | up, err := updater.New(cfg) 112 | if err != nil { 113 | return nil, nil, fmt.Errorf("failed to create Updater instance: %w", err) 114 | } 115 | 116 | // build the top-level metadata 117 | err = up.Refresh() 118 | if err != nil { 119 | return nil, nil, fmt.Errorf("failed to refresh trusted metadata: %w", err) 120 | } 121 | 122 | // download all target files 123 | for name, targetInfo := range up.GetTopLevelTargets() { 124 | // see if the target is already present locally 125 | path, _, err := up.FindCachedTarget(targetInfo, "") 126 | if err != nil { 127 | return nil, nil, fmt.Errorf("failed while finding a cached target: %w", err) 128 | } 129 | if path != "" { 130 | log.Info("Target is already present", "target", name, "path", path) 131 | } 132 | 133 | // target is not present locally, so let's try to download it 134 | // keeping the same path layout as its target path 135 | expectedTargetLocation := filepath.Join(targetsDir, name) 136 | dirName, _ := filepath.Split(expectedTargetLocation) 137 | err = os.MkdirAll(dirName, os.ModePerm) 138 | if err != nil { 139 | return nil, nil, err 140 | } 141 | 142 | // download targets (we don't have to actually store them other than for the sake of the example) 143 | path, bytes, err := up.DownloadTarget(targetInfo, expectedTargetLocation, "") 144 | if err != nil { 145 | return nil, nil, fmt.Errorf("failed to download target file %s - %w", name, err) 146 | } 147 | 148 | // populate the return values 149 | if name == "map.json" { 150 | mapBytes = bytes 151 | } else { 152 | repositoryName := strings.Split(name, string(os.PathSeparator)) 153 | trustedRoots[repositoryName[0]] = bytes 154 | } 155 | log.Info("Successfully downloaded target", "target", name, "path", path) 156 | } 157 | 158 | return mapBytes, trustedRoots, nil 159 | } 160 | 161 | func InitMultiRepoTUF(mapBytes []byte, trustedRoots map[string][]byte) (*multirepo.MultiRepoClient, error) { 162 | // get working directory 163 | cwd, err := os.Getwd() 164 | if err != nil { 165 | return nil, fmt.Errorf("failed to get current working directory: %w", err) 166 | } 167 | 168 | // create a new configuration for a multi-repository client 169 | cfg, err := multirepo.NewConfig(mapBytes, trustedRoots) 170 | if err != nil { 171 | return nil, err 172 | } 173 | cfg.LocalMetadataDir = filepath.Join(cwd, "metadata") 174 | cfg.LocalTargetsDir = filepath.Join(cwd, "download") 175 | 176 | // create a new instance of a multi-repository TUF client 177 | return multirepo.New(cfg) 178 | } 179 | -------------------------------------------------------------------------------- /examples/multirepo/client/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138", 5 | "sig": "b434f25bd61a5cb7c53883b3d91c53b94e9312cd17e19f2b1e1089d73772fad542c0a91ef109404fe4cfabbd2d4cf3b0f7eaaca4fd012555014f75a23d49460a" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "root", 10 | "consistent_snapshot": true, 11 | "expires": "2033-11-28T19:51:01.794266Z", 12 | "keys": { 13 | "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138": { 14 | "keytype": "ed25519", 15 | "keyval": { 16 | "public": "3b70174991642fd8cfd4b1a4d834843f785db02ad98372b24eb54d06750dd732" 17 | }, 18 | "scheme": "ed25519" 19 | }, 20 | "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696": { 21 | "keytype": "ed25519", 22 | "keyval": { 23 | "public": "2f70d3d75e50f50cf9af3adcc20754ba2d17601801f36d61cacb9f73a7f798f9" 24 | }, 25 | "scheme": "ed25519" 26 | }, 27 | "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5": { 28 | "keytype": "ed25519", 29 | "keyval": { 30 | "public": "3c3769ac5f74c8f2f84748586344ab8bbda9f0096859d5f37450dec1f5675434" 31 | }, 32 | "scheme": "ed25519" 33 | }, 34 | "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b": { 35 | "keytype": "ed25519", 36 | "keyval": { 37 | "public": "6b1daebaa9b926bb38c33fd7d4edc14e2e96f8795b2ffe6ed49adee6a1251b20" 38 | }, 39 | "scheme": "ed25519" 40 | } 41 | }, 42 | "roles": { 43 | "root": { 44 | "keyids": [ 45 | "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138" 46 | ], 47 | "threshold": 1 48 | }, 49 | "snapshot": { 50 | "keyids": [ 51 | "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696" 52 | ], 53 | "threshold": 1 54 | }, 55 | "targets": { 56 | "keyids": [ 57 | "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5" 58 | ], 59 | "threshold": 1 60 | }, 61 | "timestamp": { 62 | "keyids": [ 63 | "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b" 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 which 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 | -------------------------------------------------------------------------------- /examples/multirepo/repository/generate_metadata.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package main 13 | 14 | import ( 15 | "crypto" 16 | "fmt" 17 | "os" 18 | "path/filepath" 19 | "strings" 20 | "time" 21 | 22 | "github.com/rdimitrov/go-tuf-metadata/metadata" 23 | "github.com/rdimitrov/go-tuf-metadata/metadata/repository" 24 | "github.com/sigstore/sigstore/pkg/signature" 25 | "golang.org/x/crypto/ed25519" 26 | ) 27 | 28 | func main() { 29 | // Create top-level metadata 30 | roles := repository.New() 31 | keys := map[string]ed25519.PrivateKey{} 32 | 33 | // Create Targets metadata 34 | targets := metadata.Targets(helperExpireIn(10)) 35 | roles.SetTargets("targets", targets) 36 | 37 | // Add each target to Targets metadata 38 | for _, targetName := range []string{"targets/map.json", "targets/sigstore-tuf-root/root.json", "targets/staging/root.json"} { 39 | targetPath, localPath := helperGetPathForTarget(targetName) 40 | targetFileInfo, err := metadata.TargetFile().FromFile(localPath, "sha256") 41 | if err != nil { 42 | panic(fmt.Sprintln("generate_metadata.go:", "generating target file info failed", err)) 43 | } 44 | roles.Targets("targets").Signed.Targets[strings.TrimPrefix(targetPath, "targets/")] = targetFileInfo 45 | for _, eachHashValue := range targetFileInfo.Hashes { 46 | err := copyHashPrefixed(localPath, eachHashValue.String()) 47 | if err != nil { 48 | panic(err) 49 | } 50 | } 51 | } 52 | 53 | // Create Snapshot metadata 54 | snapshot := metadata.Snapshot(helperExpireIn(10)) 55 | roles.SetSnapshot(snapshot) 56 | 57 | // Create Timestamp metadata 58 | timestamp := metadata.Timestamp(helperExpireIn(10)) 59 | roles.SetTimestamp(timestamp) 60 | 61 | // Create Root metadata 62 | root := metadata.Root(helperExpireIn(10)) 63 | roles.SetRoot(root) 64 | 65 | // For this example, we generate one private key of type 'ed25519' for each top-level role 66 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { 67 | _, private, err := ed25519.GenerateKey(nil) 68 | if err != nil { 69 | panic(fmt.Sprintln("generate_metadata.go:", "key generation failed", err)) 70 | } 71 | keys[name] = private 72 | key, err := metadata.KeyFromPublicKey(private.Public()) 73 | if err != nil { 74 | panic(fmt.Sprintln("generate_metadata.go:", "key conversion failed", err)) 75 | } 76 | err = roles.Root().Signed.AddKey(key, name) 77 | if err != nil { 78 | panic(fmt.Sprintln("generate_metadata.go:", "adding key to root failed", err)) 79 | } 80 | } 81 | 82 | // Sign top-level metadata (in-band) 83 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { 84 | key := keys[name] 85 | signer, err := signature.LoadSigner(key, crypto.Hash(0)) 86 | if err != nil { 87 | panic(fmt.Sprintln("generate_metadata.go:", "loading a signer failed", err)) 88 | } 89 | switch name { 90 | case "targets": 91 | _, err = roles.Targets("targets").Sign(signer) 92 | case "snapshot": 93 | _, err = roles.Snapshot().Sign(signer) 94 | case "timestamp": 95 | _, err = roles.Timestamp().Sign(signer) 96 | case "root": 97 | _, err = roles.Root().Sign(signer) 98 | } 99 | if err != nil { 100 | panic(fmt.Sprintln("generate_metadata.go:", "metadata signing failed", err)) 101 | } 102 | } 103 | 104 | // Persist metadata (consistent snapshot) 105 | cwd, err := os.Getwd() 106 | if err != nil { 107 | panic(fmt.Sprintln("generate_metadata.go:", "getting cwd failed", err)) 108 | } 109 | // Save to metadata folder 110 | cwd = filepath.Join(cwd, "metadata") 111 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} { 112 | switch name { 113 | case "targets": 114 | filename := fmt.Sprintf("%d.%s.json", roles.Targets("targets").Signed.Version, name) 115 | err = roles.Targets("targets").ToFile(filepath.Join(cwd, filename), true) 116 | case "snapshot": 117 | filename := fmt.Sprintf("%d.%s.json", roles.Snapshot().Signed.Version, name) 118 | err = roles.Snapshot().ToFile(filepath.Join(cwd, filename), true) 119 | case "timestamp": 120 | filename := fmt.Sprintf("%s.json", name) 121 | err = roles.Timestamp().ToFile(filepath.Join(cwd, filename), true) 122 | case "root": 123 | filename := fmt.Sprintf("%d.%s.json", roles.Root().Signed.Version, name) 124 | err = roles.Root().ToFile(filepath.Join(cwd, filename), true) 125 | } 126 | if err != nil { 127 | panic(fmt.Sprintln("generate_metadata.go:", "saving metadata to file failed", err)) 128 | } 129 | } 130 | 131 | // Save the created root metadata in the client folder, this is the initial trusted root metadata 132 | err = roles.Root().ToFile(filepath.Join(cwd, "../../client/root.json"), true) 133 | if err != nil { 134 | panic(fmt.Sprintln("generate_metadata.go:", "saving trusted root metadata to client folder failed", err)) 135 | } 136 | 137 | // Verify that metadata is signed correctly 138 | // Verify root 139 | err = roles.Root().VerifyDelegate("root", roles.Root()) 140 | if err != nil { 141 | panic(fmt.Sprintln("generate_metadata.go:", "verifying root metadata failed", err)) 142 | } 143 | 144 | // Verify targets 145 | err = roles.Root().VerifyDelegate("targets", roles.Targets("targets")) 146 | if err != nil { 147 | panic(fmt.Sprintln("generate_metadata.go:", "verifying targets metadata failed", err)) 148 | } 149 | 150 | // Verify snapshot 151 | err = roles.Root().VerifyDelegate("snapshot", roles.Snapshot()) 152 | if err != nil { 153 | panic(fmt.Sprintln("generate_metadata.go:", "verifying snapshot metadata failed", err)) 154 | } 155 | 156 | // Verify timestamp 157 | err = roles.Root().VerifyDelegate("timestamp", roles.Timestamp()) 158 | if err != nil { 159 | panic(fmt.Sprintln("generate_metadata.go:", "verifying timestamp metadata failed", err)) 160 | } 161 | 162 | fmt.Println("Done! Metadata files location:", cwd) 163 | } 164 | 165 | // helperExpireIn returns time offset by years (for the sake of the example) 166 | func helperExpireIn(years int) time.Time { 167 | return time.Now().AddDate(years, 0, 0).UTC() 168 | } 169 | 170 | // helperGetPathForTarget returns local and target paths for target 171 | func helperGetPathForTarget(name string) (string, string) { 172 | cwd, err := os.Getwd() 173 | if err != nil { 174 | panic(fmt.Sprintln("generate_metadata.go:", "getting cwd failed", err)) 175 | } 176 | // _, dir := filepath.Split(cwd) 177 | // return filepath.Join(dir, name), filepath.Join(cwd, name) 178 | return name, filepath.Join(cwd, name) 179 | } 180 | 181 | func copyHashPrefixed(src string, hash string) error { 182 | data, err := os.ReadFile(src) 183 | if err != nil { 184 | return err 185 | } 186 | dirName, fileName := filepath.Split(src) 187 | err = os.WriteFile(filepath.Join(dirName, fmt.Sprintf("%s.%s", hash, fileName)), data, 0644) 188 | if err != nil { 189 | return err 190 | } 191 | return nil 192 | } 193 | -------------------------------------------------------------------------------- /examples/multirepo/repository/metadata/1.root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138", 5 | "sig": "b434f25bd61a5cb7c53883b3d91c53b94e9312cd17e19f2b1e1089d73772fad542c0a91ef109404fe4cfabbd2d4cf3b0f7eaaca4fd012555014f75a23d49460a" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "root", 10 | "consistent_snapshot": true, 11 | "expires": "2033-11-28T19:51:01.794266Z", 12 | "keys": { 13 | "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138": { 14 | "keytype": "ed25519", 15 | "keyval": { 16 | "public": "3b70174991642fd8cfd4b1a4d834843f785db02ad98372b24eb54d06750dd732" 17 | }, 18 | "scheme": "ed25519" 19 | }, 20 | "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696": { 21 | "keytype": "ed25519", 22 | "keyval": { 23 | "public": "2f70d3d75e50f50cf9af3adcc20754ba2d17601801f36d61cacb9f73a7f798f9" 24 | }, 25 | "scheme": "ed25519" 26 | }, 27 | "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5": { 28 | "keytype": "ed25519", 29 | "keyval": { 30 | "public": "3c3769ac5f74c8f2f84748586344ab8bbda9f0096859d5f37450dec1f5675434" 31 | }, 32 | "scheme": "ed25519" 33 | }, 34 | "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b": { 35 | "keytype": "ed25519", 36 | "keyval": { 37 | "public": "6b1daebaa9b926bb38c33fd7d4edc14e2e96f8795b2ffe6ed49adee6a1251b20" 38 | }, 39 | "scheme": "ed25519" 40 | } 41 | }, 42 | "roles": { 43 | "root": { 44 | "keyids": [ 45 | "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138" 46 | ], 47 | "threshold": 1 48 | }, 49 | "snapshot": { 50 | "keyids": [ 51 | "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696" 52 | ], 53 | "threshold": 1 54 | }, 55 | "targets": { 56 | "keyids": [ 57 | "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5" 58 | ], 59 | "threshold": 1 60 | }, 61 | "timestamp": { 62 | "keyids": [ 63 | "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b" 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": "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696", 5 | "sig": "e0f197531eec1a8aeea48014f4459206d59546c1a6a3ab5ea18910b229b51fe6475822fae0005fd8ff611944ca6e8b5c3b286a572d1812e561d55fdeafad9704" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "snapshot", 10 | "expires": "2033-11-28T19:51:01.794264Z", 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": "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5", 5 | "sig": "4c2ad791fe1b2d23b4a7292120b493c61795cb23519b2f23b9c17a25a1078b7cdf72f052fe4ddc583e45ef6ab6ca809f35c192bdf60649ba371bc063a62cbd05" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "targets", 10 | "expires": "2033-11-28T19:51:01.793233Z", 11 | "spec_version": "1.0.31", 12 | "targets": { 13 | "map.json": { 14 | "hashes": { 15 | "sha256": "9bb72b3c684f4e489e7fdce1afabff64b4e3f4b7877ac8cb78a684c2c65fc6d8" 16 | }, 17 | "length": 645 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": "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b", 5 | "sig": "41670fd3ca12b41a5f3109e6537646586099108c693c913d75570a47ff72e8910b32858a80be6f2ba058c0993af84de4f26a7b5a93b3322e0cf8d10e37352609" 6 | } 7 | ], 8 | "signed": { 9 | "_type": "timestamp", 10 | "expires": "2033-11-28T19:51:01.794266Z", 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/9bb72b3c684f4e489e7fdce1afabff64b4e3f4b7877ac8cb78a684c2c65fc6d8.map.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "repositories": { 4 | "sigstore-tuf-root": ["https://tuf-repo-cdn.sigstore.dev"], 5 | "staging": ["https://raw.githubusercontent.com/sigstore/root-signing/main/repository/repository"] 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://raw.githubusercontent.com/sigstore/root-signing/main/repository/repository"] 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 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rdimitrov/go-tuf-metadata 2 | 3 | go 1.21.5 4 | 5 | require ( 6 | github.com/go-logr/stdr v1.2.2 7 | github.com/secure-systems-lab/go-securesystemslib v0.8.0 8 | github.com/sigstore/sigstore v1.8.0 9 | github.com/sirupsen/logrus v1.9.3 10 | github.com/spf13/cobra v1.8.0 11 | github.com/stretchr/testify v1.8.4 12 | golang.org/x/crypto v0.18.0 13 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29 14 | golang.org/x/sys v0.16.0 15 | ) 16 | 17 | require ( 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/go-logr/logr v1.3.0 // indirect 20 | github.com/google/go-containerregistry v0.17.0 // indirect 21 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 22 | github.com/kr/pretty v0.3.1 // indirect 23 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect 24 | github.com/opencontainers/go-digest v1.0.0 // indirect 25 | github.com/pmezard/go-difflib v1.0.0 // indirect 26 | github.com/spf13/pflag v1.0.5 // indirect 27 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect 28 | golang.org/x/term v0.16.0 // indirect 29 | google.golang.org/grpc v1.56.3 // indirect 30 | gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect 31 | gopkg.in/yaml.v3 v3.0.1 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 4 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 11 | github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= 12 | github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 13 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 14 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 15 | github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= 16 | github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= 17 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 18 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 19 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 20 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 21 | github.com/google/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk= 22 | github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= 23 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 24 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 25 | github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= 26 | github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= 27 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 28 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 29 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 30 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 31 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e h1:RLTpX495BXToqxpM90Ws4hXEo4Wfh81jr9DX1n/4WOo= 32 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e/go.mod h1:EAuqr9VFWxBi9nD5jc/EA2MT1RFty9288TF6zdtYoCU= 33 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 34 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 35 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 36 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 37 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 38 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 40 | github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= 41 | github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= 42 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 43 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 44 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= 45 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= 46 | github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= 47 | github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= 48 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 49 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 50 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 51 | github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= 52 | github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= 53 | github.com/sigstore/sigstore v1.8.0 h1:sSRWXv1JiDsK4T2wNWVYcvKCgxcSrhQ/QUJxsfCO4OM= 54 | github.com/sigstore/sigstore v1.8.0/go.mod h1:l12B1gFlLIpBIVeqk/q1Lb+6YSOGNuN3xLExIjYH+qc= 55 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 56 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 57 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 58 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 59 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 60 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 61 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 62 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 63 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 64 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 65 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= 66 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= 67 | go.opentelemetry.io/otel v1.15.0 h1:NIl24d4eiLJPM0vKn4HjLYM+UZf6gSfi9Z+NmCxkWbk= 68 | go.opentelemetry.io/otel v1.15.0/go.mod h1:qfwLEbWhLPk5gyWrne4XnF0lC8wtywbuJbgfAE3zbek= 69 | go.opentelemetry.io/otel/trace v1.15.0 h1:5Fwje4O2ooOxkfyqI/kJwxWotggDLix4BSAvpE1wlpo= 70 | go.opentelemetry.io/otel/trace v1.15.0/go.mod h1:CUsmE2Ht1CRkvE8OsMESvraoZrrcgD1J2W8GV1ev0Y4= 71 | golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= 72 | golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= 73 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= 74 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 75 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 76 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 77 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 78 | golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= 79 | golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= 80 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= 81 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= 82 | google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= 83 | google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 84 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 85 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 86 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 87 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 88 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 89 | gopkg.in/go-jose/go-jose.v2 v2.6.1 h1:qEzJlIDmG9q5VO0M/o8tGS65QMHMS1w01TQJB1VPJ4U= 90 | gopkg.in/go-jose/go-jose.v2 v2.6.1/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= 91 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 92 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 93 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 94 | -------------------------------------------------------------------------------- /metadata/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package config 13 | 14 | import ( 15 | "net/url" 16 | "os" 17 | 18 | "github.com/rdimitrov/go-tuf-metadata/metadata/fetcher" 19 | ) 20 | 21 | type UpdaterConfig struct { 22 | // TUF configuration 23 | MaxRootRotations int64 24 | MaxDelegations int 25 | RootMaxLength int64 26 | TimestampMaxLength int64 27 | SnapshotMaxLength int64 28 | TargetsMaxLength int64 29 | // Updater configuration 30 | Fetcher fetcher.Fetcher 31 | LocalTrustedRoot []byte 32 | LocalMetadataDir string 33 | LocalTargetsDir string 34 | RemoteMetadataURL string 35 | RemoteTargetsURL string 36 | DisableLocalCache bool 37 | PrefixTargetsWithHash bool 38 | // UnsafeLocalMode only uses the metadata as written on disk 39 | // if the metadata is incomplete, calling updater.Refresh will fail 40 | UnsafeLocalMode bool 41 | } 42 | 43 | // New creates a new UpdaterConfig instance used by the Updater to 44 | // store configuration 45 | func New(remoteURL string, rootBytes []byte) (*UpdaterConfig, error) { 46 | // Default URL for target files - /targets 47 | targetsURL, err := url.JoinPath(remoteURL, "targets") 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return &UpdaterConfig{ 53 | // TUF configuration 54 | MaxRootRotations: 32, 55 | MaxDelegations: 32, 56 | RootMaxLength: 512000, // bytes 57 | TimestampMaxLength: 16384, // bytes 58 | SnapshotMaxLength: 2000000, // bytes 59 | TargetsMaxLength: 5000000, // bytes 60 | // Updater configuration 61 | Fetcher: &fetcher.DefaultFetcher{}, // use the default built-in download fetcher 62 | LocalTrustedRoot: rootBytes, // trusted root.json 63 | RemoteMetadataURL: remoteURL, // URL of where the TUF metadata is 64 | RemoteTargetsURL: targetsURL, // URL of where the target files should be downloaded from 65 | DisableLocalCache: false, // enable local caching of trusted metadata 66 | PrefixTargetsWithHash: true, // use hash-prefixed target files with consistent snapshots 67 | UnsafeLocalMode: false, 68 | }, nil 69 | } 70 | 71 | func (cfg *UpdaterConfig) EnsurePathsExist() error { 72 | if cfg.DisableLocalCache { 73 | return nil 74 | } 75 | 76 | for _, path := range []string{cfg.LocalMetadataDir, cfg.LocalTargetsDir} { 77 | if err := os.MkdirAll(path, os.ModePerm); err != nil { 78 | return err 79 | } 80 | } 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /metadata/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package config 13 | 14 | import ( 15 | "net/url" 16 | "os" 17 | "path/filepath" 18 | "testing" 19 | 20 | "github.com/rdimitrov/go-tuf-metadata/metadata/fetcher" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestNewUpdaterConfig(t *testing.T) { 25 | // setup testing table (tt) and create subtest for each entry 26 | for _, tt := range []struct { 27 | name string 28 | desc string 29 | remoteURL string 30 | rootBytes []byte 31 | config *UpdaterConfig 32 | wantErr error 33 | }{ 34 | { 35 | name: "success", 36 | desc: "No errors expected", 37 | remoteURL: "somepath", 38 | rootBytes: []byte("somerootbytes"), 39 | config: &UpdaterConfig{ 40 | MaxRootRotations: 32, 41 | MaxDelegations: 32, 42 | RootMaxLength: 512000, 43 | TimestampMaxLength: 16384, 44 | SnapshotMaxLength: 2000000, 45 | TargetsMaxLength: 5000000, 46 | Fetcher: &fetcher.DefaultFetcher{}, 47 | LocalTrustedRoot: []byte("somerootbytes"), 48 | RemoteMetadataURL: "somepath", 49 | RemoteTargetsURL: "somepath/targets", 50 | DisableLocalCache: false, 51 | PrefixTargetsWithHash: true, 52 | }, 53 | wantErr: nil, 54 | }, 55 | { 56 | name: "invalid character in URL", 57 | desc: "Invalid ASCII control sequence in input", 58 | remoteURL: string([]byte{0x7f}), 59 | rootBytes: []byte("somerootbytes"), 60 | config: nil, 61 | wantErr: &url.Error{}, // just make sure this is non-nil, url pkg has no exported errors 62 | }, 63 | } { 64 | t.Run(tt.name, func(t *testing.T) { 65 | // this will only be printed if run in verbose mode or if test fails 66 | t.Logf("Desc: %s", tt.desc) 67 | // run the function under test 68 | updaterConfig, err := New(tt.remoteURL, tt.rootBytes) 69 | // special case if we expect no error 70 | if tt.wantErr == nil { 71 | assert.NoErrorf(t, err, "expected no error but got %v", err) 72 | assert.EqualExportedValuesf(t, *tt.config, *updaterConfig, "expected %#+v but got %#+v", tt.config, updaterConfig) 73 | return 74 | } 75 | // compare the error with our expected error 76 | assert.Nilf(t, updaterConfig, "expected nil but got %#+v", updaterConfig) 77 | assert.Errorf(t, err, "expected %v but got %v", tt.wantErr, err) 78 | }) 79 | } 80 | } 81 | 82 | func TestEnsurePathsExist(t *testing.T) { 83 | // setup testing table (tt) and create subtest for each entry 84 | for _, tt := range []struct { 85 | name string 86 | desc string 87 | config *UpdaterConfig 88 | setup func(t *testing.T, cfg *UpdaterConfig) 89 | wantErr error 90 | }{ 91 | { 92 | name: "success", 93 | desc: "No errors expected", 94 | config: &UpdaterConfig{ 95 | DisableLocalCache: false, 96 | }, 97 | setup: func(t *testing.T, cfg *UpdaterConfig) { 98 | t.Helper() 99 | tmp := t.TempDir() 100 | cfg.LocalTargetsDir = filepath.Join(tmp, "targets") 101 | cfg.LocalMetadataDir = filepath.Join(tmp, "metadata") 102 | }, 103 | wantErr: nil, 104 | }, 105 | { 106 | name: "path not exist", 107 | desc: "No local directories error", 108 | config: &UpdaterConfig{ 109 | DisableLocalCache: false, 110 | }, 111 | setup: func(t *testing.T, cfg *UpdaterConfig) { 112 | t.Helper() 113 | }, 114 | wantErr: os.ErrNotExist, 115 | }, 116 | { 117 | name: "no local cache", 118 | desc: "Test if method no-op works", 119 | config: &UpdaterConfig{ 120 | DisableLocalCache: true, 121 | }, 122 | setup: func(t *testing.T, cfg *UpdaterConfig) { 123 | t.Helper() 124 | }, 125 | wantErr: nil, 126 | }, 127 | } { 128 | t.Run(tt.name, func(t *testing.T) { 129 | // this will only be printed if run in verbose mode or if test fails 130 | t.Logf("Desc: %s", tt.desc) 131 | // run special test setup in case it is needed for any subtest 132 | tt.setup(t, tt.config) 133 | // run the method under test 134 | err := tt.config.EnsurePathsExist() 135 | // special case if we expect no error 136 | if tt.wantErr == nil { 137 | assert.NoErrorf(t, err, "expected no error but got %v", err) 138 | return 139 | } 140 | // compare the error with our expected error 141 | assert.ErrorIsf(t, err, tt.wantErr, "expected %v but got %v", tt.wantErr, err) 142 | }) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /metadata/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package metadata 13 | 14 | import ( 15 | "fmt" 16 | ) 17 | 18 | // Define TUF error types used inside the new modern implementation. 19 | // The names chosen for TUF error types should start in 'Err' except where 20 | // there is a good reason not to, and provide that reason in those cases. 21 | 22 | // Repository errors 23 | 24 | // ErrRepository - an error with a repository's state, such as a missing file. 25 | // It covers all exceptions that come from the repository side when 26 | // looking from the perspective of users of metadata API or client 27 | type ErrRepository struct { 28 | Msg string 29 | } 30 | 31 | func (e ErrRepository) Error() string { 32 | return fmt.Sprintf("repository error: %s", e.Msg) 33 | } 34 | 35 | // ErrUnsignedMetadata - An error about metadata object with insufficient threshold of signatures 36 | type ErrUnsignedMetadata struct { 37 | Msg string 38 | } 39 | 40 | func (e ErrUnsignedMetadata) Error() string { 41 | return fmt.Sprintf("unsigned metadata error: %s", e.Msg) 42 | } 43 | 44 | // ErrUnsignedMetadata is a subset of ErrRepository 45 | func (e ErrUnsignedMetadata) Is(target error) bool { 46 | return target == ErrRepository{} || target == ErrUnsignedMetadata{} 47 | } 48 | 49 | // ErrBadVersionNumber - An error for metadata that contains an invalid version number 50 | type ErrBadVersionNumber struct { 51 | Msg string 52 | } 53 | 54 | func (e ErrBadVersionNumber) Error() string { 55 | return fmt.Sprintf("bad version number error: %s", e.Msg) 56 | } 57 | 58 | // ErrBadVersionNumber is a subset of ErrRepository 59 | func (e ErrBadVersionNumber) Is(target error) bool { 60 | return target == ErrRepository{} || target == ErrBadVersionNumber{} 61 | } 62 | 63 | // ErrEqualVersionNumber - An error for metadata containing a previously verified version number 64 | type ErrEqualVersionNumber struct { 65 | Msg string 66 | } 67 | 68 | func (e ErrEqualVersionNumber) Error() string { 69 | return fmt.Sprintf("equal version number error: %s", e.Msg) 70 | } 71 | 72 | // ErrEqualVersionNumber is a subset of both ErrRepository and ErrBadVersionNumber 73 | func (e ErrEqualVersionNumber) Is(target error) bool { 74 | return target == ErrRepository{} || target == ErrBadVersionNumber{} || target == ErrEqualVersionNumber{} 75 | } 76 | 77 | // ErrExpiredMetadata - Indicate that a TUF Metadata file has expired 78 | type ErrExpiredMetadata struct { 79 | Msg string 80 | } 81 | 82 | func (e ErrExpiredMetadata) Error() string { 83 | return fmt.Sprintf("expired metadata error: %s", e.Msg) 84 | } 85 | 86 | // ErrExpiredMetadata is a subset of ErrRepository 87 | func (e ErrExpiredMetadata) Is(target error) bool { 88 | return target == ErrRepository{} || target == ErrExpiredMetadata{} 89 | } 90 | 91 | // ErrLengthOrHashMismatch - An error while checking the length and hash values of an object 92 | type ErrLengthOrHashMismatch struct { 93 | Msg string 94 | } 95 | 96 | func (e ErrLengthOrHashMismatch) Error() string { 97 | return fmt.Sprintf("length/hash verification error: %s", e.Msg) 98 | } 99 | 100 | // ErrLengthOrHashMismatch is a subset of ErrRepository 101 | func (e ErrLengthOrHashMismatch) Is(target error) bool { 102 | return target == ErrRepository{} || target == ErrLengthOrHashMismatch{} 103 | } 104 | 105 | // Download errors 106 | 107 | // ErrDownload - An error occurred while attempting to download a file 108 | type ErrDownload struct { 109 | Msg string 110 | } 111 | 112 | func (e ErrDownload) Error() string { 113 | return fmt.Sprintf("download error: %s", e.Msg) 114 | } 115 | 116 | // ErrDownloadLengthMismatch - Indicate that a mismatch of lengths was seen while downloading a file 117 | type ErrDownloadLengthMismatch struct { 118 | Msg string 119 | } 120 | 121 | func (e ErrDownloadLengthMismatch) Error() string { 122 | return fmt.Sprintf("download length mismatch error: %s", e.Msg) 123 | } 124 | 125 | // ErrDownloadLengthMismatch is a subset of ErrDownload 126 | func (e ErrDownloadLengthMismatch) Is(target error) bool { 127 | return target == ErrDownload{} || target == ErrDownloadLengthMismatch{} 128 | } 129 | 130 | // ErrDownloadHTTP - Returned by Fetcher interface implementations for HTTP errors 131 | type ErrDownloadHTTP struct { 132 | StatusCode int 133 | URL string 134 | } 135 | 136 | func (e ErrDownloadHTTP) Error() string { 137 | return fmt.Sprintf("failed to download %s, http status code: %d", e.URL, e.StatusCode) 138 | } 139 | 140 | // ErrDownloadHTTP is a subset of ErrDownload 141 | func (e ErrDownloadHTTP) Is(target error) bool { 142 | return target == ErrDownload{} || target == ErrDownloadHTTP{} 143 | } 144 | 145 | // ValueError 146 | type ErrValue struct { 147 | Msg string 148 | } 149 | 150 | func (e ErrValue) Error() string { 151 | return fmt.Sprintf("value error: %s", e.Msg) 152 | } 153 | 154 | // TypeError 155 | type ErrType struct { 156 | Msg string 157 | } 158 | 159 | func (e ErrType) Error() string { 160 | return fmt.Sprintf("type error: %s", e.Msg) 161 | } 162 | 163 | // RuntimeError 164 | type ErrRuntime struct { 165 | Msg string 166 | } 167 | 168 | func (e ErrRuntime) Error() string { 169 | return fmt.Sprintf("runtime error: %s", e.Msg) 170 | } 171 | -------------------------------------------------------------------------------- /metadata/fetcher/fetcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package fetcher 13 | 14 | import ( 15 | "fmt" 16 | "io" 17 | "net/http" 18 | "strconv" 19 | "time" 20 | 21 | "github.com/rdimitrov/go-tuf-metadata/metadata" 22 | ) 23 | 24 | // Fetcher interface 25 | type Fetcher interface { 26 | DownloadFile(urlPath string, maxLength int64, timeout time.Duration) ([]byte, error) 27 | } 28 | 29 | // DefaultFetcher implements Fetcher 30 | type DefaultFetcher struct { 31 | httpUserAgent string 32 | } 33 | 34 | // DownloadFile downloads a file from urlPath, errors out if it failed, 35 | // its length is larger than maxLength or the timeout is reached. 36 | func (d *DefaultFetcher) DownloadFile(urlPath string, maxLength int64, timeout time.Duration) ([]byte, error) { 37 | client := &http.Client{Timeout: timeout} 38 | req, err := http.NewRequest("GET", urlPath, nil) 39 | if err != nil { 40 | return nil, err 41 | } 42 | // Use in case of multiple sessions. 43 | if d.httpUserAgent != "" { 44 | req.Header.Set("User-Agent", d.httpUserAgent) 45 | } 46 | // Execute the request. 47 | res, err := client.Do(req) 48 | if err != nil { 49 | return nil, err 50 | } 51 | defer res.Body.Close() 52 | // Handle HTTP status codes. 53 | if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusForbidden || res.StatusCode != http.StatusOK { 54 | return nil, metadata.ErrDownloadHTTP{StatusCode: res.StatusCode, URL: urlPath} 55 | } 56 | var length int64 57 | // Get content length from header (might not be accurate, -1 or not set). 58 | if header := res.Header.Get("Content-Length"); header != "" { 59 | length, err = strconv.ParseInt(header, 10, 0) 60 | if err != nil { 61 | return nil, err 62 | } 63 | // Error if the reported size is greater than what is expected. 64 | if length > maxLength { 65 | return nil, metadata.ErrDownloadLengthMismatch{Msg: fmt.Sprintf("download failed for %s, length %d is larger than expected %d", urlPath, length, maxLength)} 66 | } 67 | } 68 | // Although the size has been checked above, use a LimitReader in case 69 | // the reported size is inaccurate, or size is -1 which indicates an 70 | // unknown length. We read maxLength + 1 in order to check if the read data 71 | // surpased our set limit. 72 | data, err := io.ReadAll(io.LimitReader(res.Body, maxLength+1)) 73 | if err != nil { 74 | return nil, err 75 | } 76 | // Error if the reported size is greater than what is expected. 77 | length = int64(len(data)) 78 | if length > maxLength { 79 | return nil, metadata.ErrDownloadLengthMismatch{Msg: fmt.Sprintf("download failed for %s, length %d is larger than expected %d", urlPath, length, maxLength)} 80 | } 81 | 82 | return data, nil 83 | } 84 | -------------------------------------------------------------------------------- /metadata/fetcher/fetcher_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package fetcher 13 | 14 | import ( 15 | "net/url" 16 | "testing" 17 | "time" 18 | 19 | "github.com/rdimitrov/go-tuf-metadata/metadata" 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestDownLoadFile(t *testing.T) { 24 | for _, tt := range []struct { 25 | name string 26 | desc string 27 | url string 28 | maxLength int64 29 | timeout time.Duration 30 | data []byte 31 | wantErr error 32 | }{ 33 | { 34 | name: "success", 35 | desc: "No errors expected", 36 | url: "https://jku.github.io/tuf-demo/metadata/1.root.json", 37 | maxLength: 512000, 38 | timeout: 15 * time.Second, 39 | data: []byte{123, 10, 32, 34, 115, 105, 103, 110, 97, 116, 117, 114, 101, 115, 34, 58, 32, 91, 10, 32, 32, 123, 10, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 52, 99, 53, 54, 100, 101, 53, 98, 54, 50, 102, 100, 48, 54, 52, 102, 99, 57, 52, 52, 53, 51, 98, 54, 56, 48, 100, 102, 100, 51, 102, 97, 102, 54, 97, 48, 49, 98, 97, 97, 97, 98, 51, 98, 101, 97, 99, 50, 57, 54, 57, 50, 48, 102, 48, 99, 99, 102, 97, 50, 50, 55, 55, 53, 34, 44, 10, 32, 32, 32, 34, 115, 105, 103, 34, 58, 32, 34, 57, 54, 57, 98, 100, 101, 99, 51, 54, 100, 54, 102, 51, 100, 99, 53, 57, 99, 49, 55, 50, 48, 50, 97, 56, 53, 50, 56, 98, 98, 51, 53, 54, 97, 54, 101, 97, 53, 52, 100, 55, 99, 99, 57, 54, 98, 98, 51, 55, 49, 101, 101, 101, 52, 56, 101, 50, 52, 48, 49, 57, 50, 98, 99, 97, 99, 100, 53, 48, 53, 49, 51, 56, 56, 50, 52, 53, 49, 52, 52, 97, 97, 99, 97, 49, 48, 51, 57, 100, 51, 101, 98, 55, 48, 54, 50, 101, 48, 56, 55, 54, 55, 57, 53, 101, 56, 49, 101, 49, 100, 53, 54, 54, 102, 56, 100, 101, 100, 50, 99, 50, 56, 52, 97, 101, 101, 48, 102, 34, 10, 32, 32, 125, 44, 10, 32, 32, 123, 10, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 52, 53, 97, 57, 53, 55, 55, 99, 97, 52, 56, 51, 102, 51, 53, 56, 98, 100, 97, 52, 97, 50, 49, 97, 102, 57, 51, 98, 55, 54, 54, 48, 98, 56, 50, 98, 100, 57, 99, 48, 101, 49, 57, 51, 48, 97, 54, 98, 55, 100, 53, 50, 49, 98, 52, 50, 56, 57, 55, 97, 48, 102, 97, 51, 34, 44, 10, 32, 32, 32, 34, 115, 105, 103, 34, 58, 32, 34, 101, 100, 102, 97, 102, 51, 99, 53, 51, 56, 97, 48, 50, 51, 101, 55, 99, 102, 53, 98, 50, 54, 51, 97, 101, 52, 101, 54, 51, 99, 51, 51, 99, 57, 52, 97, 50, 98, 102, 99, 57, 102, 101, 56, 48, 56, 53, 57, 99, 52, 57, 51, 52, 100, 52, 97, 54, 54, 98, 48, 49, 53, 98, 54, 53, 98, 57, 48, 49, 101, 99, 53, 100, 53, 50, 57, 48, 101, 97, 53, 50, 52, 51, 51, 57, 101, 54, 97, 52, 48, 98, 53, 98, 56, 100, 98, 56, 97, 57, 53, 54, 49, 102, 51, 99, 49, 48, 51, 101, 50, 97, 101, 56, 55, 98, 57, 101, 101, 48, 51, 50, 97, 57, 101, 51, 48, 48, 49, 34, 10, 32, 32, 125, 10, 32, 93, 44, 10, 32, 34, 115, 105, 103, 110, 101, 100, 34, 58, 32, 123, 10, 32, 32, 34, 95, 116, 121, 112, 101, 34, 58, 32, 34, 114, 111, 111, 116, 34, 44, 10, 32, 32, 34, 99, 111, 110, 115, 105, 115, 116, 101, 110, 116, 95, 115, 110, 97, 112, 115, 104, 111, 116, 34, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 34, 101, 120, 112, 105, 114, 101, 115, 34, 58, 32, 34, 50, 48, 50, 49, 45, 48, 55, 45, 49, 56, 84, 49, 51, 58, 51, 55, 58, 51, 56, 90, 34, 44, 10, 32, 32, 34, 107, 101, 121, 115, 34, 58, 32, 123, 10, 32, 32, 32, 34, 51, 56, 54, 48, 48, 56, 50, 48, 102, 49, 49, 97, 53, 102, 55, 100, 55, 102, 102, 52, 50, 101, 54, 100, 102, 99, 57, 98, 48, 51, 102, 100, 54, 48, 50, 55, 50, 97, 51, 98, 101, 54, 102, 56, 57, 53, 100, 97, 50, 100, 56, 56, 50, 99, 101, 97, 56, 98, 98, 49, 101, 50, 48, 102, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 51, 56, 54, 48, 48, 56, 50, 48, 102, 49, 49, 97, 53, 102, 55, 100, 55, 102, 102, 52, 50, 101, 54, 100, 102, 99, 57, 98, 48, 51, 102, 100, 54, 48, 50, 55, 50, 97, 51, 98, 101, 54, 102, 56, 57, 53, 100, 97, 50, 100, 56, 56, 50, 99, 101, 97, 56, 98, 98, 49, 101, 50, 48, 102, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 53, 48, 102, 52, 56, 54, 53, 57, 54, 54, 53, 98, 51, 101, 101, 98, 50, 50, 100, 52, 57, 51, 55, 52, 101, 49, 56, 51, 49, 57, 55, 101, 101, 102, 56, 101, 52, 50, 56, 55, 54, 97, 53, 99, 98, 57, 48, 57, 99, 57, 49, 97, 98, 55, 55, 101, 52, 50, 98, 49, 101, 99, 99, 54, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 52, 53, 97, 57, 53, 55, 55, 99, 97, 52, 56, 51, 102, 51, 53, 56, 98, 100, 97, 52, 97, 50, 49, 97, 102, 57, 51, 98, 55, 54, 54, 48, 98, 56, 50, 98, 100, 57, 99, 48, 101, 49, 57, 51, 48, 97, 54, 98, 55, 100, 53, 50, 49, 98, 52, 50, 56, 57, 55, 97, 48, 102, 97, 51, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 52, 53, 97, 57, 53, 55, 55, 99, 97, 52, 56, 51, 102, 51, 53, 56, 98, 100, 97, 52, 97, 50, 49, 97, 102, 57, 51, 98, 55, 54, 54, 48, 98, 56, 50, 98, 100, 57, 99, 48, 101, 49, 57, 51, 48, 97, 54, 98, 55, 100, 53, 50, 49, 98, 52, 50, 56, 57, 55, 97, 48, 102, 97, 51, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 49, 56, 101, 98, 50, 52, 56, 51, 49, 57, 54, 98, 55, 97, 97, 50, 53, 102, 97, 102, 98, 56, 49, 50, 55, 54, 99, 55, 48, 52, 102, 55, 57, 48, 51, 99, 99, 57, 98, 49, 101, 51, 52, 99, 97, 100, 99, 52, 101, 97, 102, 54, 55, 55, 98, 55, 97, 54, 55, 52, 100, 54, 102, 53, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 52, 99, 53, 54, 100, 101, 53, 98, 54, 50, 102, 100, 48, 54, 52, 102, 99, 57, 52, 52, 53, 51, 98, 54, 56, 48, 100, 102, 100, 51, 102, 97, 102, 54, 97, 48, 49, 98, 97, 97, 97, 98, 51, 98, 101, 97, 99, 50, 57, 54, 57, 50, 48, 102, 48, 99, 99, 102, 97, 50, 50, 55, 55, 53, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 52, 99, 53, 54, 100, 101, 53, 98, 54, 50, 102, 100, 48, 54, 52, 102, 99, 57, 52, 52, 53, 51, 98, 54, 56, 48, 100, 102, 100, 51, 102, 97, 102, 54, 97, 48, 49, 98, 97, 97, 97, 98, 51, 98, 101, 97, 99, 50, 57, 54, 57, 50, 48, 102, 48, 99, 99, 102, 97, 50, 50, 55, 55, 53, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 57, 50, 49, 101, 99, 99, 56, 54, 101, 101, 57, 49, 102, 100, 100, 51, 97, 53, 53, 49, 52, 48, 50, 51, 100, 102, 49, 57, 99, 100, 56, 53, 57, 49, 53, 57, 52, 54, 55, 55, 54, 52, 102, 54, 48, 102, 99, 52, 49, 101, 49, 101, 101, 97, 99, 56, 53, 48, 51, 53, 49, 49, 54, 49, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 56, 102, 51, 99, 50, 55, 57, 52, 102, 50, 52, 52, 50, 54, 48, 49, 52, 102, 99, 50, 54, 97, 100, 99, 98, 98, 56, 101, 102, 100, 57, 52, 52, 49, 49, 102, 99, 101, 56, 56, 49, 102, 97, 54, 48, 102, 99, 56, 55, 50, 53, 97, 56, 57, 57, 49, 49, 53, 55, 53, 48, 101, 102, 97, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 56, 102, 51, 99, 50, 55, 57, 52, 102, 50, 52, 52, 50, 54, 48, 49, 52, 102, 99, 50, 54, 97, 100, 99, 98, 98, 56, 101, 102, 100, 57, 52, 52, 49, 49, 102, 99, 101, 56, 56, 49, 102, 97, 54, 48, 102, 99, 56, 55, 50, 53, 97, 56, 57, 57, 49, 49, 53, 55, 53, 48, 101, 102, 97, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 56, 57, 53, 55, 54, 57, 49, 55, 100, 49, 54, 48, 50, 56, 52, 51, 56, 52, 97, 52, 55, 55, 53, 57, 101, 101, 99, 49, 102, 99, 48, 102, 53, 98, 55, 52, 54, 99, 97, 51, 100, 102, 97, 100, 56, 49, 51, 101, 101, 51, 48, 56, 55, 53, 99, 51, 50, 98, 97, 99, 51, 54, 57, 99, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 57, 100, 55, 56, 53, 52, 51, 98, 53, 48, 56, 102, 57, 57, 97, 57, 53, 97, 51, 99, 51, 49, 102, 97, 100, 51, 99, 102, 102, 101, 102, 48, 54, 52, 52, 49, 51, 52, 102, 49, 97, 48, 50, 56, 98, 51, 48, 53, 48, 49, 97, 99, 99, 49, 50, 48, 53, 56, 99, 55, 99, 51, 101, 56, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 57, 100, 55, 56, 53, 52, 51, 98, 53, 48, 56, 102, 57, 57, 97, 57, 53, 97, 51, 99, 51, 49, 102, 97, 100, 51, 99, 102, 102, 101, 102, 48, 54, 52, 52, 49, 51, 52, 102, 49, 97, 48, 50, 56, 98, 51, 48, 53, 48, 49, 97, 99, 99, 49, 50, 48, 53, 56, 99, 55, 99, 51, 101, 56, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 48, 52, 101, 102, 51, 51, 53, 54, 102, 98, 53, 99, 100, 48, 48, 57, 55, 53, 100, 102, 99, 101, 57, 102, 56, 102, 52, 50, 100, 53, 98, 49, 50, 98, 55, 98, 56, 51, 102, 56, 98, 97, 49, 53, 99, 50, 101, 57, 56, 102, 100, 48, 52, 49, 53, 49, 52, 99, 55, 52, 98, 101, 98, 50, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 10, 32, 32, 125, 44, 10, 32, 32, 34, 114, 111, 108, 101, 115, 34, 58, 32, 123, 10, 32, 32, 32, 34, 114, 111, 111, 116, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 34, 52, 53, 97, 57, 53, 55, 55, 99, 97, 52, 56, 51, 102, 51, 53, 56, 98, 100, 97, 52, 97, 50, 49, 97, 102, 57, 51, 98, 55, 54, 54, 48, 98, 56, 50, 98, 100, 57, 99, 48, 101, 49, 57, 51, 48, 97, 54, 98, 55, 100, 53, 50, 49, 98, 52, 50, 56, 57, 55, 97, 48, 102, 97, 51, 34, 44, 10, 32, 32, 32, 32, 32, 34, 52, 99, 53, 54, 100, 101, 53, 98, 54, 50, 102, 100, 48, 54, 52, 102, 99, 57, 52, 52, 53, 51, 98, 54, 56, 48, 100, 102, 100, 51, 102, 97, 102, 54, 97, 48, 49, 98, 97, 97, 97, 98, 51, 98, 101, 97, 99, 50, 57, 54, 57, 50, 48, 102, 48, 99, 99, 102, 97, 50, 50, 55, 55, 53, 34, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 116, 104, 114, 101, 115, 104, 111, 108, 100, 34, 58, 32, 50, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 115, 110, 97, 112, 115, 104, 111, 116, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 34, 57, 100, 55, 56, 53, 52, 51, 98, 53, 48, 56, 102, 57, 57, 97, 57, 53, 97, 51, 99, 51, 49, 102, 97, 100, 51, 99, 102, 102, 101, 102, 48, 54, 52, 52, 49, 51, 52, 102, 49, 97, 48, 50, 56, 98, 51, 48, 53, 48, 49, 97, 99, 99, 49, 50, 48, 53, 56, 99, 55, 99, 51, 101, 56, 34, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 116, 104, 114, 101, 115, 104, 111, 108, 100, 34, 58, 32, 49, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 116, 97, 114, 103, 101, 116, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 34, 56, 102, 51, 99, 50, 55, 57, 52, 102, 50, 52, 52, 50, 54, 48, 49, 52, 102, 99, 50, 54, 97, 100, 99, 98, 98, 56, 101, 102, 100, 57, 52, 52, 49, 49, 102, 99, 101, 56, 56, 49, 102, 97, 54, 48, 102, 99, 56, 55, 50, 53, 97, 56, 57, 57, 49, 49, 53, 55, 53, 48, 101, 102, 97, 34, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 116, 104, 114, 101, 115, 104, 111, 108, 100, 34, 58, 32, 49, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 34, 51, 56, 54, 48, 48, 56, 50, 48, 102, 49, 49, 97, 53, 102, 55, 100, 55, 102, 102, 52, 50, 101, 54, 100, 102, 99, 57, 98, 48, 51, 102, 100, 54, 48, 50, 55, 50, 97, 51, 98, 101, 54, 102, 56, 57, 53, 100, 97, 50, 100, 56, 56, 50, 99, 101, 97, 56, 98, 98, 49, 101, 50, 48, 102, 34, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 116, 104, 114, 101, 115, 104, 111, 108, 100, 34, 58, 32, 49, 10, 32, 32, 32, 125, 10, 32, 32, 125, 44, 10, 32, 32, 34, 115, 112, 101, 99, 95, 118, 101, 114, 115, 105, 111, 110, 34, 58, 32, 34, 49, 46, 48, 46, 49, 57, 34, 44, 10, 32, 32, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 32, 49, 44, 10, 32, 32, 34, 120, 45, 116, 117, 102, 114, 101, 112, 111, 45, 101, 120, 112, 105, 114, 121, 45, 112, 101, 114, 105, 111, 100, 34, 58, 32, 56, 54, 52, 48, 48, 10, 32, 125, 10, 125}, 40 | wantErr: nil, 41 | }, 42 | { 43 | name: "invalid url", 44 | desc: "URL does not exist", 45 | url: "https://somebadtufrepourl.com/metadata/", 46 | data: nil, 47 | wantErr: &url.Error{}, 48 | }, 49 | { 50 | name: "invalid url format", 51 | desc: "URL is malformed", 52 | url: string([]byte{0x7f}), 53 | data: nil, 54 | wantErr: &url.Error{}, 55 | }, 56 | { 57 | name: "invalid path", 58 | desc: "Path does not exist", 59 | url: "https://jku.github.io/tuf-demo/metadata/badPath.json", 60 | data: nil, 61 | wantErr: metadata.ErrDownloadHTTP{}, 62 | }, 63 | { 64 | name: "data too long", 65 | desc: "Returned data is longer than maxLength", 66 | url: "https://jku.github.io/tuf-demo/metadata/1.root.json", 67 | maxLength: 1, 68 | data: nil, 69 | wantErr: metadata.ErrDownloadLengthMismatch{}, 70 | }, 71 | } { 72 | t.Run(tt.name, func(t *testing.T) { 73 | // this will only be printed if run in verbose mode or if test fails 74 | t.Logf("Desc: %s", tt.desc) 75 | // run the function under test 76 | fetcher := DefaultFetcher{httpUserAgent: "Metadata_Unit_Test/1.0"} 77 | data, err := fetcher.DownloadFile(tt.url, tt.maxLength, tt.timeout) 78 | // special case if we expect no error 79 | if tt.wantErr == nil { 80 | assert.NoErrorf(t, err, "expected no error but got %v", err) 81 | return 82 | } 83 | // compare the error and data with our expected error and data 84 | assert.Equal(t, tt.data, data, "fetched data did not match") 85 | assert.IsTypef(t, tt.wantErr, err, "expected %v but got %v", tt.wantErr, err) 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /metadata/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package metadata 13 | 14 | import ( 15 | "crypto" 16 | "crypto/ecdsa" 17 | "crypto/ed25519" 18 | "crypto/rsa" 19 | "crypto/sha256" 20 | "crypto/x509" 21 | "encoding/hex" 22 | "fmt" 23 | 24 | "github.com/secure-systems-lab/go-securesystemslib/cjson" 25 | "github.com/sigstore/sigstore/pkg/cryptoutils" 26 | ) 27 | 28 | const ( 29 | KeyTypeEd25519 = "ed25519" 30 | KeyTypeECDSA_SHA2_P256_COMPAT = "ecdsa-sha2-nistp256" 31 | KeyTypeECDSA_SHA2_P256 = "ecdsa" 32 | KeyTypeRSASSA_PSS_SHA256 = "rsa" 33 | KeySchemeEd25519 = "ed25519" 34 | KeySchemeECDSA_SHA2_P256 = "ecdsa-sha2-nistp256" 35 | KeySchemeRSASSA_PSS_SHA256 = "rsassa-pss-sha256" 36 | ) 37 | 38 | // ToPublicKey generate crypto.PublicKey from metadata type Key 39 | func (k *Key) ToPublicKey() (crypto.PublicKey, error) { 40 | switch k.Type { 41 | case KeyTypeRSASSA_PSS_SHA256: 42 | publicKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(k.Value.PublicKey)) 43 | if err != nil { 44 | return nil, err 45 | } 46 | rsaKey, ok := publicKey.(*rsa.PublicKey) 47 | if !ok { 48 | return nil, fmt.Errorf("invalid rsa public key") 49 | } 50 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357 51 | if _, err := x509.MarshalPKIXPublicKey(rsaKey); err != nil { 52 | return nil, err 53 | } 54 | return rsaKey, nil 55 | 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 56 | publicKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(k.Value.PublicKey)) 57 | if err != nil { 58 | return nil, err 59 | } 60 | ecdsaKey, ok := publicKey.(*ecdsa.PublicKey) 61 | if !ok { 62 | return nil, fmt.Errorf("invalid ecdsa public key") 63 | } 64 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357 65 | if _, err := x509.MarshalPKIXPublicKey(ecdsaKey); err != nil { 66 | return nil, err 67 | } 68 | return ecdsaKey, nil 69 | case KeyTypeEd25519: 70 | publicKey, err := hex.DecodeString(k.Value.PublicKey) 71 | if err != nil { 72 | return nil, err 73 | } 74 | ed25519Key := ed25519.PublicKey(publicKey) 75 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357 76 | if _, err := x509.MarshalPKIXPublicKey(ed25519Key); err != nil { 77 | return nil, err 78 | } 79 | return ed25519Key, nil 80 | } 81 | return nil, fmt.Errorf("unsupported public key type") 82 | } 83 | 84 | // KeyFromPublicKey generate metadata type Key from crypto.PublicKey 85 | func KeyFromPublicKey(k crypto.PublicKey) (*Key, error) { 86 | key := &Key{} 87 | switch k := k.(type) { 88 | case *rsa.PublicKey: 89 | key.Type = KeyTypeRSASSA_PSS_SHA256 90 | key.Scheme = KeySchemeRSASSA_PSS_SHA256 91 | pemKey, err := cryptoutils.MarshalPublicKeyToPEM(k) 92 | if err != nil { 93 | return nil, err 94 | } 95 | key.Value.PublicKey = string(pemKey) 96 | case *ecdsa.PublicKey: 97 | key.Type = KeyTypeECDSA_SHA2_P256 98 | key.Scheme = KeySchemeECDSA_SHA2_P256 99 | pemKey, err := cryptoutils.MarshalPublicKeyToPEM(k) 100 | if err != nil { 101 | return nil, err 102 | } 103 | key.Value.PublicKey = string(pemKey) 104 | case ed25519.PublicKey: 105 | key.Type = KeyTypeEd25519 106 | key.Scheme = KeySchemeEd25519 107 | key.Value.PublicKey = hex.EncodeToString(k) 108 | default: 109 | return nil, fmt.Errorf("unsupported public key type") 110 | } 111 | return key, nil 112 | } 113 | 114 | // ID returns the keyID value for the given Key 115 | func (k *Key) ID() string { 116 | // the identifier is a hexdigest of the SHA-256 hash of the canonical form of the key 117 | if k.id == "" { 118 | data, err := cjson.EncodeCanonical(k) 119 | if err != nil { 120 | panic(fmt.Errorf("error creating key ID: %w", err)) 121 | } 122 | digest := sha256.Sum256(data) 123 | k.id = hex.EncodeToString(digest[:]) 124 | } 125 | return k.id 126 | } 127 | -------------------------------------------------------------------------------- /metadata/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package metadata 13 | 14 | var log Logger = DiscardLogger{} 15 | 16 | // Logger partially implements the go-log/logr's interface: 17 | // https://github.com/go-logr/logr/blob/master/logr.go 18 | type Logger interface { 19 | // Info logs a non-error message with key/value pairs 20 | Info(msg string, kv ...any) 21 | // Error logs an error with a given message and key/value pairs. 22 | Error(err error, msg string, kv ...any) 23 | } 24 | 25 | type DiscardLogger struct{} 26 | 27 | func (d DiscardLogger) Info(msg string, kv ...any) { 28 | } 29 | 30 | func (d DiscardLogger) Error(err error, msg string, kv ...any) { 31 | } 32 | 33 | func SetLogger(logger Logger) { 34 | log = logger 35 | } 36 | 37 | func GetLogger() Logger { 38 | return log 39 | } 40 | -------------------------------------------------------------------------------- /metadata/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package metadata 13 | 14 | import ( 15 | stdlog "log" 16 | "os" 17 | "testing" 18 | 19 | "github.com/go-logr/stdr" 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestSetLogger(t *testing.T) { 24 | // This function is just a simple setter, no need for testing table 25 | testLogger := stdr.New(stdlog.New(os.Stdout, "test", stdlog.LstdFlags)) 26 | SetLogger(testLogger) 27 | assert.Equal(t, testLogger, log, "setting package global logger was unsuccessful") 28 | } 29 | 30 | func TestGetLogger(t *testing.T) { 31 | // This function is just a simple getter, no need for testing table 32 | testLogger := GetLogger() 33 | assert.Equal(t, log, testLogger, "function did not return current logger") 34 | } 35 | -------------------------------------------------------------------------------- /metadata/repository/repository.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package repository 13 | 14 | import ( 15 | "github.com/rdimitrov/go-tuf-metadata/metadata" 16 | ) 17 | 18 | // repositoryType struct for storing metadata 19 | type repositoryType struct { 20 | root *metadata.Metadata[metadata.RootType] 21 | snapshot *metadata.Metadata[metadata.SnapshotType] 22 | timestamp *metadata.Metadata[metadata.TimestampType] 23 | targets map[string]*metadata.Metadata[metadata.TargetsType] 24 | } 25 | 26 | // New creates an empty repository instance 27 | func New() *repositoryType { 28 | return &repositoryType{ 29 | targets: map[string]*metadata.Metadata[metadata.TargetsType]{}, 30 | } 31 | } 32 | 33 | // Root returns metadata of type Root 34 | func (r *repositoryType) Root() *metadata.Metadata[metadata.RootType] { 35 | return r.root 36 | } 37 | 38 | // SetRoot sets metadata of type Root 39 | func (r *repositoryType) SetRoot(meta *metadata.Metadata[metadata.RootType]) { 40 | r.root = meta 41 | } 42 | 43 | // Snapshot returns metadata of type Snapshot 44 | func (r *repositoryType) Snapshot() *metadata.Metadata[metadata.SnapshotType] { 45 | return r.snapshot 46 | } 47 | 48 | // SetSnapshot sets metadata of type Snapshot 49 | func (r *repositoryType) SetSnapshot(meta *metadata.Metadata[metadata.SnapshotType]) { 50 | r.snapshot = meta 51 | } 52 | 53 | // Timestamp returns metadata of type Timestamp 54 | func (r *repositoryType) Timestamp() *metadata.Metadata[metadata.TimestampType] { 55 | return r.timestamp 56 | } 57 | 58 | // SetTimestamp sets metadata of type Timestamp 59 | func (r *repositoryType) SetTimestamp(meta *metadata.Metadata[metadata.TimestampType]) { 60 | r.timestamp = meta 61 | } 62 | 63 | // Targets returns metadata of type Targets 64 | func (r *repositoryType) Targets(name string) *metadata.Metadata[metadata.TargetsType] { 65 | return r.targets[name] 66 | } 67 | 68 | // SetTargets sets metadata of type Targets 69 | func (r *repositoryType) SetTargets(name string, meta *metadata.Metadata[metadata.TargetsType]) { 70 | r.targets[name] = meta 71 | } 72 | -------------------------------------------------------------------------------- /metadata/repository/repository_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package repository 13 | 14 | import ( 15 | "testing" 16 | "time" 17 | 18 | "github.com/rdimitrov/go-tuf-metadata/metadata" 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | func TestNewRepository(t *testing.T) { 23 | repo := New() 24 | 25 | now := time.Now().UTC() 26 | safeExpiry := now.Truncate(time.Second).AddDate(0, 0, 30) 27 | 28 | root := metadata.Root(safeExpiry) 29 | repo.SetRoot(root) 30 | assert.Equal(t, "root", repo.Root().Signed.Type) 31 | assert.Equal(t, int64(1), repo.Root().Signed.Version) 32 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Root().Signed.SpecVersion) 33 | 34 | targets := metadata.Targets(safeExpiry) 35 | repo.SetTargets("targets", targets) 36 | assert.Equal(t, "targets", repo.Targets("targets").Signed.Type) 37 | assert.Equal(t, int64(1), repo.Targets("targets").Signed.Version) 38 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Targets("targets").Signed.SpecVersion) 39 | 40 | timestamp := metadata.Timestamp(safeExpiry) 41 | repo.SetTimestamp(timestamp) 42 | // repo.SetRoot(root) 43 | assert.Equal(t, "timestamp", repo.Timestamp().Signed.Type) 44 | assert.Equal(t, int64(1), repo.Timestamp().Signed.Version) 45 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Timestamp().Signed.SpecVersion) 46 | 47 | snapshot := metadata.Snapshot(safeExpiry) 48 | repo.SetSnapshot(snapshot) 49 | assert.Equal(t, "snapshot", repo.Snapshot().Signed.Type) 50 | assert.Equal(t, int64(1), repo.Snapshot().Signed.Version) 51 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Snapshot().Signed.SpecVersion) 52 | } 53 | -------------------------------------------------------------------------------- /metadata/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package metadata 13 | 14 | import ( 15 | "encoding/json" 16 | "time" 17 | ) 18 | 19 | // Generic type constraint 20 | type Roles interface { 21 | RootType | SnapshotType | TimestampType | TargetsType 22 | } 23 | 24 | // Define version of the TUF specification 25 | const ( 26 | SPECIFICATION_VERSION = "1.0.31" 27 | ) 28 | 29 | // Define top level role names 30 | const ( 31 | ROOT = "root" 32 | SNAPSHOT = "snapshot" 33 | TARGETS = "targets" 34 | TIMESTAMP = "timestamp" 35 | ) 36 | 37 | var TOP_LEVEL_ROLE_NAMES = [...]string{ROOT, TIMESTAMP, SNAPSHOT, TARGETS} 38 | 39 | // Metadata[T Roles] represents a TUF metadata. 40 | // Provides methods to read and write to and 41 | // from file and bytes, also to create, verify and clear metadata signatures. 42 | type Metadata[T Roles] struct { 43 | Signed T `json:"signed"` 44 | Signatures []Signature `json:"signatures"` 45 | UnrecognizedFields map[string]any `json:"-"` 46 | } 47 | 48 | // Signature represents the Signature part of a TUF metadata 49 | type Signature struct { 50 | KeyID string `json:"keyid"` 51 | Signature HexBytes `json:"sig"` 52 | UnrecognizedFields map[string]any `json:"-"` 53 | } 54 | 55 | // RootType represents the Signed portion of a root metadata 56 | type RootType struct { 57 | Type string `json:"_type"` 58 | SpecVersion string `json:"spec_version"` 59 | ConsistentSnapshot bool `json:"consistent_snapshot"` 60 | Version int64 `json:"version"` 61 | Expires time.Time `json:"expires"` 62 | Keys map[string]*Key `json:"keys"` 63 | Roles map[string]*Role `json:"roles"` 64 | UnrecognizedFields map[string]any `json:"-"` 65 | } 66 | 67 | // SnapshotType represents the Signed portion of a snapshot metadata 68 | type SnapshotType struct { 69 | Type string `json:"_type"` 70 | SpecVersion string `json:"spec_version"` 71 | Version int64 `json:"version"` 72 | Expires time.Time `json:"expires"` 73 | Meta map[string]*MetaFiles `json:"meta"` 74 | UnrecognizedFields map[string]any `json:"-"` 75 | } 76 | 77 | // TargetsType represents the Signed portion of a targets metadata 78 | type TargetsType struct { 79 | Type string `json:"_type"` 80 | SpecVersion string `json:"spec_version"` 81 | Version int64 `json:"version"` 82 | Expires time.Time `json:"expires"` 83 | Targets map[string]*TargetFiles `json:"targets"` 84 | Delegations *Delegations `json:"delegations,omitempty"` 85 | UnrecognizedFields map[string]any `json:"-"` 86 | } 87 | 88 | // TimestampType represents the Signed portion of a timestamp metadata 89 | type TimestampType struct { 90 | Type string `json:"_type"` 91 | SpecVersion string `json:"spec_version"` 92 | Version int64 `json:"version"` 93 | Expires time.Time `json:"expires"` 94 | Meta map[string]*MetaFiles `json:"meta"` 95 | UnrecognizedFields map[string]any `json:"-"` 96 | } 97 | 98 | // Key represents a key in TUF 99 | type Key struct { 100 | Type string `json:"keytype"` 101 | Scheme string `json:"scheme"` 102 | Value KeyVal `json:"keyval"` 103 | id string `json:"-"` 104 | UnrecognizedFields map[string]any `json:"-"` 105 | } 106 | 107 | type KeyVal struct { 108 | PublicKey string `json:"public"` 109 | UnrecognizedFields map[string]any `json:"-"` 110 | } 111 | 112 | // Role represents one of the top-level roles in TUF 113 | type Role struct { 114 | KeyIDs []string `json:"keyids"` 115 | Threshold int `json:"threshold"` 116 | UnrecognizedFields map[string]any `json:"-"` 117 | } 118 | 119 | type HexBytes []byte 120 | 121 | type Hashes map[string]HexBytes 122 | 123 | // MetaFiles represents the value portion of METAFILES in TUF (used in Snapshot and Timestamp metadata). Used to store information about a particular meta file. 124 | type MetaFiles struct { 125 | Length int64 `json:"length,omitempty"` 126 | Hashes Hashes `json:"hashes,omitempty"` 127 | Version int64 `json:"version"` 128 | UnrecognizedFields map[string]any `json:"-"` 129 | } 130 | 131 | // TargetFiles represents the value portion of TARGETS in TUF (used Targets metadata). Used to store information about a particular target file. 132 | type TargetFiles struct { 133 | Length int64 `json:"length"` 134 | Hashes Hashes `json:"hashes"` 135 | Custom *json.RawMessage `json:"custom,omitempty"` 136 | Path string `json:"-"` 137 | UnrecognizedFields map[string]any `json:"-"` 138 | } 139 | 140 | // Delegations is an optional object which represents delegation roles and their corresponding keys 141 | type Delegations struct { 142 | Keys map[string]*Key `json:"keys"` 143 | Roles []DelegatedRole `json:"roles,omitempty"` 144 | SuccinctRoles *SuccinctRoles `json:"succinct_roles,omitempty"` 145 | UnrecognizedFields map[string]any `json:"-"` 146 | } 147 | 148 | // DelegatedRole represents a delegated role in TUF 149 | type DelegatedRole struct { 150 | Name string `json:"name"` 151 | KeyIDs []string `json:"keyids"` 152 | Threshold int `json:"threshold"` 153 | Terminating bool `json:"terminating"` 154 | PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"` 155 | Paths []string `json:"paths,omitempty"` 156 | UnrecognizedFields map[string]any `json:"-"` 157 | } 158 | 159 | // SuccinctRoles represents a delegation graph that covers all targets, 160 | // distributing them uniformly over the delegated roles (i.e. bins) in the graph. 161 | type SuccinctRoles struct { 162 | KeyIDs []string `json:"keyids"` 163 | Threshold int `json:"threshold"` 164 | BitLength int `json:"bit_length"` 165 | NamePrefix string `json:"name_prefix"` 166 | UnrecognizedFields map[string]any `json:"-"` 167 | } 168 | -------------------------------------------------------------------------------- /metadata/updater/updater_consistent_snapshot_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package updater 13 | 14 | import ( 15 | "testing" 16 | 17 | "github.com/stretchr/testify/assert" 18 | 19 | "github.com/rdimitrov/go-tuf-metadata/metadata" 20 | simulator "github.com/rdimitrov/go-tuf-metadata/testutils/simulator" 21 | ) 22 | 23 | func TestTopLevelRolesUpdateWithConsistentSnapshotDisabled(t *testing.T) { 24 | // Test if the client fetches and stores metadata files with the 25 | // correct version prefix when ConsistentSnapshot is false 26 | 27 | err := loadOrResetTrustedRootMetadata() 28 | assert.NoError(t, err) 29 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = false 30 | simulator.Sim.MDRoot.Signed.Version += 1 31 | simulator.Sim.PublishRoot() 32 | 33 | updaterConfig, err := loadUpdaterConfig() 34 | assert.NoError(t, err) 35 | updater := initUpdater(updaterConfig) 36 | 37 | // cleanup fetch tracker metadata 38 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{} 39 | err = updater.Refresh() 40 | assert.NoError(t, err) 41 | 42 | // metadata files are fetched with the expected version (or None) 43 | expectedsnapshotEnabled := []simulator.FTMetadata{ 44 | {Name: "root", Value: 2}, 45 | {Name: "root", Value: 3}, 46 | {Name: "timestamp", Value: -1}, 47 | {Name: "snapshot", Value: -1}, 48 | {Name: "targets", Value: -1}, 49 | } 50 | assert.EqualValues(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata) 51 | // metadata files are always persisted without a version prefix 52 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:]) 53 | } 54 | 55 | func TestTopLevelRolesUpdateWithConsistentSnapshotEnabled(t *testing.T) { 56 | // Test if the client fetches and stores metadata files with the 57 | // correct version prefix when ConsistentSnapshot is true 58 | 59 | err := loadOrResetTrustedRootMetadata() 60 | assert.NoError(t, err) 61 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = true 62 | simulator.Sim.MDRoot.Signed.Version += 1 63 | simulator.Sim.PublishRoot() 64 | 65 | updaterConfig, err := loadUpdaterConfig() 66 | assert.NoError(t, err) 67 | updater := initUpdater(updaterConfig) 68 | 69 | // cleanup fetch tracker metadata 70 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{} 71 | err = updater.Refresh() 72 | assert.NoError(t, err) 73 | 74 | // metadata files are fetched with the expected version (or None) 75 | expectedSnapshotDisabled := []simulator.FTMetadata{ 76 | {Name: "root", Value: 2}, 77 | {Name: "root", Value: 3}, 78 | {Name: "timestamp", Value: -1}, 79 | {Name: "snapshot", Value: 1}, 80 | {Name: "targets", Value: 1}, 81 | } 82 | assert.EqualValues(t, expectedSnapshotDisabled, simulator.Sim.FetchTracker.Metadata) 83 | // metadata files are always persisted without a version prefix 84 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:]) 85 | } 86 | 87 | func TestDelegatesRolesUpdateWithConsistentSnapshotDisabled(t *testing.T) { 88 | // Test if the client fetches and stores delegated metadata files with 89 | // the correct version prefix when ConsistentSnapshot is false 90 | 91 | err := loadOrResetTrustedRootMetadata() 92 | assert.NoError(t, err) 93 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = false 94 | simulator.Sim.MDRoot.Signed.Version += 1 95 | simulator.Sim.PublishRoot() 96 | 97 | target := metadata.Targets(simulator.Sim.SafeExpiry) 98 | 99 | delegatedRole := metadata.DelegatedRole{ 100 | Name: "role1", 101 | KeyIDs: []string{}, 102 | Threshold: 1, 103 | Terminating: false, 104 | Paths: []string{"*"}, 105 | } 106 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 107 | 108 | delegatedRole = metadata.DelegatedRole{ 109 | Name: "..", 110 | KeyIDs: []string{}, 111 | Threshold: 1, 112 | Terminating: false, 113 | Paths: []string{"*"}, 114 | } 115 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 116 | 117 | delegatedRole = metadata.DelegatedRole{ 118 | Name: ".", 119 | KeyIDs: []string{}, 120 | Threshold: 1, 121 | Terminating: false, 122 | Paths: []string{"*"}, 123 | } 124 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 125 | 126 | simulator.Sim.UpdateSnapshot() 127 | updaterConfig, err := loadUpdaterConfig() 128 | assert.NoError(t, err) 129 | updater := initUpdater(updaterConfig) 130 | 131 | err = updater.Refresh() 132 | assert.NoError(t, err) 133 | 134 | // cleanup fetch tracker metadata 135 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{} 136 | // trigger updater to fetch the delegated metadata 137 | _, err = updater.GetTargetInfo("anything") 138 | assert.ErrorContains(t, err, "target anything not found") 139 | 140 | // metadata files are fetched with the expected version (or None) 141 | expectedsnapshotEnabled := []simulator.FTMetadata{ 142 | {Name: "role1", Value: -1}, 143 | {Name: "..", Value: -1}, 144 | {Name: ".", Value: -1}, 145 | } 146 | assert.EqualValues(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata) 147 | // metadata files are always persisted without a version prefix 148 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:]) 149 | } 150 | 151 | func TestDelegatesRolesUpdateWithConsistentSnapshotEnabled(t *testing.T) { 152 | // Test if the client fetches and stores delegated metadata files with 153 | // the correct version prefix when ConsistentSnapshot is true 154 | 155 | err := loadOrResetTrustedRootMetadata() 156 | assert.NoError(t, err) 157 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = true 158 | simulator.Sim.MDRoot.Signed.Version += 1 159 | simulator.Sim.PublishRoot() 160 | 161 | target := metadata.Targets(simulator.Sim.SafeExpiry) 162 | 163 | delegatedRole := metadata.DelegatedRole{ 164 | Name: "role1", 165 | KeyIDs: []string{}, 166 | Threshold: 1, 167 | Terminating: false, 168 | Paths: []string{"*"}, 169 | } 170 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 171 | 172 | delegatedRole = metadata.DelegatedRole{ 173 | Name: "..", 174 | KeyIDs: []string{}, 175 | Threshold: 1, 176 | Terminating: false, 177 | Paths: []string{"*"}, 178 | } 179 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 180 | 181 | delegatedRole = metadata.DelegatedRole{ 182 | Name: ".", 183 | KeyIDs: []string{}, 184 | Threshold: 1, 185 | Terminating: false, 186 | Paths: []string{"*"}, 187 | } 188 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed) 189 | 190 | simulator.Sim.UpdateSnapshot() 191 | updaterConfig, err := loadUpdaterConfig() 192 | assert.NoError(t, err) 193 | updater := initUpdater(updaterConfig) 194 | 195 | err = updater.Refresh() 196 | assert.NoError(t, err) 197 | 198 | // cleanup fetch tracker metadata 199 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{} 200 | // trigger updater to fetch the delegated metadata 201 | _, err = updater.GetTargetInfo("anything") 202 | assert.ErrorContains(t, err, "target anything not found") 203 | 204 | // metadata files are fetched with the expected version (or None) 205 | expectedsnapshotEnabled := []simulator.FTMetadata{ 206 | {Name: "role1", Value: 1}, 207 | {Name: "..", Value: 1}, 208 | {Name: ".", Value: 1}, 209 | } 210 | assert.ElementsMatch(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata) 211 | // metadata files are always persisted without a version prefix 212 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:]) 213 | } 214 | -------------------------------------------------------------------------------- /testutils/repository_data/keystore/delegation_key: -------------------------------------------------------------------------------- 1 | 68593a508472ad3007915379e6b1f3c0@@@@100000@@@@615986af4d1ba89aeadc2f489f89b0e8d46da133a6f75c7b162b8f99f63f86ed@@@@8319255f9856c4f40f9d71bc10e79e5d@@@@1dc7b20f1c668a1f544dc39c7a9fcb3c4a4dd34d1cc8c9d8f779bab026cf0b8e0f46e53bc5ed20bf0e5048b94a5d2ea176e79c12bcc7daa65cd55bf810deebeec5bc903ce9e5316d7dbba88f1a2b51d3f9bc782f8fa9b21dff91609ad0260e21a2039223f816d0fe97ace2e204d0025d327b38d27aa6cd87e85aa8883bfcb6d12f93155d72ffd3c7717a0570cf9811eb6d6a340baa0f27433315d83322c685fec02053ff8c173c4ebf91a258e83402f39546821e3352baa7b246e33b2a573a8ff7b289682407abbcb9184249d4304db68d3bf8e124e94377fd62dde5c4f3b7617d483776345154d047d139b1e559351577da315f54e16153c510159e1908231574bcf49c4f96cafe6530e86a09e9eee47bcff78f2fed2984754c895733938999ff085f9e3532d7174fd76dc09921506dd2137e16ec4926998f5d9df8a8ffb3e6649c71bc32571b2e24357739fa1a56be -------------------------------------------------------------------------------- /testutils/repository_data/keystore/delegation_key.pub: -------------------------------------------------------------------------------- 1 | {"keyval": {"public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} -------------------------------------------------------------------------------- /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----- -------------------------------------------------------------------------------- /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----- -------------------------------------------------------------------------------- /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----- -------------------------------------------------------------------------------- /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----- -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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----- -------------------------------------------------------------------------------- /testutils/repository_data/keystore/targets_key.pub: -------------------------------------------------------------------------------- 1 | {"keyval": {"public": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjm6HPktvTGsygQ8Gvmu+zydTNe1zqoxLxV7mVRbmsCI4kn7JTHc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3w8wqv8cuPxbmsFSxMoHfzBBIuJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNMDn+9T55B0WJYT/VPxwIDAQAB"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]} -------------------------------------------------------------------------------- /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----- -------------------------------------------------------------------------------- /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"]} -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /testutils/repository_data/repository/metadata/root.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2", 5 | "sig": "d0283ac0653e324ce132e47a518f8a1539b59430efe5cdec58ec53f824bec28628b57dd5fb2452bde83fc8f5d11ab0b7350a9bbcbefc7acc6c447785545fa1e36f1352c9e20dd1ebcc3ab16a2a7ff702e32e481ceba88e0f348dc2cddd26ca577445d00c7194e8656d901fd2382c479555af93a64eef48cf79cdff6ecdcd7cb7" 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 | } -------------------------------------------------------------------------------- /testutils/repository_data/repository/metadata/snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6", 5 | "sig": "3075fe9ef3008603eb0531500a93101b8f7eb52b07ce63fb71abaffd5eb20784bcab888abfca8041798b13dd35c6e18ff4a64d536161c4d5e7535f006edec3a46c71684a632269222da82d50bf380e20eb477032e45df0b44af9e1dc46f25cd72f9901b4fc41b90869649b6257a66188b61b83c7295baf16f113e9cc4d39b3a6" 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 | } -------------------------------------------------------------------------------- /testutils/repository_data/repository/metadata/targets.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d", 5 | "sig": "80cd125a4b128c9508df8bc6f71ad2ed9896a9e7afccd53fca9e7dbc2f02db69c3ae712234d3730c929d891fa035bdf059736e7debf62cbac6f0e8d22ab0c5de3b3e47b249eb0d41dea66d9fda9588893cde824a95614129263b6fed72fafb21cd7114e603fe3a30e3871e9eb5b5029e3e9a8353190f1bcb332a81ec211a93eb" 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 | } -------------------------------------------------------------------------------- /testutils/repository_data/repository/metadata/timestamp.json: -------------------------------------------------------------------------------- 1 | { 2 | "signatures": [ 3 | { 4 | "keyid": "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946", 5 | "sig": "639c9ce3dbb705265b5e9ad6d67fea2b38780c48ff7917e372adace8e50a7a2f054383d5960457a113059be521b8ce7e6d8a5787c600c4850b8c0ed1ae17a931a6bfe794476e7824c6f53df5232561e0a2e146b11dde7889b397c6f8136e2105bbb21b4b59b5addc032a0e755d97e531255f3b458d474184168541e542626e81" 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 | } -------------------------------------------------------------------------------- /testutils/repository_data/repository/targets/file1.txt: -------------------------------------------------------------------------------- 1 | This is an example target file. -------------------------------------------------------------------------------- /testutils/repository_data/repository/targets/file2.txt: -------------------------------------------------------------------------------- 1 | This is an another example target file. -------------------------------------------------------------------------------- /testutils/repository_data/repository/targets/file3.txt: -------------------------------------------------------------------------------- 1 | This is role1's target file. -------------------------------------------------------------------------------- /testutils/simulator/repository_simulator_setup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package simulator 13 | 14 | import ( 15 | "os" 16 | "time" 17 | 18 | log "github.com/sirupsen/logrus" 19 | ) 20 | 21 | var ( 22 | MetadataURL = "https://jku.github.io/tuf-demo/metadata" 23 | TargetsURL = "https://jku.github.io/tuf-demo/targets" 24 | 25 | MetadataDir string 26 | RootBytes []byte 27 | PastDateTime time.Time 28 | Sim *RepositorySimulator 29 | 30 | metadataPath = "/metadata" 31 | targetsPath = "/targets" 32 | LocalDir string 33 | DumpDir string 34 | ) 35 | 36 | func InitLocalEnv() error { 37 | 38 | tmp := os.TempDir() 39 | 40 | tmpDir, err := os.MkdirTemp(tmp, "0750") 41 | if err != nil { 42 | log.Fatal("failed to create temporary directory: ", err) 43 | } 44 | 45 | err = os.Mkdir(tmpDir+metadataPath, 0750) 46 | if err != nil { 47 | log.Debugf("repository simulator: failed to create dir: %v", err) 48 | } 49 | err = os.Mkdir(tmpDir+targetsPath, 0750) 50 | if err != nil { 51 | log.Debugf("repository simulator: failed to create dir: %v", err) 52 | } 53 | LocalDir = tmpDir 54 | return nil 55 | } 56 | 57 | func InitMetadataDir() (*RepositorySimulator, string, string, error) { 58 | err := InitLocalEnv() 59 | if err != nil { 60 | log.Fatal("failed to initialize environment: ", err) 61 | } 62 | metadataDir := LocalDir + metadataPath 63 | 64 | sim := NewRepository() 65 | 66 | f, err := os.Create(metadataDir + "/root.json") 67 | if err != nil { 68 | log.Fatalf("failed to create root: %v", err) 69 | } 70 | 71 | _, err = f.Write(sim.SignedRoots[0]) 72 | if err != nil { 73 | log.Debugf("repository simulator setup: failed to write signed roots: %v", err) 74 | } 75 | targetsDir := LocalDir + targetsPath 76 | sim.LocalDir = LocalDir 77 | return sim, metadataDir, targetsDir, err 78 | } 79 | 80 | func GetRootBytes(localMetadataDir string) ([]byte, error) { 81 | return os.ReadFile(localMetadataDir + "/root.json") 82 | } 83 | 84 | func RepositoryCleanup(tmpDir string) { 85 | log.Printf("Cleaning temporary directory: %s\n", tmpDir) 86 | os.RemoveAll(tmpDir) 87 | } 88 | -------------------------------------------------------------------------------- /testutils/testutils/setup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 VMware, Inc. 2 | // 3 | // This product is licensed to you under the BSD-2 license (the "License"). 4 | // You may not use this product except in compliance with the BSD-2 License. 5 | // This product may include a number of subcomponents with separate copyright 6 | // notices and license terms. Your use of these subcomponents is subject to 7 | // the terms and conditions of the subcomponent's license, as noted in the 8 | // LICENSE file. 9 | // 10 | // SPDX-License-Identifier: BSD-2-Clause 11 | 12 | package testutils 13 | 14 | import ( 15 | "fmt" 16 | "log" 17 | "os" 18 | "path/filepath" 19 | ) 20 | 21 | var ( 22 | TempDir string 23 | RepoDir string 24 | TargetsDir string 25 | KeystoreDir string 26 | ) 27 | 28 | func SetupTestDirs(repoPath string, targetsPath string, keystorePath string) error { 29 | tmp := os.TempDir() 30 | var err error 31 | TempDir, err = os.MkdirTemp(tmp, "0750") 32 | if err != nil { 33 | return fmt.Errorf("failed to create temporary directory: %w", err) 34 | } 35 | 36 | RepoDir = fmt.Sprintf("%s/repository_data/repository", TempDir) 37 | absPath, err := filepath.Abs(repoPath) 38 | if err != nil { 39 | return fmt.Errorf("failed to get absolute path: %w", err) 40 | } 41 | err = Copy(absPath, RepoDir) 42 | if err != nil { 43 | return fmt.Errorf("failed to copy metadata to %s: %w", RepoDir, err) 44 | } 45 | 46 | TargetsDir = fmt.Sprintf("%s/repository_data/repository/targets", TempDir) 47 | targetsAbsPath, err := filepath.Abs(targetsPath) 48 | if err != nil { 49 | return fmt.Errorf("failed to get absolute targets path: %w", err) 50 | } 51 | err = Copy(targetsAbsPath, TargetsDir) 52 | if err != nil { 53 | return fmt.Errorf("failed to copy metadata to %s: %w", RepoDir, err) 54 | } 55 | 56 | KeystoreDir = fmt.Sprintf("%s/keystore", TempDir) 57 | err = os.Mkdir(KeystoreDir, 0750) 58 | if err != nil { 59 | return fmt.Errorf("failed to create keystore dir %s: %w", KeystoreDir, err) 60 | } 61 | absPath, err = filepath.Abs(keystorePath) 62 | if err != nil { 63 | return fmt.Errorf("failed to get absolute path: %w", err) 64 | } 65 | err = Copy(absPath, KeystoreDir) 66 | if err != nil { 67 | return fmt.Errorf("failed to copy keystore to %s: %w", KeystoreDir, err) 68 | } 69 | 70 | return nil 71 | } 72 | 73 | func Copy(fromPath string, toPath string) error { 74 | err := os.MkdirAll(toPath, 0750) 75 | if err != nil { 76 | return fmt.Errorf("failed to create directory %s: %w", toPath, err) 77 | } 78 | files, err := os.ReadDir(fromPath) 79 | if err != nil { 80 | return fmt.Errorf("failed to read path %s: %w", fromPath, err) 81 | } 82 | for _, file := range files { 83 | data, err := os.ReadFile(fmt.Sprintf("%s/%s", fromPath, file.Name())) 84 | if err != nil { 85 | return fmt.Errorf("failed to read file %s: %w", file.Name(), err) 86 | } 87 | filePath := fmt.Sprintf("%s/%s", toPath, file.Name()) 88 | err = os.WriteFile(filePath, data, 0750) 89 | if err != nil { 90 | return fmt.Errorf("failed to write file %s: %w", filePath, err) 91 | } 92 | } 93 | return nil 94 | } 95 | 96 | func Cleanup() { 97 | log.Printf("cleaning temporary directory: %s\n", TempDir) 98 | err := os.RemoveAll(TempDir) 99 | if err != nil { 100 | log.Fatalf("failed to cleanup test directories: %v", err) 101 | } 102 | } 103 | --------------------------------------------------------------------------------