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