├── .github
├── codecov.yml
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ ├── examples.yml
│ ├── linting.yml
│ └── tests.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yaml
├── .minder.yaml
├── CODE-OF-CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── NOTICE
├── README.md
├── examples
├── README.md
├── cli
│ ├── README.md
│ ├── tuf-client
│ │ ├── README.md
│ │ ├── cmd
│ │ │ ├── get.go
│ │ │ ├── init.go
│ │ │ ├── reset.go
│ │ │ └── root.go
│ │ └── main.go
│ └── tuf
│ │ ├── README.md
│ │ ├── cmd
│ │ ├── init.go
│ │ └── root.go
│ │ └── main.go
├── client
│ └── client_example.go
├── multirepo
│ ├── client
│ │ ├── README.md
│ │ ├── client_example.go
│ │ └── root.json
│ └── repository
│ │ ├── README.md
│ │ ├── generate_metadata.go
│ │ ├── metadata
│ │ ├── 1.root.json
│ │ ├── 1.snapshot.json
│ │ ├── 1.targets.json
│ │ └── timestamp.json
│ │ └── targets
│ │ ├── 9bb72b3c684f4e489e7fdce1afabff64b4e3f4b7877ac8cb78a684c2c65fc6d8.map.json
│ │ ├── map.json
│ │ ├── sigstore-tuf-root
│ │ ├── e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json
│ │ └── root.json
│ │ └── staging
│ │ ├── e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json
│ │ └── root.json
└── repository
│ └── basic_repository.go
├── go.mod
├── go.sum
├── metadata
├── config
│ ├── config.go
│ └── config_test.go
├── errors.go
├── fetcher
│ ├── fetcher.go
│ └── fetcher_test.go
├── keys.go
├── logger.go
├── logger_test.go
├── marshal.go
├── metadata.go
├── metadata_api_test.go
├── metadata_test.go
├── multirepo
│ └── multirepo.go
├── repository
│ ├── repository.go
│ └── repository_test.go
├── trustedmetadata
│ ├── trustedmetadata.go
│ └── trustedmetadata_test.go
├── types.go
└── updater
│ ├── updater.go
│ ├── updater_consistent_snapshot_test.go
│ └── updater_top_level_update_test.go
└── testutils
├── repository_data
├── keystore
│ ├── delegation_key
│ ├── delegation_key.pub
│ ├── root_key
│ ├── root_key.pub
│ ├── root_key2
│ ├── root_key2.pub
│ ├── snapshot_key
│ ├── snapshot_key.pub
│ ├── targets_key
│ ├── targets_key.pub
│ ├── timestamp_key
│ └── timestamp_key.pub
└── repository
│ ├── metadata
│ ├── 1.root.json
│ ├── role1.json
│ ├── role2.json
│ ├── root.json
│ ├── snapshot.json
│ ├── targets.json
│ └── timestamp.json
│ └── targets
│ ├── file1.txt
│ ├── file2.txt
│ └── file3.txt
├── simulator
├── repository_simulator.go
└── repository_simulator_setup.go
└── testutils
└── setup.go
/.github/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | default:
5 | # basic
6 | target: auto
7 | threshold: 5%
8 | patch: off
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | # Monitor Go dependencies
9 | - package-ecosystem: "gomod"
10 | directory: "/"
11 | schedule:
12 | interval: "weekly"
13 | time: "10:00"
14 | commit-message:
15 | prefix: "chore"
16 | include: "scope"
17 | open-pull-requests-limit: 10
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2023 VMware, Inc.
2 | #
3 | # This product is licensed to you under the BSD-2 license (the "License").
4 | # You may not use this product except in compliance with the BSD-2 License.
5 | # This product may include a number of subcomponents with separate copyright
6 | # notices and license terms. Your use of these subcomponents is subject to
7 | # the terms and conditions of the subcomponent's license, as noted in the
8 | # LICENSE file.
9 | #
10 | # SPDX-License-Identifier: BSD-2-Clause
11 | on:
12 | pull_request:
13 | push:
14 | branches:
15 | - "main"
16 | tags:
17 | - "v*"
18 | name: CI
19 | jobs:
20 | linting:
21 | uses: ./.github/workflows/linting.yml
22 | tests:
23 | uses: ./.github/workflows/tests.yml
24 | examples:
25 | uses: ./.github/workflows/examples.yml
26 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [main]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [main]
20 | schedule:
21 | - cron: "25 14 * * 6"
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: ["go"]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
41 | - uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f
42 | with:
43 | go-version-file: 'go.mod'
44 | cache: false
45 | # Initializes the CodeQL tools for scanning.
46 | - name: Initialize CodeQL
47 | uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99
48 | with:
49 | languages: ${{ matrix.language }}
50 | # If you wish to specify custom queries, you can do so here or in a config file.
51 | # By default, queries listed here will override any specified in a config file.
52 | # Prefix the list here with "+" to use these queries and those in the config file.
53 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
54 |
55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
56 | # If this step fails, then you should remove it and run the build manually (see below)
57 | - name: Autobuild
58 | uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99
59 |
60 | # ℹ️ Command-line programs to run using the OS shell.
61 | # 📚 https://git.io/JvXDl
62 |
63 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
64 | # and modify them (or add more) to build your code if your project
65 | # uses a compiled language
66 |
67 | #- run: |
68 | # make bootstrap
69 | # make release
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99
73 |
--------------------------------------------------------------------------------
/.github/workflows/examples.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2023 VMware, Inc.
2 | #
3 | # This product is licensed to you under the BSD-2 license (the "License").
4 | # You may not use this product except in compliance with the BSD-2 License.
5 | # This product may include a number of subcomponents with separate copyright
6 | # notices and license terms. Your use of these subcomponents is subject to
7 | # the terms and conditions of the subcomponent's license, as noted in the
8 | # LICENSE file.
9 | #
10 | # SPDX-License-Identifier: BSD-2-Clause
11 | on:
12 | workflow_call:
13 | name: Examples # not exactly right to test functionality in such a way but it does act as a set of end to end test cases for the time being, nevertheless should be updated
14 | jobs:
15 | get-go-versions:
16 | name: Collect available Go versions
17 | runs-on: ubuntu-latest
18 | outputs:
19 | matrix: ${{ steps.versions.outputs.matrix }}
20 | steps:
21 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
22 | - uses: arnested/go-version-action@b556f8d91b644164318c709d28b9083eaf0c064d
23 | id: versions
24 | client:
25 | strategy:
26 | fail-fast: false # Keep running if one leg fails.
27 | matrix:
28 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources
29 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }}
30 | runs-on: ${{ matrix.os }}
31 | needs: get-go-versions
32 | steps:
33 | - name: Checkout code
34 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
35 | - name: Setup - Go ${{ matrix.go-version }}
36 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f
37 | with:
38 | go-version: ${{ matrix.go-version }}
39 | - run: make example-client
40 | repository:
41 | strategy:
42 | fail-fast: false # Keep running if one leg fails.
43 | matrix:
44 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources
45 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }}
46 | runs-on: ${{ matrix.os }}
47 | needs: get-go-versions
48 | steps:
49 | - name: Checkout code
50 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
51 | - name: Setup - Go ${{ matrix.go-version }}
52 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f
53 | with:
54 | go-version: ${{ matrix.go-version }}
55 | - run: make example-repository
56 | multirepo:
57 | strategy:
58 | fail-fast: false # Keep running if one leg fails.
59 | matrix:
60 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources
61 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }}
62 | runs-on: ${{ matrix.os }}
63 | needs: get-go-versions
64 | steps:
65 | - name: Checkout code
66 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
67 | - name: Setup - Go ${{ matrix.go-version }}
68 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f
69 | with:
70 | go-version: ${{ matrix.go-version }}
71 | - run: make example-multirepo
72 | tuf-client-cli:
73 | strategy:
74 | fail-fast: false # Keep running if one leg fails.
75 | matrix:
76 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources
77 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }}
78 | runs-on: ${{ matrix.os }}
79 | needs: get-go-versions
80 | steps:
81 | - name: Checkout code
82 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
83 | - name: Setup - Go ${{ matrix.go-version }}
84 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f
85 | with:
86 | go-version: ${{ matrix.go-version }}
87 | - run: make example-tuf-client-cli
88 | root-signing:
89 | strategy:
90 | fail-fast: false # Keep running if one leg fails.
91 | matrix:
92 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources
93 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }}
94 | runs-on: ${{ matrix.os }}
95 | needs: get-go-versions
96 | steps:
97 | - name: Checkout code
98 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
99 | - name: Setup - Go ${{ matrix.go-version }}
100 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f
101 | with:
102 | go-version: ${{ matrix.go-version }}
103 | - run: make example-root-signing
104 |
--------------------------------------------------------------------------------
/.github/workflows/linting.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2023 VMware, Inc.
2 | #
3 | # This product is licensed to you under the BSD-2 license (the "License").
4 | # You may not use this product except in compliance with the BSD-2 License.
5 | # This product may include a number of subcomponents with separate copyright
6 | # notices and license terms. Your use of these subcomponents is subject to
7 | # the terms and conditions of the subcomponent's license, as noted in the
8 | # LICENSE file.
9 | #
10 | # SPDX-License-Identifier: BSD-2-Clause
11 | on:
12 | workflow_call:
13 | name: Linting
14 | jobs:
15 | govulncheck_job:
16 | runs-on: ubuntu-latest
17 | name: govulncheck
18 | steps:
19 | - id: govulncheck
20 | uses: golang/govulncheck-action@7da72f730e37eeaad891fcff0a532d27ed737cd4
21 | with:
22 | go-version-file: 'go.mod'
23 | go-package: ./...
24 | golangci:
25 | name: golangci-lint
26 | runs-on: ubuntu-latest
27 | steps:
28 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
29 | - uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f
30 | with:
31 | go-version-file: 'go.mod'
32 | cache: false
33 | - name: golangci-lint
34 | uses: golangci/golangci-lint-action@v3
35 | with:
36 | # Require: The version of golangci-lint to use.
37 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
38 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
39 | version: v1.54
40 | args: --timeout 5m --verbose
41 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2023 VMware, Inc.
2 | #
3 | # This product is licensed to you under the BSD-2 license (the "License").
4 | # You may not use this product except in compliance with the BSD-2 License.
5 | # This product may include a number of subcomponents with separate copyright
6 | # notices and license terms. Your use of these subcomponents is subject to
7 | # the terms and conditions of the subcomponent's license, as noted in the
8 | # LICENSE file.
9 | #
10 | # SPDX-License-Identifier: BSD-2-Clause
11 | on:
12 | workflow_call:
13 | name: Tests
14 | jobs:
15 | get-go-versions:
16 | name: Collect available Go versions
17 | runs-on: ubuntu-latest
18 | outputs:
19 | matrix: ${{ steps.versions.outputs.matrix }}
20 | steps:
21 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
22 | - uses: arnested/go-version-action@b556f8d91b644164318c709d28b9083eaf0c064d
23 | id: versions
24 | run:
25 | name: Run
26 | strategy:
27 | fail-fast: false # Keep running if one leg fails.
28 | matrix:
29 | os: [ubuntu-latest] # , macos-latest, windows-latest] Enable later so we don't waste github actions resources
30 | go-version: ${{ fromJSON(needs.get-go-versions.outputs.matrix) }}
31 | runs-on: ${{ matrix.os }}
32 | needs: get-go-versions
33 | steps:
34 | - name: Checkout code
35 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
36 |
37 | - name: Setup - Go ${{ matrix.go-version }}
38 | uses: actions/setup-go@c4a742cab115ed795e34d4513e2cf7d472deb55f
39 | with:
40 | go-version: ${{ matrix.go-version }}
41 |
42 | - name: Run tests
43 | run: go test -race -covermode=atomic -coverpkg=./metadata/... -coverprofile=coverage.out ./...
44 |
45 | - name: Send coverage
46 | uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d
47 | with:
48 | flags: Go-${{ matrix.go-version }}
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | dist/
3 | .idea/
4 | *~
5 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2022-2023 VMware, Inc.
2 | #
3 | # This product is licensed to you under the BSD-2 license (the "License").
4 | # You may not use this product except in compliance with the BSD-2 License.
5 | # This product may include a number of subcomponents with separate copyright
6 | # notices and license terms. Your use of these subcomponents is subject to
7 | # the terms and conditions of the subcomponent's license, as noted in the
8 | # LICENSE file.
9 | #
10 | # SPDX-License-Identifier: BSD-2-Clause
11 | run:
12 | linters:
13 | enable:
14 | - gofmt
15 | - bodyclose
16 | - contextcheck
17 | - errname
18 | - gocyclo
19 | - godot
20 | - godox
21 | - misspell
22 | - stylecheck
23 | - whitespace
24 | - gocritic
25 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | # This is an example .goreleaser.yml file with some sensible defaults.
2 | # Make sure to check the documentation at https://goreleaser.com
3 | before:
4 | hooks:
5 | # You may remove this if you don't use go modules.
6 | - go mod tidy
7 | # you may remove this if you don't need go generate
8 | - go generate ./...
9 | builds:
10 | - skip: true
11 | # - env:
12 | # - CGO_ENABLED=0
13 | # goos:
14 | # - linux
15 | # - windows
16 | # - darwin
17 |
18 | archives:
19 | - format: tar.gz
20 | # this name template makes the OS and Arch compatible with the results of uname.
21 | name_template: >-
22 | {{ .ProjectName }}_
23 | {{- title .Os }}_
24 | {{- if eq .Arch "amd64" }}x86_64
25 | {{- else if eq .Arch "386" }}i386
26 | {{- else }}{{ .Arch }}{{ end }}
27 | {{- if .Arm }}v{{ .Arm }}{{ end }}
28 | # use zip for windows archives
29 | format_overrides:
30 | - goos: windows
31 | format: zip
32 | checksum:
33 | name_template: "checksums.txt"
34 | snapshot:
35 | name_template: "{{ incpatch .Version }}-next"
36 | source:
37 | enabled: true
38 | changelog:
39 | use: github
40 | groups:
41 | - title: "Breaking changes"
42 | regexp: "^.*(?:BREAKING CHANGE)|![(\\w)]*:+.*$"
43 | order: 0
44 | - title: Features
45 | regexp: "^.*feat[(\\w)]*:+.*$"
46 | order: 1
47 | - title: "Bug fixes"
48 | regexp: "^.*fix[(\\w)]*:+.*$"
49 | order: 2
50 | - title: Others
51 | order: 999
52 | release:
53 | # If set to auto, will mark the release as not ready for production
54 | # in case there is an indicator for this in the tag e.g. v1.0.0-rc1
55 | prerelease: auto
56 |
--------------------------------------------------------------------------------
/.minder.yaml:
--------------------------------------------------------------------------------
1 | # This is the Minder profile file used for securely monitoring rdimitrov/go-tuf-metadata.
2 | # For more information, see https://github.com/stacklok/minder.
3 | ---
4 | version: v1
5 | type: profile
6 | name: go-tuf-metadata
7 | context:
8 | provider: github
9 | alert: "on"
10 | remediate: "on"
11 | repository:
12 | - type: secret_scanning
13 | def:
14 | enabled: true
15 | - type: secret_push_protection
16 | def:
17 | enabled: true
18 | - type: github_actions_allowed
19 | def:
20 | allowed_actions: all
21 | # - type: allowed_selected_actions
22 | # def:
23 | # github_owned_allowed: true
24 | # verified_allowed: true
25 | # patterns_allowed: []
26 | - type: default_workflow_permissions
27 | def:
28 | default_workflow_permissions: write
29 | can_approve_pull_request_reviews: true
30 | - type: codeql_enabled
31 | def:
32 | languages: [go]
33 | schedule_interval: '30 4-6 * * *'
34 | - type: actions_check_pinned_tags
35 | def: {}
36 | - type: dependabot_configured
37 | def:
38 | package_ecosystem: gomod
39 | schedule_interval: weekly
40 | apply_if_file: go.mod
41 | - type: dockerfile_no_latest_tag
42 | def: {}
43 | # - type: trivy_action_enabled
44 | # def: {}
45 | - type: branch_protection_enabled
46 | params:
47 | branch: main
48 | def: {}
49 | - type: branch_protection_allow_deletions
50 | params:
51 | branch: main
52 | def:
53 | allow_deletions: false
54 | - type: branch_protection_allow_force_pushes
55 | params:
56 | branch: main
57 | def:
58 | allow_force_pushes: true
59 | # - type: branch_protection_enforce_admins
60 | # params:
61 | # branch: main
62 | # def:
63 | # enforce_admins: true
64 | - type: branch_protection_lock_branch
65 | params:
66 | branch: main
67 | def:
68 | lock_branch: false
69 | - type: branch_protection_require_conversation_resolution
70 | params:
71 | branch: main
72 | def:
73 | required_conversation_resolution: true
74 | - type: branch_protection_require_linear_history
75 | params:
76 | branch: main
77 | def:
78 | required_linear_history: true
79 | - type: branch_protection_require_pull_request_approving_review_count
80 | params:
81 | branch: main
82 | def:
83 | required_approving_review_count: 1
84 | - type: branch_protection_require_pull_request_code_owners_review
85 | params:
86 | branch: main
87 | def:
88 | require_code_owner_reviews: true
89 | - type: branch_protection_require_pull_request_dismiss_stale_reviews
90 | params:
91 | branch: main
92 | def:
93 | dismiss_stale_reviews: true
94 | - type: branch_protection_require_pull_request_last_push_approval
95 | params:
96 | branch: main
97 | def:
98 | require_last_push_approval: true
99 | - type: branch_protection_require_pull_requests
100 | params:
101 | branch: main
102 | def:
103 | required_pull_request_reviews: true
104 | - type: branch_protection_require_signatures
105 | params:
106 | branch: main
107 | def:
108 | required_signatures: false
109 | - type: license
110 | def:
111 | license_filename: LICENSE
112 | license_type: "BSD-2-Clause"
113 | # artifact:
114 | # - type: artifact_signature
115 | # params:
116 | # tags: [main]
117 | # name: test
118 | # def:
119 | # is_signed: true
120 | # is_verified: true
121 | # is_bundle_verified: true
122 | pull_request:
123 | - type: pr_vulnerability_check
124 | def:
125 | action: review
126 | ecosystem_config:
127 | - name: go
128 | vulnerability_database_type: osv
129 | vulnerability_database_endpoint: https://vuln.go.dev
130 | package_repository:
131 | url: https://proxy.golang.org
132 | sum_repository:
133 | url: https://sum.golang.org
134 |
--------------------------------------------------------------------------------
/CODE-OF-CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at oss-coc@vmware.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Developer Certificate of Origin
4 |
5 | Before you start working with this project, please read our [Developer Certificate of Origin](https://cla.vmware.com/dco). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch.
6 |
7 | ## Contribution Process
8 |
9 | * Follow the [GitHub process](https://help.github.com/articles/fork-a-repo)
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2022-2023 VMware, Inc.
2 |
3 | The BSD-2-Clause license (the "License") set forth below applies to all parts of the go-tuf-metadata project. You may not use this file except in compliance with the License.
4 |
5 | BSD-2-Clause License
6 |
7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
8 |
9 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
10 |
11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2023 VMware, Inc.
2 | #
3 | # This product is licensed to you under the BSD-2 license (the "License").
4 | # You may not use this product except in compliance with the BSD-2 License.
5 | # This product may include a number of subcomponents with separate copyright
6 | # notices and license terms. Your use of these subcomponents is subject to
7 | # the terms and conditions of the subcomponent's license, as noted in the
8 | # LICENSE file.
9 | #
10 | # SPDX-License-Identifier: BSD-2-Clause
11 |
12 | # We want to use bash
13 | SHELL:=/bin/bash
14 |
15 | # Set environment variables
16 | CLIS:=tuf-client # tuf
17 |
18 | # Default target
19 | .PHONY: default
20 | default: build
21 |
22 | #####################
23 | # build section
24 | #####################
25 |
26 | # Build
27 | .PHONY: build
28 | build: $(addprefix build-, $(CLIS))
29 |
30 | # Target for building a Go binary
31 | .PHONY: build-%
32 | build-%:
33 | @echo "Building $*"
34 | @go build -o $* examples/cli/$*/main.go
35 |
36 | #####################
37 | # test section
38 | #####################
39 |
40 | # Test target
41 | .PHONY: test
42 | test:
43 | go test -race -covermode atomic ./...
44 |
45 | #####################
46 | # lint section
47 | #####################
48 |
49 | .PHONY: lint
50 | lint:
51 | golangci-lint run
52 |
53 | .PHONY: fmt
54 | fmt:
55 | go fmt ./...
56 |
57 | #####################
58 | # examples section
59 | #####################
60 |
61 | # Target for running all examples
62 | .PHONY: example-all
63 | example-all: example-client example-repository example-multirepo example-tuf-client-cli example-root-signing
64 |
65 | # Target for demoing the examples/client/client_example.go
66 | .PHONY: example-client
67 | example-client:
68 | @echo "Executing the following example - client/client_example.go"
69 | @cd examples/client/ && go run .
70 |
71 | # Target for demoing the examples/repository/basic_repository.go
72 | .PHONY: example-repository
73 | example-repository:
74 | @echo "Executing the following example - repository/basic_repository.go"
75 | @cd examples/repository/ && go run .
76 |
77 | # Target for demoing the examples/multirepo/client/client_example.go
78 | .PHONY: example-multirepo
79 | example-multirepo:
80 | @echo "Executing the following example - multirepo/client/client_example.go"
81 | @cd examples/multirepo/client/ && go run .
82 |
83 | # Target for demoing the tuf-client cli
84 | .PHONY: example-tuf-client-cli
85 | example-tuf-client-cli: build-tuf-client
86 | @echo "Clearing any leftover artifacts..."
87 | ./tuf-client reset --force
88 | @echo "Initializing the following https://jku.github.io/tuf-demo/ TUF repository"
89 | @sleep 2
90 | ./tuf-client init --url https://jku.github.io/tuf-demo/metadata
91 | @echo "Downloading the following target file - rdimitrov/artifact-example.md"
92 | @sleep 2
93 | ./tuf-client get --url https://jku.github.io/tuf-demo/metadata --turl https://jku.github.io/tuf-demo/targets rdimitrov/artifact-example.md
94 |
95 | # Target for demoing the tuf-client cli with root-signing repo
96 | .PHONY: example-root-signing
97 | example-root-signing: build-tuf-client
98 | @echo "Clearing any leftover artifacts..."
99 | ./tuf-client reset --force
100 | @echo "Downloading the initial root of trust"
101 | @curl -L "https://tuf-repo-cdn.sigstore.dev/5.root.json" > root.json
102 | @echo "Initializing the following https://tuf-repo-cdn.sigstore.dev TUF repository"
103 | @sleep 2
104 | ./tuf-client init --url https://tuf-repo-cdn.sigstore.dev --file root.json
105 | @echo "Downloading the following target file - rekor.pub"
106 | @sleep 2
107 | ./tuf-client get --url https://tuf-repo-cdn.sigstore.dev --turl https://tuf-repo-cdn.sigstore.dev/targets rekor.pub
108 |
109 | # Clean target
110 | .PHONY: clean
111 | clean:
112 | @rm -rf examples/multirepo/client/bootstrap/
113 | @rm -rf examples/multirepo/client/download/
114 | @rm -rf examples/multirepo/client/metadata/
115 | @rm -rf examples/repository/tmp*
116 | @rm -rf examples/client/tmp*
117 | @rm -rf tuf_download
118 | @rm -rf tuf_metadata
119 | @rm -f tuf-client
120 | @rm -f root.json
121 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Copyright 2022-2023 VMware, Inc.
2 |
3 | This product is licensed to you under the BSD-2 license (the "License").
4 | You may not use this product except in compliance with the BSD-2 License.
5 | This product may include a number of subcomponents with separate copyright
6 | notices and license terms. Your use of these subcomponents is subject to
7 | the terms and conditions of the subcomponent's license, as noted in the
8 | LICENSE file.
9 |
10 | SPDX-License-Identifier: BSD-2-Clause
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ----------------------------
2 | ## Deprecation notice:
3 |
4 | The code base of this project got donated to https://github.com/theupdateframework/go-tuf where it replaced the existing implementation and is now being distributed under the v2 version.
5 |
6 | That said take into account that I'll be archiving this repository in a few weeks so feel free to switch to https://github.com/theupdateframework/go-tuf/v2
7 |
8 | I'm happy to see how something that started as a PoC while being sick with COVID progressed to replacing the existing go-tuf code a year later.
9 |
10 | Thanks to everyone that helped.
11 |
12 | ----------------------------
13 |
14 | 
15 | [](https://codecov.io/github/rdimitrov/go-tuf-metadata)
16 | [](https://pkg.go.dev/github.com/rdimitrov/go-tuf-metadata)
17 | [](https://goreportcard.com/report/github.com/rdimitrov/go-tuf-metadata)
18 | [](https://opensource.org/licenses/BSD-2-Clause)
19 |
20 | #
A Framework for Securing Software Update Systems
21 |
22 | ----------------------------
23 |
24 | [The Update Framework (TUF)](https://theupdateframework.io/) is a framework for
25 | secure content delivery and updates. It protects against various types of
26 | supply chain attacks and provides resilience to compromise.
27 |
28 | [go-tuf-metadata](https://github.com/rdimitrov/go-tuf-metadata) started from the idea of providing a Go implementation of TUF that is heavily influenced by the
29 | design decisions made in [python-tuf](https://github.com/theupdateframework/python-tuf).
30 |
31 | ## About The Update Framework
32 |
33 | ----------------------------
34 | The Update Framework (TUF) design helps developers maintain the security of a
35 | software update system, even against attackers that compromise the repository
36 | or signing keys.
37 | TUF provides a flexible
38 | [specification](https://github.com/theupdateframework/specification/blob/master/tuf-spec.md)
39 | defining functionality that developers can use in any software update system or
40 | re-implement to fit their needs.
41 |
42 | TUF is hosted by the [Linux Foundation](https://www.linuxfoundation.org/) as
43 | part of the [Cloud Native Computing Foundation](https://www.cncf.io/) (CNCF)
44 | and its design is [used in production](https://theupdateframework.io/adoptions/)
45 | by various tech companies and open-source organizations.
46 |
47 | Please see [TUF's website](https://theupdateframework.com/) for more information about TUF!
48 |
49 | ## Overview
50 |
51 | ----------------------------
52 |
53 | The [go-tuf-metadata](https://github.com/rdimitrov/go-tuf-metadata) project provides the following functionality:
54 |
55 | * creation, reading, and writing of metadata
56 | * an easy object-oriented approach for interacting with metadata
57 | * consistent snapshots
58 | * signing and verifying metadata
59 | * ED25519, RSA, and ECDSA key types referenced by the latest TUF specification
60 | * top-level role delegation
61 | * target delegation via standard and hash bin delegations
62 | * support of [succinct hash bin delegations](https://github.com/theupdateframework/taps/blob/master/tap15.md) which significantly reduce the size of metadata
63 | * support for unrecognized fields within the metadata (i.e. preserved and accessible through `root.Signed.UnrecognizedFields["some-unknown-field"]`, also used for verifying/signing (if included in the Signed portion of the metadata))
64 | * TUF client API
65 | * TUF multi-repository client API (implements [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md))
66 |
67 | ## Examples
68 |
69 | ----------------------------
70 |
71 | * [basic_repository.go](examples/repository/basic_repository.go) example which demonstrates how to *manually* create and
72 | maintain repository metadata using the low-level Metadata API.
73 |
74 | To try it - run `make example-repository` (the artifacts will be located at `examples/repository/`).
75 |
76 | * [client_example.go](examples/client/client_example.go) which demonstrates how to implement a client using the [updater](metadata/updater/updater.go) package.
77 |
78 | To try it - run `make example-client` (the artifacts will be located at `examples/client/`)
79 |
80 | * [tuf-client CLI](examples/cli/tuf-client/) - a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification.
81 |
82 | To try it - run `make example-tuf-client-cli`
83 |
84 | * [multi-repository client example (TAP4)](examples/multirepo/client/client_example.go) which demonstrates how to implement a multi-repository TUF client using the [multirepo](metadata/multirepo/multirepo.go) package.
85 |
86 | To try it - run `make example-multirepo`
87 |
88 | ## Package details
89 |
90 | ----------------------------
91 |
92 | ### The `metadata` package
93 |
94 | * The `metadata` package provides access to a Metadata file abstraction that closely
95 | follows the TUF specification’s document formats. This API handles de/serialization
96 | to and from files and bytes. It also covers the process of creating and verifying metadata
97 | signatures and makes it easier to access and modify metadata content. It is purely
98 | focused on individual pieces of Metadata and provides no concepts like “repository”
99 | or “update workflow”.
100 |
101 | ### The `trustedmetadata` package
102 |
103 | * A `TrustedMetadata` instance ensures that the collection of metadata in it is valid
104 | and trusted through the whole client update workflow. It provides easy ways to update
105 | the metadata with the caller making decisions on what is updated.
106 |
107 | ### The `config` package
108 |
109 | * The `config` package stores configuration for an ``Updater`` instance.
110 |
111 | ### The `fetcher` package
112 |
113 | * The `fetcher` package defines an interface for abstract network download.
114 |
115 | ### The `updater` package
116 |
117 | * The `updater` package provides an implementation of the TUF client workflow.
118 | It provides ways to query and download target files securely while handling the
119 | TUF update workflow behind the scenes. It is implemented on top of the Metadata API
120 | and can be used to implement various TUF clients with relatively little effort.
121 |
122 | ### The `multirepo` package
123 |
124 | * The `multirepo` package provides an implementation of [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md). It provides a secure search for particular targets across multiple repositories. It provides the functionality for how multiple repositories with separate roots of trust can be required to sign off on the same targets, effectively creating an AND relation and ensuring any files obtained can be trusted. It offers a way to initialize multiple repositories using a `map.json` file and also mechanisms to query and download target files securely. It is implemented on top of the Updater API and can be used to implement various multi-repository TUF clients with relatively little effort.
125 |
126 | ## Documentation
127 |
128 | ----------------------------
129 |
130 | * [go-tuf-metadata documentation](https://pkg.go.dev/github.com/rdimitrov/go-tuf-metadata)
131 |
132 | * [Introduction to TUF's Design](https://theupdateframework.io/overview/)
133 |
134 | * [The TUF Specification](https://theupdateframework.github.io/specification/latest/)
135 |
136 | ## Contact
137 |
138 | ----------------------------
139 |
140 | Questions, feedback, and suggestions are welcomed on the [#tuf](https://cloud-native.slack.com/archives/C8NMD3QJ3) channel on
141 | [CNCF Slack](https://slack.cncf.io/).
142 |
143 | We strive to make the specification easy to implement, so if you come across
144 | any inconsistencies or experience any difficulty, do let us know by sending an
145 | email, or by reporting an issue in the GitHub [specification
146 | repo](https://github.com/theupdateframework/specification/issues).
147 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | ----------------------------
4 |
5 | ## Repository
6 |
7 | ----------------------------
8 |
9 | See the [basic_repository.go](repository/basic_repository.go) example which demonstrates how to *manually* create and
10 | maintain repository metadata using the low-level Metadata API.
11 |
12 | The example highlights the following functionality supported by the metadata API:
13 |
14 | * creation of top-level metadata
15 | * target file handling
16 | * consistent snapshots
17 | * support a mixture of key types - ED25519, RSA and ECDSA
18 | * top-level delegation and signing thresholds
19 | * metadata verification
20 | * target delegation
21 | * in-band and out-of-band metadata signing
22 | * writing and reading metadata files
23 | * root key rotation
24 |
25 | ## Client
26 |
27 | ----------------------------
28 | There's also a [client_example.go](client/client_example.go) which demonstrates how to implement a client using the [updater](metadata/updater/updater.go) package.
29 |
30 | * it uses [https://jku.github.io/tuf-demo](https://jku.github.io/tuf-demo), a live TUF repository hosted on GitHub
31 | * shows an example of how to initialize a client
32 | * shows an example of how to download a target file
33 | * the repository is based on python-tuf so it also highlights the interoperability between the two implementations
34 |
35 | ## Multi-repository client
36 |
37 | ----------------------------
38 | There's a [client_example.go](multirepo/client/client_example.go) which demonstrates how to implement a multi-repository client using the [multirepo](metadata/multirepo/multirepo.go) package which implements [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md). The example consists of the following:
39 |
40 | * The `map.json` along with the root files for each repository are distributed via a trusted repository used for initialization
41 | * The metadata, these target files and the script generating them are located in the [examples/multirepo/repository](../repository/) folder
42 | * These files are then used to bootstrap the multi-repository TUF client
43 | * Shows the API provided by the `multirepo` package
44 |
45 | ## CLI tools
46 |
47 | ----------------------------
48 |
49 | The following CLIs are experimental replacements of the CLI tools provided by the go-tuf package. At some point these will be moved to a separate repository.
50 |
51 | * [tuf-client](cli/tuf-client/README.md) - a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification
52 |
53 | * [tuf](cli/tuf/README.md) - Not implemented
54 |
--------------------------------------------------------------------------------
/examples/cli/README.md:
--------------------------------------------------------------------------------
1 | # tuf and tuf-client CLI tools
2 |
3 | ----------------------------
4 |
5 | ## Overview
6 |
7 | ----------------------------
8 |
9 | The following CLIs are experimental replacements of the CLI tools provided by the go-tuf package:
10 |
11 | * [tuf-client](tuf-client/README.md) - a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification
12 |
13 | * [tuf](tuf/README.md) - Not implemented
14 |
--------------------------------------------------------------------------------
/examples/cli/tuf-client/README.md:
--------------------------------------------------------------------------------
1 | # tuf-client CLI
2 |
3 | ----------------------------
4 |
5 | ## Overview
6 |
7 | ----------------------------
8 |
9 | `tuf-client` is a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification.
10 |
11 | The tuf-client can be used to query for available targets and to download them in a secure manner.
12 |
13 | All downloaded files are verified by signed metadata.
14 |
15 | The CLI provides three commands:
16 |
17 | * `tuf-client init` - Initialize the client with trusted root.json metadata
18 | * `tuf-client get` - Download a target file
19 | * `tuf-client reset` - Resets the local environment. Warning: this deletes both the metadata and download folders and all of their contents
20 |
21 | All commands except `reset` require the URL of the TUF repository passed as a flag via `--url/u`
22 |
23 | Run `tuf-client help` from the command line to get more detailed usage information.
24 |
25 | ## Usage
26 |
27 | ----------------------------
28 |
29 | ```bash
30 | # Initialize by providing a root.json
31 | #
32 | # Usage: tuf-client init --url -f root.json
33 | #
34 | $ tuf-client init --url https://jku.github.io/tuf-demo/metadata -f root.json
35 |
36 | # Initialize without providing a root.json
37 | #
38 | # Usage: tuf-client init --url
39 | #
40 | $ tuf-client init --url https://jku.github.io/tuf-demo/metadata
41 |
42 | # Get a target
43 | #
44 | # Usage: tuf-client get --url
45 | #
46 | $ tuf-client get --url https://jku.github.io/tuf-demo/metadata demo/succinctly-delegated-5.txt
47 |
48 | # Get a target by providing a URL of where target files are located
49 | #
50 | # Usage: tuf-client get --url -t
51 | #
52 | # Use --nonprefixed for non-prefixed target files
53 | #
54 | $ tuf-client get --url https://jku.github.io/tuf-demo/metadata --turl https://jku.github.io/tuf-demo/targets --nonprefixed demo/succinctly-delegated-5.txt
55 |
56 | # Reset your local environment
57 | $ tuf-client reset
58 | ```
59 |
--------------------------------------------------------------------------------
/examples/cli/tuf-client/cmd/get.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package cmd
13 |
14 | import (
15 | "fmt"
16 | stdlog "log"
17 | "os"
18 | "path/filepath"
19 |
20 | "github.com/go-logr/stdr"
21 | "github.com/rdimitrov/go-tuf-metadata/metadata"
22 | "github.com/rdimitrov/go-tuf-metadata/metadata/config"
23 | "github.com/rdimitrov/go-tuf-metadata/metadata/updater"
24 | "github.com/spf13/cobra"
25 | )
26 |
27 | var targetsURL string
28 | var useNonHashPrefixedTargetFiles bool
29 |
30 | type localConfig struct {
31 | MetadataDir string
32 | DownloadDir string
33 | MetadataURL string
34 | TargetsURL string
35 | }
36 |
37 | var getCmd = &cobra.Command{
38 | Use: "get",
39 | Aliases: []string{"g"},
40 | Short: "Download a target file",
41 | Args: cobra.ExactArgs(1),
42 | RunE: func(cmd *cobra.Command, args []string) error {
43 | if RepositoryURL == "" {
44 | fmt.Println("Error: required flag(s) \"url\" not set")
45 | os.Exit(1)
46 | }
47 | return GetCmd(args[0])
48 | },
49 | }
50 |
51 | func init() {
52 | getCmd.Flags().StringVarP(&targetsURL, "turl", "t", "", "URL of where the target files are hosted")
53 | getCmd.Flags().BoolVarP(&useNonHashPrefixedTargetFiles, "nonprefixed", "", false, "Do not use hash-prefixed target files with consistent snapshots")
54 | rootCmd.AddCommand(getCmd)
55 | }
56 |
57 | func GetCmd(target string) error {
58 | // set logger and debug verbosity level
59 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "get_cmd", stdlog.LstdFlags)))
60 | if Verbosity {
61 | stdr.SetVerbosity(5)
62 | }
63 |
64 | // verify the client environment was initialized and fetch path names
65 | env, err := verifyEnv()
66 | if err != nil {
67 | return err
68 | }
69 | // read the trusted root metadata
70 | rootBytes, err := os.ReadFile(filepath.Join(env.MetadataDir, "root.json"))
71 | if err != nil {
72 | return err
73 | }
74 |
75 | // updater configuration
76 | cfg, err := config.New(env.MetadataURL, rootBytes) // default config
77 | if err != nil {
78 | return err
79 | }
80 | cfg.LocalMetadataDir = env.MetadataDir
81 | cfg.LocalTargetsDir = env.DownloadDir
82 | cfg.RemoteTargetsURL = env.TargetsURL
83 | cfg.PrefixTargetsWithHash = !useNonHashPrefixedTargetFiles
84 |
85 | // create an Updater instance
86 | up, err := updater.New(cfg)
87 | if err != nil {
88 | return fmt.Errorf("failed to create Updater instance: %w", err)
89 | }
90 |
91 | // try to build the top-level metadata
92 | err = up.Refresh()
93 | if err != nil {
94 | return fmt.Errorf("failed to refresh trusted metadata: %w", err)
95 | }
96 |
97 | // search if the desired target is available
98 | targetInfo, err := up.GetTargetInfo(target)
99 | if err != nil {
100 | return fmt.Errorf("target %s not found: %w", target, err)
101 | }
102 |
103 | // target is available, so let's see if the target is already present locally
104 | path, _, err := up.FindCachedTarget(targetInfo, "")
105 | if err != nil {
106 | return fmt.Errorf("failed while finding a cached target: %w", err)
107 | }
108 |
109 | if path != "" {
110 | fmt.Printf("Target %s is already present at - %s\n", target, path)
111 | return nil
112 | }
113 |
114 | // target is not present locally, so let's try to download it
115 | path, _, err = up.DownloadTarget(targetInfo, "", "")
116 | if err != nil {
117 | return fmt.Errorf("failed to download target file %s - %w", target, err)
118 | }
119 |
120 | fmt.Printf("Successfully downloaded target %s at - %s\n", target, path)
121 |
122 | return nil
123 | }
124 |
125 | func verifyEnv() (*localConfig, error) {
126 | // get working directory
127 | cwd, err := os.Getwd()
128 | if err != nil {
129 | return nil, err
130 | }
131 | // if no targetsURL is set, we expect that the target files are located at the same location where the metadata is
132 | if targetsURL == "" {
133 | targetsURL = RepositoryURL
134 | }
135 | // start populating what we need
136 | env := &localConfig{
137 | MetadataDir: filepath.Join(cwd, DefaultMetadataDir),
138 | DownloadDir: filepath.Join(cwd, DefaultDownloadDir),
139 | MetadataURL: RepositoryURL,
140 | TargetsURL: targetsURL,
141 | }
142 |
143 | // verify there's local metadata folder
144 | _, err = os.Stat(env.MetadataDir)
145 | if err != nil {
146 | return nil, fmt.Errorf("no local metadata folder: %w", err)
147 | }
148 | // verify there's local download folder
149 | _, err = os.Stat(env.DownloadDir)
150 | if err != nil {
151 | return nil, fmt.Errorf("no local download folder: %w", err)
152 | }
153 | // verify there's a local root.json available for bootstrapping trust
154 | _, err = os.Stat(fmt.Sprintf("%s/%s.json", env.MetadataDir, metadata.ROOT))
155 | if err != nil {
156 | return nil, fmt.Errorf("no local download folder: %w", err)
157 | }
158 | return env, nil
159 | }
160 |
--------------------------------------------------------------------------------
/examples/cli/tuf-client/cmd/init.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package cmd
13 |
14 | import (
15 | "fmt"
16 | "io"
17 | stdlog "log"
18 | "net/http"
19 | "net/url"
20 | "os"
21 | "path/filepath"
22 |
23 | "github.com/go-logr/stdr"
24 | "github.com/rdimitrov/go-tuf-metadata/metadata"
25 | "github.com/rdimitrov/go-tuf-metadata/metadata/trustedmetadata"
26 | "github.com/spf13/cobra"
27 | )
28 |
29 | var rootPath string
30 |
31 | var initCmd = &cobra.Command{
32 | Use: "init",
33 | Aliases: []string{"i"},
34 | Short: "Initialize the client with trusted root.json metadata",
35 | Args: cobra.ExactArgs(0),
36 | RunE: func(cmd *cobra.Command, args []string) error {
37 | if RepositoryURL == "" {
38 | fmt.Println("Error: required flag(s) \"url\" not set")
39 | os.Exit(1)
40 | }
41 | return InitializeCmd()
42 | },
43 | }
44 |
45 | func init() {
46 | initCmd.Flags().StringVarP(&rootPath, "file", "f", "", "location of the trusted root metadata file")
47 | rootCmd.AddCommand(initCmd)
48 | }
49 |
50 | func InitializeCmd() error {
51 | copyTrusted := true
52 | // set logger and debug verbosity level
53 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "ini_cmd", stdlog.LstdFlags)))
54 | if Verbosity {
55 | stdr.SetVerbosity(5)
56 | }
57 |
58 | // prepare the local environment
59 | localMetadataDir, err := prepareEnvironment()
60 | if err != nil {
61 | return err
62 | }
63 |
64 | // if there's no root.json file passed, try to download the 1.root.json from the repository URL
65 | if rootPath == "" {
66 |
67 | fmt.Printf("No root.json file was provided. Trying to download one from %s\n", RepositoryURL)
68 | rootPath, err = fetchTrustedRoot(localMetadataDir)
69 | if err != nil {
70 | return err
71 | }
72 | rootPath = fmt.Sprintf("%s/%s.json", rootPath, metadata.ROOT)
73 | // no need to copy root.json to the metadata folder as we already download it in the expected location
74 | copyTrusted = false
75 | }
76 |
77 | // read the content of root.json
78 | rootBytes, err := ReadFile(rootPath)
79 | if err != nil {
80 | return err
81 | }
82 |
83 | // verify the content
84 | _, err = trustedmetadata.New(rootBytes)
85 | if err != nil {
86 | return err
87 | }
88 |
89 | // Save the trusted root.json file to the metadata folder so it is available for future operations (if we haven't downloaded it)
90 | if copyTrusted {
91 | err = os.WriteFile(filepath.Join(localMetadataDir, rootPath), rootBytes, 0644)
92 | if err != nil {
93 | return err
94 | }
95 | }
96 |
97 | fmt.Println("Initialization successful")
98 |
99 | return nil
100 | }
101 |
102 | // prepareEnvironment prepares the local environment
103 | func prepareEnvironment() (string, error) {
104 | // get working directory
105 | cwd, err := os.Getwd()
106 | if err != nil {
107 | return "", fmt.Errorf("failed to get current working directory: %w", err)
108 | }
109 | metadataPath := filepath.Join(cwd, DefaultMetadataDir)
110 | downloadPath := filepath.Join(cwd, DefaultDownloadDir)
111 |
112 | // create a folder for storing the artifacts
113 | err = os.Mkdir(metadataPath, 0750)
114 | if err != nil {
115 | return "", fmt.Errorf("failed to create local metadata folder: %w", err)
116 | }
117 |
118 | // create a destination folder for storing the downloaded target
119 | err = os.Mkdir(downloadPath, 0750)
120 | if err != nil {
121 | return "", fmt.Errorf("failed to create download folder: %w", err)
122 | }
123 | return metadataPath, nil
124 | }
125 |
126 | // fetchTrustedRoot downloads the initial root metadata
127 | func fetchTrustedRoot(metadataDir string) (string, error) {
128 | // download the initial root metadata so we can bootstrap Trust-On-First-Use
129 | rootURL, err := url.JoinPath(RepositoryURL, "1.root.json")
130 | if err != nil {
131 | return "", fmt.Errorf("failed to create URL path for 1.root.json: %w", err)
132 | }
133 |
134 | req, err := http.NewRequest("GET", rootURL, nil)
135 | if err != nil {
136 | return "", fmt.Errorf("failed to create http request: %w", err)
137 | }
138 |
139 | client := http.DefaultClient
140 |
141 | res, err := client.Do(req)
142 | if err != nil {
143 | return "", fmt.Errorf("failed to executed the http request: %w", err)
144 | }
145 |
146 | defer res.Body.Close()
147 |
148 | data, err := io.ReadAll(res.Body)
149 | if err != nil {
150 | return "", fmt.Errorf("failed to read the http request body: %w", err)
151 | }
152 |
153 | // write the downloaded root metadata to file
154 | err = os.WriteFile(filepath.Join(metadataDir, "root.json"), data, 0644)
155 | if err != nil {
156 | return "", fmt.Errorf("failed to write root.json metadata: %w", err)
157 | }
158 | return metadataDir, nil
159 | }
160 |
--------------------------------------------------------------------------------
/examples/cli/tuf-client/cmd/reset.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package cmd
13 |
14 | import (
15 | "fmt"
16 | "os"
17 | "path/filepath"
18 | "strings"
19 |
20 | "github.com/spf13/cobra"
21 | )
22 |
23 | var ForceDelete bool
24 |
25 | var resetCmd = &cobra.Command{
26 | Use: "reset",
27 | Aliases: []string{"r"},
28 | Short: "Resets the local environment. Warning: this deletes both the metadata and download folders and all of their contents",
29 | Args: cobra.ExactArgs(0),
30 | RunE: func(cmd *cobra.Command, args []string) error {
31 | return ResetCmd()
32 | },
33 | }
34 |
35 | func init() {
36 | resetCmd.Flags().BoolVarP(&ForceDelete, "force", "f", false, "force delete without waiting for confirmation")
37 | rootCmd.AddCommand(resetCmd)
38 | }
39 |
40 | func ResetCmd() error {
41 | // get working directory
42 | cwd, err := os.Getwd()
43 | if err != nil {
44 | return fmt.Errorf("failed to get current working directory: %w", err)
45 | }
46 |
47 | // folders to delete
48 | metadataPath := filepath.Join(cwd, DefaultMetadataDir)
49 | downloadPath := filepath.Join(cwd, DefaultDownloadDir)
50 |
51 | // warning: deletes the metadata folder and all of its contents
52 | fmt.Printf("Warning: Are you sure you want to delete the \"%s\" folder and all of its contents? (y/n)\n", metadataPath)
53 | if ForceDelete || askForConfirmation() {
54 | os.RemoveAll(metadataPath)
55 | fmt.Printf("Folder %s was successfully deleted\n", metadataPath)
56 | } else {
57 | fmt.Printf("Folder \"%s\" was not deleted\n", metadataPath)
58 | }
59 |
60 | // warning: deletes the download folder and all of its contents
61 | fmt.Printf("Warning: Are you sure you want to delete the \"%s\" folder and all of its contents? (y/n)\n", downloadPath)
62 | if ForceDelete || askForConfirmation() {
63 | os.RemoveAll(downloadPath)
64 | fmt.Printf("Folder %s was successfully deleted\n", downloadPath)
65 | } else {
66 | fmt.Printf("Folder \"%s\" was not deleted\n", downloadPath)
67 | }
68 |
69 | return nil
70 | }
71 |
72 | func askForConfirmation() bool {
73 | var response string
74 | _, err := fmt.Scanln(&response)
75 | if err != nil {
76 | fmt.Println(err)
77 | os.Exit(1)
78 | }
79 | switch strings.ToLower(response) {
80 | case "y", "yes":
81 | return true
82 | case "n", "no":
83 | return false
84 | default:
85 | fmt.Println("I'm sorry but I didn't get what you meant, please type (y)es or (n)o and then press enter:")
86 | return askForConfirmation()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/examples/cli/tuf-client/cmd/root.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package cmd
13 |
14 | import (
15 | "io"
16 | "os"
17 |
18 | "github.com/spf13/cobra"
19 | )
20 |
21 | const (
22 | DefaultMetadataDir = "tuf_metadata"
23 | DefaultDownloadDir = "tuf_download"
24 | )
25 |
26 | var Verbosity bool
27 | var RepositoryURL string
28 |
29 | var rootCmd = &cobra.Command{
30 | Use: "tuf-client",
31 | Short: "tuf-client - a client-side CLI tool for The Update Framework (TUF)",
32 | Long: `tuf-client is a CLI tool that implements the client workflow specified by The Update Framework (TUF) specification.
33 |
34 | The tuf-client can be used to query for available targets and to download them in a secure manner.
35 |
36 | All downloaded files are verified by signed metadata.`,
37 | Run: func(cmd *cobra.Command, args []string) {
38 | // show the help message if no command has been used
39 | if len(args) == 0 {
40 | _ = cmd.Help()
41 | os.Exit(0)
42 | }
43 | },
44 | }
45 |
46 | func Execute() {
47 | rootCmd.PersistentFlags().BoolVarP(&Verbosity, "verbose", "v", false, "verbose output")
48 | rootCmd.PersistentFlags().StringVarP(&RepositoryURL, "url", "u", "", "URL of the TUF repository")
49 |
50 | if err := rootCmd.Execute(); err != nil {
51 | os.Exit(1)
52 | }
53 | }
54 |
55 | // ReadFile reads the content of a file and return its bytes
56 | func ReadFile(name string) ([]byte, error) {
57 | in, err := os.Open(name)
58 | if err != nil {
59 | return nil, err
60 | }
61 | defer in.Close()
62 | data, err := io.ReadAll(in)
63 | if err != nil {
64 | return nil, err
65 | }
66 | return data, nil
67 | }
68 |
--------------------------------------------------------------------------------
/examples/cli/tuf-client/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package main
13 |
14 | import (
15 | tufclient "github.com/rdimitrov/go-tuf-metadata/examples/cli/tuf-client/cmd"
16 | )
17 |
18 | func main() {
19 | tufclient.Execute()
20 | }
21 |
--------------------------------------------------------------------------------
/examples/cli/tuf/README.md:
--------------------------------------------------------------------------------
1 | # tuf CLI
2 |
3 | ----------------------------
4 |
5 | ## Overview
6 |
7 | ----------------------------
8 |
9 | Not implemented
10 |
--------------------------------------------------------------------------------
/examples/cli/tuf/cmd/init.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package cmd
13 |
14 | import (
15 | "fmt"
16 | stdlog "log"
17 | "os"
18 |
19 | "github.com/go-logr/stdr"
20 | "github.com/rdimitrov/go-tuf-metadata/metadata"
21 | "github.com/spf13/cobra"
22 | )
23 |
24 | var initCmd = &cobra.Command{
25 | Use: "init",
26 | Aliases: []string{"i"},
27 | Short: "Initialize a repository",
28 | Args: cobra.ExactArgs(0),
29 | RunE: func(cmd *cobra.Command, args []string) error {
30 | return InitializeCmd()
31 | },
32 | }
33 |
34 | func init() {
35 | rootCmd.AddCommand(initCmd)
36 | }
37 |
38 | func InitializeCmd() error {
39 | // set logger and debug verbosity level
40 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "ini_cmd", stdlog.LstdFlags)))
41 | if Verbosity {
42 | stdr.SetVerbosity(5)
43 | }
44 |
45 | fmt.Println("Initialization successful")
46 |
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/examples/cli/tuf/cmd/root.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package cmd
13 |
14 | import (
15 | "io"
16 | "os"
17 |
18 | "github.com/spf13/cobra"
19 | )
20 |
21 | var Verbosity bool
22 |
23 | var rootCmd = &cobra.Command{
24 | Use: "tuf",
25 | Short: "tuf - a repository-side CLI tool for The Update Framework (TUF)",
26 | Long: "tuf - a repository-side CLI tool for The Update Framework (TUF)",
27 | Run: func(cmd *cobra.Command, args []string) {
28 |
29 | },
30 | }
31 |
32 | func Execute() {
33 | rootCmd.PersistentFlags().BoolVarP(&Verbosity, "verbose", "v", false, "verbose output")
34 |
35 | if err := rootCmd.Execute(); err != nil {
36 | os.Exit(1)
37 | }
38 | }
39 |
40 | // ReadFile reads the content of a file and return its bytes
41 | func ReadFile(name string) ([]byte, error) {
42 | in, err := os.Open(name)
43 | if err != nil {
44 | return nil, err
45 | }
46 | defer in.Close()
47 | data, err := io.ReadAll(in)
48 | if err != nil {
49 | return nil, err
50 | }
51 | return data, nil
52 | }
53 |
--------------------------------------------------------------------------------
/examples/cli/tuf/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package main
13 |
14 | import (
15 | "fmt"
16 | "os"
17 |
18 | tuf "github.com/rdimitrov/go-tuf-metadata/examples/cli/tuf/cmd"
19 | )
20 |
21 | func main() {
22 | fmt.Println("Not implemented")
23 | os.Exit(1)
24 | tuf.Execute()
25 | }
26 |
--------------------------------------------------------------------------------
/examples/client/client_example.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package main
13 |
14 | import (
15 | "fmt"
16 | "io"
17 | stdlog "log"
18 | "net/http"
19 | "net/url"
20 | "os"
21 | "path/filepath"
22 |
23 | "github.com/go-logr/stdr"
24 |
25 | "github.com/rdimitrov/go-tuf-metadata/metadata"
26 | "github.com/rdimitrov/go-tuf-metadata/metadata/config"
27 | "github.com/rdimitrov/go-tuf-metadata/metadata/updater"
28 | )
29 |
30 | // The following config is used to fetch a target from Jussi's GitHub repository example
31 | const (
32 | metadataURL = "https://jku.github.io/tuf-demo/metadata"
33 | targetsURL = "https://jku.github.io/tuf-demo/targets"
34 | targetName = "rdimitrov/artifact-example.md"
35 | verbosity = 4
36 | generateRandomFolder = false
37 | )
38 |
39 | func main() {
40 | // set logger to stdout with info level
41 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "client_example", stdlog.LstdFlags)))
42 | stdr.SetVerbosity(verbosity)
43 |
44 | log := metadata.GetLogger()
45 |
46 | // initialize environment - temporary folders, etc.
47 | metadataDir, err := InitEnvironment()
48 | if err != nil {
49 | log.Error(err, "Failed to initialize environment")
50 | }
51 |
52 | // initialize client with Trust-On-First-Use
53 | err = InitTrustOnFirstUse(metadataDir)
54 | if err != nil {
55 | log.Error(err, "Trust-On-First-Use failed")
56 | }
57 |
58 | // download the desired target
59 | err = DownloadTarget(metadataDir, targetName)
60 | if err != nil {
61 | log.Error(err, "Download failed")
62 | }
63 | }
64 |
65 | // InitEnvironment prepares the local environment - temporary folders, etc.
66 | func InitEnvironment() (string, error) {
67 | var tmpDir string
68 | // get working directory
69 | cwd, err := os.Getwd()
70 | if err != nil {
71 | return "", fmt.Errorf("failed to get current working directory: %w", err)
72 | }
73 | if !generateRandomFolder {
74 | tmpDir = filepath.Join(cwd, "tmp")
75 | // create a temporary folder for storing the demo artifacts
76 | os.Mkdir(tmpDir, 0750)
77 | } else {
78 | // create a temporary folder for storing the demo artifacts
79 | tmpDir, err = os.MkdirTemp(cwd, "tmp")
80 | if err != nil {
81 | return "", fmt.Errorf("failed to create a temporary folder: %w", err)
82 | }
83 | }
84 |
85 | // create a destination folder for storing the downloaded target
86 | os.Mkdir(filepath.Join(tmpDir, "download"), 0750)
87 | return tmpDir, nil
88 | }
89 |
90 | // InitTrustOnFirstUse initialize local trusted metadata (Trust-On-First-Use)
91 | func InitTrustOnFirstUse(metadataDir string) error {
92 | // check if there's already a local root.json available for bootstrapping trust
93 | _, err := os.Stat(filepath.Join(metadataDir, "root.json"))
94 | if err == nil {
95 | return nil
96 | }
97 |
98 | // download the initial root metadata so we can bootstrap Trust-On-First-Use
99 | rootURL, err := url.JoinPath(metadataURL, "1.root.json")
100 | if err != nil {
101 | return fmt.Errorf("failed to create URL path for 1.root.json: %w", err)
102 | }
103 |
104 | req, err := http.NewRequest("GET", rootURL, nil)
105 | if err != nil {
106 | return fmt.Errorf("failed to create http request: %w", err)
107 | }
108 |
109 | client := http.DefaultClient
110 |
111 | res, err := client.Do(req)
112 | if err != nil {
113 | return fmt.Errorf("failed to executed the http request: %w", err)
114 | }
115 |
116 | defer res.Body.Close()
117 |
118 | data, err := io.ReadAll(res.Body)
119 | if err != nil {
120 | return fmt.Errorf("failed to read the http request body: %w", err)
121 | }
122 |
123 | // write the downloaded root metadata to file
124 | err = os.WriteFile(filepath.Join(metadataDir, "root.json"), data, 0644)
125 | if err != nil {
126 | return fmt.Errorf("failed to write root.json metadata: %w", err)
127 | }
128 |
129 | return nil
130 | }
131 |
132 | // DownloadTarget downloads the target file using Updater. The Updater refreshes the top-level metadata,
133 | // get the target information, verifies if the target is already cached, and in case it
134 | // is not cached, downloads the target file.
135 | func DownloadTarget(localMetadataDir, target string) error {
136 | log := metadata.GetLogger()
137 |
138 | rootBytes, err := os.ReadFile(filepath.Join(localMetadataDir, "root.json"))
139 | if err != nil {
140 | return err
141 | }
142 | // create updater configuration
143 | cfg, err := config.New(metadataURL, rootBytes) // default config
144 | if err != nil {
145 | return err
146 | }
147 | cfg.LocalMetadataDir = localMetadataDir
148 | cfg.LocalTargetsDir = filepath.Join(localMetadataDir, "download")
149 | cfg.RemoteTargetsURL = targetsURL
150 | cfg.PrefixTargetsWithHash = true
151 |
152 | // create a new Updater instance
153 | up, err := updater.New(cfg)
154 | if err != nil {
155 | return fmt.Errorf("failed to create Updater instance: %w", err)
156 | }
157 |
158 | // try to build the top-level metadata
159 | err = up.Refresh()
160 | if err != nil {
161 | return fmt.Errorf("failed to refresh trusted metadata: %w", err)
162 | }
163 |
164 | // search if the desired target is available
165 | targetInfo, err := up.GetTargetInfo(target)
166 | if err != nil {
167 | return fmt.Errorf("target %s not found: %w", target, err)
168 | }
169 |
170 | // target is available, so let's see if the target is already present locally
171 | path, _, err := up.FindCachedTarget(targetInfo, "")
172 | if err != nil {
173 | return fmt.Errorf("failed while finding a cached target: %w", err)
174 | }
175 | if path != "" {
176 | log.Info("Target is already present", "target", target, "path", path)
177 | }
178 |
179 | // target is not present locally, so let's try to download it
180 | path, _, err = up.DownloadTarget(targetInfo, "", "")
181 | if err != nil {
182 | return fmt.Errorf("failed to download target file %s - %w", target, err)
183 | }
184 |
185 | log.Info("Successfully downloaded target", "target", target, "path", path)
186 |
187 | return nil
188 | }
189 |
--------------------------------------------------------------------------------
/examples/multirepo/client/README.md:
--------------------------------------------------------------------------------
1 | # Example repository showing a multi repository TUF client (TAP 4)
2 |
3 | The following is a TUF multi-repository client example of the `multirepo` package which implements [TAP 4 - Multiple repository consensus on entrusted targets](https://github.com/theupdateframework/taps/blob/master/tap4.md):
4 |
5 | - The `map.json` along with the root files for each repository are distributed via a trusted repository used for initialization
6 | - The metadata, these target files and the script generating them are located in the [examples/multirepo/repository](../repository/) folder
7 | - These files are then used to bootstrap the multi-repository TUF client
8 | - Shows the API provided by the `multirepo` package
9 |
--------------------------------------------------------------------------------
/examples/multirepo/client/client_example.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package main
13 |
14 | import (
15 | "fmt"
16 | stdlog "log"
17 | "os"
18 | "path/filepath"
19 | "strings"
20 |
21 | "github.com/go-logr/stdr"
22 |
23 | "github.com/rdimitrov/go-tuf-metadata/metadata"
24 | "github.com/rdimitrov/go-tuf-metadata/metadata/config"
25 | "github.com/rdimitrov/go-tuf-metadata/metadata/multirepo"
26 | "github.com/rdimitrov/go-tuf-metadata/metadata/updater"
27 | )
28 |
29 | const (
30 | metadataURL = "https://raw.githubusercontent.com/rdimitrov/go-tuf-metadata/main/examples/multirepo/repository/metadata"
31 | targetsURL = "https://raw.githubusercontent.com/rdimitrov/go-tuf-metadata/main/examples/multirepo/repository/targets"
32 | verbosity = 4
33 | )
34 |
35 | func main() {
36 | // set logger to stdout with info level
37 | metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "multirepo_client_example", stdlog.LstdFlags)))
38 | stdr.SetVerbosity(verbosity)
39 |
40 | // Bootstrap TUF
41 | fmt.Printf("Bootstrapping the initial TUF repo - fetching map.json file and necessary trusted root files\n\n")
42 | mapBytes, trustedRoots, err := BootstrapTUF() // returns the map.json and the trusted root files
43 | if err != nil {
44 | panic(err)
45 | }
46 |
47 | // Initialize the multi-repository TUF client
48 | fmt.Printf("Initializing the multi-repository TUF client with the given map.json file\n\n")
49 | client, err := InitMultiRepoTUF(mapBytes, trustedRoots)
50 | if err != nil {
51 | panic(err)
52 | }
53 |
54 | // Refresh all repositories
55 | fmt.Printf("Refreshing each TUF client (updating metadata/client update workflow)\n\n")
56 | err = client.Refresh()
57 | if err != nil {
58 | panic(err)
59 | }
60 |
61 | // Get target info for the given target
62 | fmt.Printf("Searching for a target using the multi-repository TUF client\n\n")
63 | targetInfo, repositories, err := client.GetTargetInfo("rekor.pub") // rekor.pub trusted_root.json fulcio_v1.crt.pem
64 | if err != nil {
65 | panic(err)
66 | }
67 |
68 | // Download the target using that target info
69 | fmt.Println("Downloading a target using the multi-repository TUF client")
70 | _, _, err = client.DownloadTarget(repositories, targetInfo, "", "")
71 | if err != nil {
72 | panic(err)
73 | }
74 | }
75 |
76 | // BootstrapTUF returns the map file and the related trusted root metadata files
77 | func BootstrapTUF() ([]byte, map[string][]byte, error) {
78 | log := metadata.GetLogger()
79 |
80 | trustedRoots := map[string][]byte{}
81 | mapBytes := []byte{}
82 | // get working directory
83 | cwd, err := os.Getwd()
84 | if err != nil {
85 | return nil, nil, fmt.Errorf("failed to get current working directory: %w", err)
86 | }
87 | targetsDir := filepath.Join(cwd, "bootstrap/targets")
88 |
89 | // ensure the necessary folder layout
90 | err = os.MkdirAll(targetsDir, os.ModePerm)
91 | if err != nil {
92 | return nil, nil, err
93 | }
94 |
95 | // read the trusted root metadata
96 | rootBytes, err := os.ReadFile(filepath.Join(cwd, "root.json"))
97 | if err != nil {
98 | return nil, nil, err
99 | }
100 |
101 | // create updater configuration
102 | cfg, err := config.New(metadataURL, rootBytes) // default config
103 | if err != nil {
104 | return nil, nil, err
105 | }
106 | cfg.LocalMetadataDir = filepath.Join(cwd, "bootstrap")
107 | cfg.LocalTargetsDir = targetsDir
108 | cfg.RemoteTargetsURL = targetsURL
109 |
110 | // create a new Updater instance
111 | up, err := updater.New(cfg)
112 | if err != nil {
113 | return nil, nil, fmt.Errorf("failed to create Updater instance: %w", err)
114 | }
115 |
116 | // build the top-level metadata
117 | err = up.Refresh()
118 | if err != nil {
119 | return nil, nil, fmt.Errorf("failed to refresh trusted metadata: %w", err)
120 | }
121 |
122 | // download all target files
123 | for name, targetInfo := range up.GetTopLevelTargets() {
124 | // see if the target is already present locally
125 | path, _, err := up.FindCachedTarget(targetInfo, "")
126 | if err != nil {
127 | return nil, nil, fmt.Errorf("failed while finding a cached target: %w", err)
128 | }
129 | if path != "" {
130 | log.Info("Target is already present", "target", name, "path", path)
131 | }
132 |
133 | // target is not present locally, so let's try to download it
134 | // keeping the same path layout as its target path
135 | expectedTargetLocation := filepath.Join(targetsDir, name)
136 | dirName, _ := filepath.Split(expectedTargetLocation)
137 | err = os.MkdirAll(dirName, os.ModePerm)
138 | if err != nil {
139 | return nil, nil, err
140 | }
141 |
142 | // download targets (we don't have to actually store them other than for the sake of the example)
143 | path, bytes, err := up.DownloadTarget(targetInfo, expectedTargetLocation, "")
144 | if err != nil {
145 | return nil, nil, fmt.Errorf("failed to download target file %s - %w", name, err)
146 | }
147 |
148 | // populate the return values
149 | if name == "map.json" {
150 | mapBytes = bytes
151 | } else {
152 | repositoryName := strings.Split(name, string(os.PathSeparator))
153 | trustedRoots[repositoryName[0]] = bytes
154 | }
155 | log.Info("Successfully downloaded target", "target", name, "path", path)
156 | }
157 |
158 | return mapBytes, trustedRoots, nil
159 | }
160 |
161 | func InitMultiRepoTUF(mapBytes []byte, trustedRoots map[string][]byte) (*multirepo.MultiRepoClient, error) {
162 | // get working directory
163 | cwd, err := os.Getwd()
164 | if err != nil {
165 | return nil, fmt.Errorf("failed to get current working directory: %w", err)
166 | }
167 |
168 | // create a new configuration for a multi-repository client
169 | cfg, err := multirepo.NewConfig(mapBytes, trustedRoots)
170 | if err != nil {
171 | return nil, err
172 | }
173 | cfg.LocalMetadataDir = filepath.Join(cwd, "metadata")
174 | cfg.LocalTargetsDir = filepath.Join(cwd, "download")
175 |
176 | // create a new instance of a multi-repository TUF client
177 | return multirepo.New(cfg)
178 | }
179 |
--------------------------------------------------------------------------------
/examples/multirepo/client/root.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138",
5 | "sig": "b434f25bd61a5cb7c53883b3d91c53b94e9312cd17e19f2b1e1089d73772fad542c0a91ef109404fe4cfabbd2d4cf3b0f7eaaca4fd012555014f75a23d49460a"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "root",
10 | "consistent_snapshot": true,
11 | "expires": "2033-11-28T19:51:01.794266Z",
12 | "keys": {
13 | "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138": {
14 | "keytype": "ed25519",
15 | "keyval": {
16 | "public": "3b70174991642fd8cfd4b1a4d834843f785db02ad98372b24eb54d06750dd732"
17 | },
18 | "scheme": "ed25519"
19 | },
20 | "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696": {
21 | "keytype": "ed25519",
22 | "keyval": {
23 | "public": "2f70d3d75e50f50cf9af3adcc20754ba2d17601801f36d61cacb9f73a7f798f9"
24 | },
25 | "scheme": "ed25519"
26 | },
27 | "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5": {
28 | "keytype": "ed25519",
29 | "keyval": {
30 | "public": "3c3769ac5f74c8f2f84748586344ab8bbda9f0096859d5f37450dec1f5675434"
31 | },
32 | "scheme": "ed25519"
33 | },
34 | "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b": {
35 | "keytype": "ed25519",
36 | "keyval": {
37 | "public": "6b1daebaa9b926bb38c33fd7d4edc14e2e96f8795b2ffe6ed49adee6a1251b20"
38 | },
39 | "scheme": "ed25519"
40 | }
41 | },
42 | "roles": {
43 | "root": {
44 | "keyids": [
45 | "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138"
46 | ],
47 | "threshold": 1
48 | },
49 | "snapshot": {
50 | "keyids": [
51 | "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696"
52 | ],
53 | "threshold": 1
54 | },
55 | "targets": {
56 | "keyids": [
57 | "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5"
58 | ],
59 | "threshold": 1
60 | },
61 | "timestamp": {
62 | "keyids": [
63 | "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b"
64 | ],
65 | "threshold": 1
66 | }
67 | },
68 | "spec_version": "1.0.31",
69 | "version": 1
70 | }
71 | }
--------------------------------------------------------------------------------
/examples/multirepo/repository/README.md:
--------------------------------------------------------------------------------
1 | # Example repository used for bootstrapping a multi repository TUF client (TAP 4)
2 |
3 | The following is a helper TUF repository which serves several targets:
4 |
5 | - `map.json` which holds repository mappings and can be used to bootstrap a TUF client supporting multiple repositories
6 | - A set of trusted root files for each repository listed in the `map.json` file
7 | - The `examples/multirepo/client/client_example.go`(../client/client_example.go) is a client which uses this repository to bootstrap a multi-repository TUF client
8 |
--------------------------------------------------------------------------------
/examples/multirepo/repository/generate_metadata.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package main
13 |
14 | import (
15 | "crypto"
16 | "fmt"
17 | "os"
18 | "path/filepath"
19 | "strings"
20 | "time"
21 |
22 | "github.com/rdimitrov/go-tuf-metadata/metadata"
23 | "github.com/rdimitrov/go-tuf-metadata/metadata/repository"
24 | "github.com/sigstore/sigstore/pkg/signature"
25 | "golang.org/x/crypto/ed25519"
26 | )
27 |
28 | func main() {
29 | // Create top-level metadata
30 | roles := repository.New()
31 | keys := map[string]ed25519.PrivateKey{}
32 |
33 | // Create Targets metadata
34 | targets := metadata.Targets(helperExpireIn(10))
35 | roles.SetTargets("targets", targets)
36 |
37 | // Add each target to Targets metadata
38 | for _, targetName := range []string{"targets/map.json", "targets/sigstore-tuf-root/root.json", "targets/staging/root.json"} {
39 | targetPath, localPath := helperGetPathForTarget(targetName)
40 | targetFileInfo, err := metadata.TargetFile().FromFile(localPath, "sha256")
41 | if err != nil {
42 | panic(fmt.Sprintln("generate_metadata.go:", "generating target file info failed", err))
43 | }
44 | roles.Targets("targets").Signed.Targets[strings.TrimPrefix(targetPath, "targets/")] = targetFileInfo
45 | for _, eachHashValue := range targetFileInfo.Hashes {
46 | err := copyHashPrefixed(localPath, eachHashValue.String())
47 | if err != nil {
48 | panic(err)
49 | }
50 | }
51 | }
52 |
53 | // Create Snapshot metadata
54 | snapshot := metadata.Snapshot(helperExpireIn(10))
55 | roles.SetSnapshot(snapshot)
56 |
57 | // Create Timestamp metadata
58 | timestamp := metadata.Timestamp(helperExpireIn(10))
59 | roles.SetTimestamp(timestamp)
60 |
61 | // Create Root metadata
62 | root := metadata.Root(helperExpireIn(10))
63 | roles.SetRoot(root)
64 |
65 | // For this example, we generate one private key of type 'ed25519' for each top-level role
66 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} {
67 | _, private, err := ed25519.GenerateKey(nil)
68 | if err != nil {
69 | panic(fmt.Sprintln("generate_metadata.go:", "key generation failed", err))
70 | }
71 | keys[name] = private
72 | key, err := metadata.KeyFromPublicKey(private.Public())
73 | if err != nil {
74 | panic(fmt.Sprintln("generate_metadata.go:", "key conversion failed", err))
75 | }
76 | err = roles.Root().Signed.AddKey(key, name)
77 | if err != nil {
78 | panic(fmt.Sprintln("generate_metadata.go:", "adding key to root failed", err))
79 | }
80 | }
81 |
82 | // Sign top-level metadata (in-band)
83 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} {
84 | key := keys[name]
85 | signer, err := signature.LoadSigner(key, crypto.Hash(0))
86 | if err != nil {
87 | panic(fmt.Sprintln("generate_metadata.go:", "loading a signer failed", err))
88 | }
89 | switch name {
90 | case "targets":
91 | _, err = roles.Targets("targets").Sign(signer)
92 | case "snapshot":
93 | _, err = roles.Snapshot().Sign(signer)
94 | case "timestamp":
95 | _, err = roles.Timestamp().Sign(signer)
96 | case "root":
97 | _, err = roles.Root().Sign(signer)
98 | }
99 | if err != nil {
100 | panic(fmt.Sprintln("generate_metadata.go:", "metadata signing failed", err))
101 | }
102 | }
103 |
104 | // Persist metadata (consistent snapshot)
105 | cwd, err := os.Getwd()
106 | if err != nil {
107 | panic(fmt.Sprintln("generate_metadata.go:", "getting cwd failed", err))
108 | }
109 | // Save to metadata folder
110 | cwd = filepath.Join(cwd, "metadata")
111 | for _, name := range []string{"targets", "snapshot", "timestamp", "root"} {
112 | switch name {
113 | case "targets":
114 | filename := fmt.Sprintf("%d.%s.json", roles.Targets("targets").Signed.Version, name)
115 | err = roles.Targets("targets").ToFile(filepath.Join(cwd, filename), true)
116 | case "snapshot":
117 | filename := fmt.Sprintf("%d.%s.json", roles.Snapshot().Signed.Version, name)
118 | err = roles.Snapshot().ToFile(filepath.Join(cwd, filename), true)
119 | case "timestamp":
120 | filename := fmt.Sprintf("%s.json", name)
121 | err = roles.Timestamp().ToFile(filepath.Join(cwd, filename), true)
122 | case "root":
123 | filename := fmt.Sprintf("%d.%s.json", roles.Root().Signed.Version, name)
124 | err = roles.Root().ToFile(filepath.Join(cwd, filename), true)
125 | }
126 | if err != nil {
127 | panic(fmt.Sprintln("generate_metadata.go:", "saving metadata to file failed", err))
128 | }
129 | }
130 |
131 | // Save the created root metadata in the client folder, this is the initial trusted root metadata
132 | err = roles.Root().ToFile(filepath.Join(cwd, "../../client/root.json"), true)
133 | if err != nil {
134 | panic(fmt.Sprintln("generate_metadata.go:", "saving trusted root metadata to client folder failed", err))
135 | }
136 |
137 | // Verify that metadata is signed correctly
138 | // Verify root
139 | err = roles.Root().VerifyDelegate("root", roles.Root())
140 | if err != nil {
141 | panic(fmt.Sprintln("generate_metadata.go:", "verifying root metadata failed", err))
142 | }
143 |
144 | // Verify targets
145 | err = roles.Root().VerifyDelegate("targets", roles.Targets("targets"))
146 | if err != nil {
147 | panic(fmt.Sprintln("generate_metadata.go:", "verifying targets metadata failed", err))
148 | }
149 |
150 | // Verify snapshot
151 | err = roles.Root().VerifyDelegate("snapshot", roles.Snapshot())
152 | if err != nil {
153 | panic(fmt.Sprintln("generate_metadata.go:", "verifying snapshot metadata failed", err))
154 | }
155 |
156 | // Verify timestamp
157 | err = roles.Root().VerifyDelegate("timestamp", roles.Timestamp())
158 | if err != nil {
159 | panic(fmt.Sprintln("generate_metadata.go:", "verifying timestamp metadata failed", err))
160 | }
161 |
162 | fmt.Println("Done! Metadata files location:", cwd)
163 | }
164 |
165 | // helperExpireIn returns time offset by years (for the sake of the example)
166 | func helperExpireIn(years int) time.Time {
167 | return time.Now().AddDate(years, 0, 0).UTC()
168 | }
169 |
170 | // helperGetPathForTarget returns local and target paths for target
171 | func helperGetPathForTarget(name string) (string, string) {
172 | cwd, err := os.Getwd()
173 | if err != nil {
174 | panic(fmt.Sprintln("generate_metadata.go:", "getting cwd failed", err))
175 | }
176 | // _, dir := filepath.Split(cwd)
177 | // return filepath.Join(dir, name), filepath.Join(cwd, name)
178 | return name, filepath.Join(cwd, name)
179 | }
180 |
181 | func copyHashPrefixed(src string, hash string) error {
182 | data, err := os.ReadFile(src)
183 | if err != nil {
184 | return err
185 | }
186 | dirName, fileName := filepath.Split(src)
187 | err = os.WriteFile(filepath.Join(dirName, fmt.Sprintf("%s.%s", hash, fileName)), data, 0644)
188 | if err != nil {
189 | return err
190 | }
191 | return nil
192 | }
193 |
--------------------------------------------------------------------------------
/examples/multirepo/repository/metadata/1.root.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138",
5 | "sig": "b434f25bd61a5cb7c53883b3d91c53b94e9312cd17e19f2b1e1089d73772fad542c0a91ef109404fe4cfabbd2d4cf3b0f7eaaca4fd012555014f75a23d49460a"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "root",
10 | "consistent_snapshot": true,
11 | "expires": "2033-11-28T19:51:01.794266Z",
12 | "keys": {
13 | "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138": {
14 | "keytype": "ed25519",
15 | "keyval": {
16 | "public": "3b70174991642fd8cfd4b1a4d834843f785db02ad98372b24eb54d06750dd732"
17 | },
18 | "scheme": "ed25519"
19 | },
20 | "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696": {
21 | "keytype": "ed25519",
22 | "keyval": {
23 | "public": "2f70d3d75e50f50cf9af3adcc20754ba2d17601801f36d61cacb9f73a7f798f9"
24 | },
25 | "scheme": "ed25519"
26 | },
27 | "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5": {
28 | "keytype": "ed25519",
29 | "keyval": {
30 | "public": "3c3769ac5f74c8f2f84748586344ab8bbda9f0096859d5f37450dec1f5675434"
31 | },
32 | "scheme": "ed25519"
33 | },
34 | "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b": {
35 | "keytype": "ed25519",
36 | "keyval": {
37 | "public": "6b1daebaa9b926bb38c33fd7d4edc14e2e96f8795b2ffe6ed49adee6a1251b20"
38 | },
39 | "scheme": "ed25519"
40 | }
41 | },
42 | "roles": {
43 | "root": {
44 | "keyids": [
45 | "492e4e5660058c39cae70b380750e4f8ea86d0fc2047c795af9bbbe73cedd138"
46 | ],
47 | "threshold": 1
48 | },
49 | "snapshot": {
50 | "keyids": [
51 | "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696"
52 | ],
53 | "threshold": 1
54 | },
55 | "targets": {
56 | "keyids": [
57 | "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5"
58 | ],
59 | "threshold": 1
60 | },
61 | "timestamp": {
62 | "keyids": [
63 | "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b"
64 | ],
65 | "threshold": 1
66 | }
67 | },
68 | "spec_version": "1.0.31",
69 | "version": 1
70 | }
71 | }
--------------------------------------------------------------------------------
/examples/multirepo/repository/metadata/1.snapshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "8dcef43f9966d6b5c88cb10f7b5258361532de2151c65378bb8a21c281658696",
5 | "sig": "e0f197531eec1a8aeea48014f4459206d59546c1a6a3ab5ea18910b229b51fe6475822fae0005fd8ff611944ca6e8b5c3b286a572d1812e561d55fdeafad9704"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "snapshot",
10 | "expires": "2033-11-28T19:51:01.794264Z",
11 | "meta": {
12 | "targets.json": {
13 | "version": 1
14 | }
15 | },
16 | "spec_version": "1.0.31",
17 | "version": 1
18 | }
19 | }
--------------------------------------------------------------------------------
/examples/multirepo/repository/metadata/1.targets.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "a606e603cc4974a8fa894928cb165ce38a3f9948c245414bc71ef681caae9dc5",
5 | "sig": "4c2ad791fe1b2d23b4a7292120b493c61795cb23519b2f23b9c17a25a1078b7cdf72f052fe4ddc583e45ef6ab6ca809f35c192bdf60649ba371bc063a62cbd05"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "targets",
10 | "expires": "2033-11-28T19:51:01.793233Z",
11 | "spec_version": "1.0.31",
12 | "targets": {
13 | "map.json": {
14 | "hashes": {
15 | "sha256": "9bb72b3c684f4e489e7fdce1afabff64b4e3f4b7877ac8cb78a684c2c65fc6d8"
16 | },
17 | "length": 645
18 | },
19 | "sigstore-tuf-root/root.json": {
20 | "hashes": {
21 | "sha256": "e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4"
22 | },
23 | "length": 6388
24 | },
25 | "staging/root.json": {
26 | "hashes": {
27 | "sha256": "e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4"
28 | },
29 | "length": 6388
30 | }
31 | },
32 | "version": 1
33 | }
34 | }
--------------------------------------------------------------------------------
/examples/multirepo/repository/metadata/timestamp.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "e92842caf93e6e50a64ec0a467dee5bbd06669a6474895b3d5133c69de33545b",
5 | "sig": "41670fd3ca12b41a5f3109e6537646586099108c693c913d75570a47ff72e8910b32858a80be6f2ba058c0993af84de4f26a7b5a93b3322e0cf8d10e37352609"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "timestamp",
10 | "expires": "2033-11-28T19:51:01.794266Z",
11 | "meta": {
12 | "snapshot.json": {
13 | "version": 1
14 | }
15 | },
16 | "spec_version": "1.0.31",
17 | "version": 1
18 | }
19 | }
--------------------------------------------------------------------------------
/examples/multirepo/repository/targets/9bb72b3c684f4e489e7fdce1afabff64b4e3f4b7877ac8cb78a684c2c65fc6d8.map.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "repositories": {
4 | "sigstore-tuf-root": ["https://tuf-repo-cdn.sigstore.dev"],
5 | "staging": ["https://raw.githubusercontent.com/sigstore/root-signing/main/repository/repository"]
6 | },
7 | "mapping": [
8 | {
9 | "paths": ["fulcio*", "*.json"],
10 | "repositories": ["staging"],
11 | "threshold": 1,
12 | "terminating": true
13 | },
14 | {
15 | "paths": ["*.pub"],
16 | "repositories": ["sigstore-tuf-root", "staging"],
17 | "threshold": 2,
18 | "terminating": false
19 | },
20 | {
21 | "paths": ["*"],
22 | "repositories": ["sigstore-tuf-root"],
23 | "terminating": true,
24 | "threshold": 1
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/multirepo/repository/targets/map.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "repositories": {
4 | "sigstore-tuf-root": ["https://tuf-repo-cdn.sigstore.dev"],
5 | "staging": ["https://raw.githubusercontent.com/sigstore/root-signing/main/repository/repository"]
6 | },
7 | "mapping": [
8 | {
9 | "paths": ["fulcio*", "*.json"],
10 | "repositories": ["staging"],
11 | "threshold": 1,
12 | "terminating": true
13 | },
14 | {
15 | "paths": ["*.pub"],
16 | "repositories": ["sigstore-tuf-root", "staging"],
17 | "threshold": 2,
18 | "terminating": false
19 | },
20 | {
21 | "paths": ["*"],
22 | "repositories": ["sigstore-tuf-root"],
23 | "terminating": true,
24 | "threshold": 1
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/multirepo/repository/targets/sigstore-tuf-root/e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json:
--------------------------------------------------------------------------------
1 | {
2 | "signed": {
3 | "_type": "root",
4 | "spec_version": "1.0",
5 | "version": 5,
6 | "expires": "2023-04-18T18:13:43Z",
7 | "keys": {
8 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": {
9 | "keytype": "ecdsa-sha2-nistp256",
10 | "scheme": "ecdsa-sha2-nistp256",
11 | "keyid_hash_algorithms": [
12 | "sha256",
13 | "sha512"
14 | ],
15 | "keyval": {
16 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n"
17 | }
18 | },
19 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": {
20 | "keytype": "ecdsa-sha2-nistp256",
21 | "scheme": "ecdsa-sha2-nistp256",
22 | "keyid_hash_algorithms": [
23 | "sha256",
24 | "sha512"
25 | ],
26 | "keyval": {
27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n"
28 | }
29 | },
30 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": {
31 | "keytype": "ecdsa-sha2-nistp256",
32 | "scheme": "ecdsa-sha2-nistp256",
33 | "keyid_hash_algorithms": [
34 | "sha256",
35 | "sha512"
36 | ],
37 | "keyval": {
38 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n"
39 | }
40 | },
41 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": {
42 | "keytype": "ecdsa-sha2-nistp256",
43 | "scheme": "ecdsa-sha2-nistp256",
44 | "keyid_hash_algorithms": [
45 | "sha256",
46 | "sha512"
47 | ],
48 | "keyval": {
49 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n"
50 | }
51 | },
52 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": {
53 | "keytype": "ecdsa-sha2-nistp256",
54 | "scheme": "ecdsa-sha2-nistp256",
55 | "keyid_hash_algorithms": [
56 | "sha256",
57 | "sha512"
58 | ],
59 | "keyval": {
60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n"
61 | }
62 | },
63 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": {
64 | "keytype": "ecdsa-sha2-nistp256",
65 | "scheme": "ecdsa-sha2-nistp256",
66 | "keyid_hash_algorithms": [
67 | "sha256",
68 | "sha512"
69 | ],
70 | "keyval": {
71 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n"
72 | }
73 | },
74 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": {
75 | "keytype": "ecdsa-sha2-nistp256",
76 | "scheme": "ecdsa-sha2-nistp256",
77 | "keyid_hash_algorithms": [
78 | "sha256",
79 | "sha512"
80 | ],
81 | "keyval": {
82 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n"
83 | }
84 | }
85 | },
86 | "roles": {
87 | "root": {
88 | "keyids": [
89 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
90 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
91 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f",
92 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
93 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de"
94 | ],
95 | "threshold": 3
96 | },
97 | "snapshot": {
98 | "keyids": [
99 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b"
100 | ],
101 | "threshold": 1
102 | },
103 | "targets": {
104 | "keyids": [
105 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
106 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
107 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f",
108 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
109 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de"
110 | ],
111 | "threshold": 3
112 | },
113 | "timestamp": {
114 | "keyids": [
115 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a"
116 | ],
117 | "threshold": 1
118 | }
119 | },
120 | "consistent_snapshot": true
121 | },
122 | "signatures": [
123 | {
124 | "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
125 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a"
126 | },
127 | {
128 | "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
129 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d"
130 | },
131 | {
132 | "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
133 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b"
134 | },
135 | {
136 | "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de",
137 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9"
138 | },
139 | {
140 | "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
141 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a"
142 | },
143 | {
144 | "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
145 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d"
146 | },
147 | {
148 | "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
149 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b"
150 | },
151 | {
152 | "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90",
153 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9"
154 | }
155 | ]
156 | }
--------------------------------------------------------------------------------
/examples/multirepo/repository/targets/sigstore-tuf-root/root.json:
--------------------------------------------------------------------------------
1 | {
2 | "signed": {
3 | "_type": "root",
4 | "spec_version": "1.0",
5 | "version": 5,
6 | "expires": "2023-04-18T18:13:43Z",
7 | "keys": {
8 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": {
9 | "keytype": "ecdsa-sha2-nistp256",
10 | "scheme": "ecdsa-sha2-nistp256",
11 | "keyid_hash_algorithms": [
12 | "sha256",
13 | "sha512"
14 | ],
15 | "keyval": {
16 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n"
17 | }
18 | },
19 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": {
20 | "keytype": "ecdsa-sha2-nistp256",
21 | "scheme": "ecdsa-sha2-nistp256",
22 | "keyid_hash_algorithms": [
23 | "sha256",
24 | "sha512"
25 | ],
26 | "keyval": {
27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n"
28 | }
29 | },
30 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": {
31 | "keytype": "ecdsa-sha2-nistp256",
32 | "scheme": "ecdsa-sha2-nistp256",
33 | "keyid_hash_algorithms": [
34 | "sha256",
35 | "sha512"
36 | ],
37 | "keyval": {
38 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n"
39 | }
40 | },
41 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": {
42 | "keytype": "ecdsa-sha2-nistp256",
43 | "scheme": "ecdsa-sha2-nistp256",
44 | "keyid_hash_algorithms": [
45 | "sha256",
46 | "sha512"
47 | ],
48 | "keyval": {
49 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n"
50 | }
51 | },
52 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": {
53 | "keytype": "ecdsa-sha2-nistp256",
54 | "scheme": "ecdsa-sha2-nistp256",
55 | "keyid_hash_algorithms": [
56 | "sha256",
57 | "sha512"
58 | ],
59 | "keyval": {
60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n"
61 | }
62 | },
63 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": {
64 | "keytype": "ecdsa-sha2-nistp256",
65 | "scheme": "ecdsa-sha2-nistp256",
66 | "keyid_hash_algorithms": [
67 | "sha256",
68 | "sha512"
69 | ],
70 | "keyval": {
71 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n"
72 | }
73 | },
74 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": {
75 | "keytype": "ecdsa-sha2-nistp256",
76 | "scheme": "ecdsa-sha2-nistp256",
77 | "keyid_hash_algorithms": [
78 | "sha256",
79 | "sha512"
80 | ],
81 | "keyval": {
82 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n"
83 | }
84 | }
85 | },
86 | "roles": {
87 | "root": {
88 | "keyids": [
89 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
90 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
91 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f",
92 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
93 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de"
94 | ],
95 | "threshold": 3
96 | },
97 | "snapshot": {
98 | "keyids": [
99 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b"
100 | ],
101 | "threshold": 1
102 | },
103 | "targets": {
104 | "keyids": [
105 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
106 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
107 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f",
108 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
109 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de"
110 | ],
111 | "threshold": 3
112 | },
113 | "timestamp": {
114 | "keyids": [
115 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a"
116 | ],
117 | "threshold": 1
118 | }
119 | },
120 | "consistent_snapshot": true
121 | },
122 | "signatures": [
123 | {
124 | "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
125 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a"
126 | },
127 | {
128 | "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
129 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d"
130 | },
131 | {
132 | "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
133 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b"
134 | },
135 | {
136 | "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de",
137 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9"
138 | },
139 | {
140 | "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
141 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a"
142 | },
143 | {
144 | "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
145 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d"
146 | },
147 | {
148 | "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
149 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b"
150 | },
151 | {
152 | "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90",
153 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9"
154 | }
155 | ]
156 | }
--------------------------------------------------------------------------------
/examples/multirepo/repository/targets/staging/e2a930b2d1d4053dd56e8faf66fd113658545d522e35d222ccf58fea87ccccf4.root.json:
--------------------------------------------------------------------------------
1 | {
2 | "signed": {
3 | "_type": "root",
4 | "spec_version": "1.0",
5 | "version": 5,
6 | "expires": "2023-04-18T18:13:43Z",
7 | "keys": {
8 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": {
9 | "keytype": "ecdsa-sha2-nistp256",
10 | "scheme": "ecdsa-sha2-nistp256",
11 | "keyid_hash_algorithms": [
12 | "sha256",
13 | "sha512"
14 | ],
15 | "keyval": {
16 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n"
17 | }
18 | },
19 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": {
20 | "keytype": "ecdsa-sha2-nistp256",
21 | "scheme": "ecdsa-sha2-nistp256",
22 | "keyid_hash_algorithms": [
23 | "sha256",
24 | "sha512"
25 | ],
26 | "keyval": {
27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n"
28 | }
29 | },
30 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": {
31 | "keytype": "ecdsa-sha2-nistp256",
32 | "scheme": "ecdsa-sha2-nistp256",
33 | "keyid_hash_algorithms": [
34 | "sha256",
35 | "sha512"
36 | ],
37 | "keyval": {
38 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n"
39 | }
40 | },
41 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": {
42 | "keytype": "ecdsa-sha2-nistp256",
43 | "scheme": "ecdsa-sha2-nistp256",
44 | "keyid_hash_algorithms": [
45 | "sha256",
46 | "sha512"
47 | ],
48 | "keyval": {
49 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n"
50 | }
51 | },
52 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": {
53 | "keytype": "ecdsa-sha2-nistp256",
54 | "scheme": "ecdsa-sha2-nistp256",
55 | "keyid_hash_algorithms": [
56 | "sha256",
57 | "sha512"
58 | ],
59 | "keyval": {
60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n"
61 | }
62 | },
63 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": {
64 | "keytype": "ecdsa-sha2-nistp256",
65 | "scheme": "ecdsa-sha2-nistp256",
66 | "keyid_hash_algorithms": [
67 | "sha256",
68 | "sha512"
69 | ],
70 | "keyval": {
71 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n"
72 | }
73 | },
74 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": {
75 | "keytype": "ecdsa-sha2-nistp256",
76 | "scheme": "ecdsa-sha2-nistp256",
77 | "keyid_hash_algorithms": [
78 | "sha256",
79 | "sha512"
80 | ],
81 | "keyval": {
82 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n"
83 | }
84 | }
85 | },
86 | "roles": {
87 | "root": {
88 | "keyids": [
89 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
90 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
91 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f",
92 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
93 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de"
94 | ],
95 | "threshold": 3
96 | },
97 | "snapshot": {
98 | "keyids": [
99 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b"
100 | ],
101 | "threshold": 1
102 | },
103 | "targets": {
104 | "keyids": [
105 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
106 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
107 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f",
108 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
109 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de"
110 | ],
111 | "threshold": 3
112 | },
113 | "timestamp": {
114 | "keyids": [
115 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a"
116 | ],
117 | "threshold": 1
118 | }
119 | },
120 | "consistent_snapshot": true
121 | },
122 | "signatures": [
123 | {
124 | "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
125 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a"
126 | },
127 | {
128 | "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
129 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d"
130 | },
131 | {
132 | "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
133 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b"
134 | },
135 | {
136 | "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de",
137 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9"
138 | },
139 | {
140 | "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
141 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a"
142 | },
143 | {
144 | "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
145 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d"
146 | },
147 | {
148 | "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
149 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b"
150 | },
151 | {
152 | "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90",
153 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9"
154 | }
155 | ]
156 | }
--------------------------------------------------------------------------------
/examples/multirepo/repository/targets/staging/root.json:
--------------------------------------------------------------------------------
1 | {
2 | "signed": {
3 | "_type": "root",
4 | "spec_version": "1.0",
5 | "version": 5,
6 | "expires": "2023-04-18T18:13:43Z",
7 | "keys": {
8 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99": {
9 | "keytype": "ecdsa-sha2-nistp256",
10 | "scheme": "ecdsa-sha2-nistp256",
11 | "keyid_hash_algorithms": [
12 | "sha256",
13 | "sha512"
14 | ],
15 | "keyval": {
16 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEXsz3SZXFb8jMV42j6pJlyjbjR8K\nN3Bwocexq6LMIb5qsWKOQvLN16NUefLc4HswOoumRsVVaajSpQS6fobkRw==\n-----END PUBLIC KEY-----\n"
17 | }
18 | },
19 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de": {
20 | "keytype": "ecdsa-sha2-nistp256",
21 | "scheme": "ecdsa-sha2-nistp256",
22 | "keyid_hash_algorithms": [
23 | "sha256",
24 | "sha512"
25 | ],
26 | "keyval": {
27 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0ghrh92Lw1Yr3idGV5WqCtMDB8Cx\n+D8hdC4w2ZLNIplVRoVGLskYa3gheMyOjiJ8kPi15aQ2//7P+oj7UvJPGw==\n-----END PUBLIC KEY-----\n"
28 | }
29 | },
30 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b": {
31 | "keytype": "ecdsa-sha2-nistp256",
32 | "scheme": "ecdsa-sha2-nistp256",
33 | "keyid_hash_algorithms": [
34 | "sha256",
35 | "sha512"
36 | ],
37 | "keyval": {
38 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELrWvNt94v4R085ELeeCMxHp7PldF\n0/T1GxukUh2ODuggLGJE0pc1e8CSBf6CS91Fwo9FUOuRsjBUld+VqSyCdQ==\n-----END PUBLIC KEY-----\n"
39 | }
40 | },
41 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b": {
42 | "keytype": "ecdsa-sha2-nistp256",
43 | "scheme": "ecdsa-sha2-nistp256",
44 | "keyid_hash_algorithms": [
45 | "sha256",
46 | "sha512"
47 | ],
48 | "keyval": {
49 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinikSsAQmYkNeH5eYq/CnIzLaacO\nxlSaawQDOwqKy/tCqxq5xxPSJc21K4WIhs9GyOkKfzueY3GILzcMJZ4cWw==\n-----END PUBLIC KEY-----\n"
50 | }
51 | },
52 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a": {
53 | "keytype": "ecdsa-sha2-nistp256",
54 | "scheme": "ecdsa-sha2-nistp256",
55 | "keyid_hash_algorithms": [
56 | "sha256",
57 | "sha512"
58 | ],
59 | "keyval": {
60 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWRiGr5+j+3J5SsH+Ztr5nE2H2wO7\nBV+nO3s93gLca18qTOzHY1oWyAGDykMSsGTUBSt9D+An0KfKsD2mfSM42Q==\n-----END PUBLIC KEY-----\n"
61 | }
62 | },
63 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f": {
64 | "keytype": "ecdsa-sha2-nistp256",
65 | "scheme": "ecdsa-sha2-nistp256",
66 | "keyid_hash_algorithms": [
67 | "sha256",
68 | "sha512"
69 | ],
70 | "keyval": {
71 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzBzVOmHCPojMVLSI364WiiV8NPrD\n6IgRxVliskz/v+y3JER5mcVGcONliDcWMC5J2lfHmjPNPhb4H7xm8LzfSA==\n-----END PUBLIC KEY-----\n"
72 | }
73 | },
74 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c": {
75 | "keytype": "ecdsa-sha2-nistp256",
76 | "scheme": "ecdsa-sha2-nistp256",
77 | "keyid_hash_algorithms": [
78 | "sha256",
79 | "sha512"
80 | ],
81 | "keyval": {
82 | "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEy8XKsmhBYDI8Jc0GwzBxeKax0cm5\nSTKEU65HPFunUn41sT8pi0FjM4IkHz/YUmwmLUO0Wt7lxhj6BkLIK4qYAw==\n-----END PUBLIC KEY-----\n"
83 | }
84 | }
85 | },
86 | "roles": {
87 | "root": {
88 | "keyids": [
89 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
90 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
91 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f",
92 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
93 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de"
94 | ],
95 | "threshold": 3
96 | },
97 | "snapshot": {
98 | "keyids": [
99 | "45b283825eb184cabd582eb17b74fc8ed404f68cf452acabdad2ed6f90ce216b"
100 | ],
101 | "threshold": 1
102 | },
103 | "targets": {
104 | "keyids": [
105 | "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
106 | "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
107 | "f5312f542c21273d9485a49394386c4575804770667f2ddb59b3bf0669fddd2f",
108 | "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
109 | "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de"
110 | ],
111 | "threshold": 3
112 | },
113 | "timestamp": {
114 | "keyids": [
115 | "e1863ba02070322ebc626dcecf9d881a3a38c35c3b41a83765b6ad6c37eaec2a"
116 | ],
117 | "threshold": 1
118 | }
119 | },
120 | "consistent_snapshot": true
121 | },
122 | "signatures": [
123 | {
124 | "keyid": "ff51e17fcf253119b7033f6f57512631da4a0969442afcf9fc8b141c7f2be99c",
125 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a"
126 | },
127 | {
128 | "keyid": "25a0eb450fd3ee2bd79218c963dce3f1cc6118badf251bf149f0bd07d5cabe99",
129 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d"
130 | },
131 | {
132 | "keyid": "7f7513b25429a64473e10ce3ad2f3da372bbdd14b65d07bbaf547e7c8bbbe62b",
133 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b"
134 | },
135 | {
136 | "keyid": "2e61cd0cbf4a8f45809bda9f7f78c0d33ad11842ff94ae340873e2664dc843de",
137 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9"
138 | },
139 | {
140 | "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
141 | "sig": "3045022100fc1c2be509ce50ea917bbad1d9efe9d96c8c2ebea04af2717aa3d9c6fe617a75022012eef282a19f2d8bd4818aa333ef48a06489f49d4d34a20b8fe8fc867bb25a7a"
142 | },
143 | {
144 | "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
145 | "sig": "30450221008a4392ae5057fc00778b651e61fea244766a4ae58db84d9f1d3810720ab0f3b702207c49e59e8031318caf02252ecea1281cecc1e5986c309a9cef61f455ecf7165d"
146 | },
147 | {
148 | "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
149 | "sig": "3046022100da1b8dc5d53aaffbbfac98de3e23ee2d2ad3446a7bed09fac0f88bae19be2587022100b681c046afc3919097dfe794e0d819be891e2e850aade315bec06b0c4dea221b"
150 | },
151 | {
152 | "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90",
153 | "sig": "3046022100b534e0030e1b271133ecfbdf3ba9fbf3becb3689abea079a2150afbb63cdb7c70221008c39a718fd9495f249b4ab8788d5b9dc269f0868dbe38b272f48207359d3ded9"
154 | }
155 | ]
156 | }
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/rdimitrov/go-tuf-metadata
2 |
3 | go 1.21.5
4 |
5 | require (
6 | github.com/go-logr/stdr v1.2.2
7 | github.com/secure-systems-lab/go-securesystemslib v0.8.0
8 | github.com/sigstore/sigstore v1.8.0
9 | github.com/sirupsen/logrus v1.9.3
10 | github.com/spf13/cobra v1.8.0
11 | github.com/stretchr/testify v1.8.4
12 | golang.org/x/crypto v0.18.0
13 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29
14 | golang.org/x/sys v0.16.0
15 | )
16 |
17 | require (
18 | github.com/davecgh/go-spew v1.1.1 // indirect
19 | github.com/go-logr/logr v1.3.0 // indirect
20 | github.com/google/go-containerregistry v0.17.0 // indirect
21 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
22 | github.com/kr/pretty v0.3.1 // indirect
23 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect
24 | github.com/opencontainers/go-digest v1.0.0 // indirect
25 | github.com/pmezard/go-difflib v1.0.0 // indirect
26 | github.com/spf13/pflag v1.0.5 // indirect
27 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
28 | golang.org/x/term v0.16.0 // indirect
29 | google.golang.org/grpc v1.56.3 // indirect
30 | gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
31 | gopkg.in/yaml.v3 v3.0.1 // indirect
32 | )
33 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
3 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
4 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
11 | github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
12 | github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
13 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
14 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
15 | github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
16 | github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
17 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
18 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
19 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
20 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
21 | github.com/google/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk=
22 | github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
23 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
24 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
25 | github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
26 | github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
27 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
28 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
29 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
30 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
31 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e h1:RLTpX495BXToqxpM90Ws4hXEo4Wfh81jr9DX1n/4WOo=
32 | github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e/go.mod h1:EAuqr9VFWxBi9nD5jc/EA2MT1RFty9288TF6zdtYoCU=
33 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
34 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
35 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
36 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
37 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
38 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
40 | github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
41 | github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
42 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
43 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
44 | github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
45 | github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
46 | github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
47 | github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
48 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
49 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
50 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
51 | github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA=
52 | github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU=
53 | github.com/sigstore/sigstore v1.8.0 h1:sSRWXv1JiDsK4T2wNWVYcvKCgxcSrhQ/QUJxsfCO4OM=
54 | github.com/sigstore/sigstore v1.8.0/go.mod h1:l12B1gFlLIpBIVeqk/q1Lb+6YSOGNuN3xLExIjYH+qc=
55 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
56 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
57 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
58 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
59 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
60 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
61 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
62 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
63 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
64 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
65 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
66 | github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
67 | go.opentelemetry.io/otel v1.15.0 h1:NIl24d4eiLJPM0vKn4HjLYM+UZf6gSfi9Z+NmCxkWbk=
68 | go.opentelemetry.io/otel v1.15.0/go.mod h1:qfwLEbWhLPk5gyWrne4XnF0lC8wtywbuJbgfAE3zbek=
69 | go.opentelemetry.io/otel/trace v1.15.0 h1:5Fwje4O2ooOxkfyqI/kJwxWotggDLix4BSAvpE1wlpo=
70 | go.opentelemetry.io/otel/trace v1.15.0/go.mod h1:CUsmE2Ht1CRkvE8OsMESvraoZrrcgD1J2W8GV1ev0Y4=
71 | golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
72 | golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
73 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
74 | golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
75 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
76 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
77 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
78 | golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
79 | golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
80 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
81 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
82 | google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
83 | google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
84 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
85 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
86 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
87 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
88 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
89 | gopkg.in/go-jose/go-jose.v2 v2.6.1 h1:qEzJlIDmG9q5VO0M/o8tGS65QMHMS1w01TQJB1VPJ4U=
90 | gopkg.in/go-jose/go-jose.v2 v2.6.1/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
91 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
92 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
93 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
94 |
--------------------------------------------------------------------------------
/metadata/config/config.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package config
13 |
14 | import (
15 | "net/url"
16 | "os"
17 |
18 | "github.com/rdimitrov/go-tuf-metadata/metadata/fetcher"
19 | )
20 |
21 | type UpdaterConfig struct {
22 | // TUF configuration
23 | MaxRootRotations int64
24 | MaxDelegations int
25 | RootMaxLength int64
26 | TimestampMaxLength int64
27 | SnapshotMaxLength int64
28 | TargetsMaxLength int64
29 | // Updater configuration
30 | Fetcher fetcher.Fetcher
31 | LocalTrustedRoot []byte
32 | LocalMetadataDir string
33 | LocalTargetsDir string
34 | RemoteMetadataURL string
35 | RemoteTargetsURL string
36 | DisableLocalCache bool
37 | PrefixTargetsWithHash bool
38 | // UnsafeLocalMode only uses the metadata as written on disk
39 | // if the metadata is incomplete, calling updater.Refresh will fail
40 | UnsafeLocalMode bool
41 | }
42 |
43 | // New creates a new UpdaterConfig instance used by the Updater to
44 | // store configuration
45 | func New(remoteURL string, rootBytes []byte) (*UpdaterConfig, error) {
46 | // Default URL for target files - /targets
47 | targetsURL, err := url.JoinPath(remoteURL, "targets")
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | return &UpdaterConfig{
53 | // TUF configuration
54 | MaxRootRotations: 32,
55 | MaxDelegations: 32,
56 | RootMaxLength: 512000, // bytes
57 | TimestampMaxLength: 16384, // bytes
58 | SnapshotMaxLength: 2000000, // bytes
59 | TargetsMaxLength: 5000000, // bytes
60 | // Updater configuration
61 | Fetcher: &fetcher.DefaultFetcher{}, // use the default built-in download fetcher
62 | LocalTrustedRoot: rootBytes, // trusted root.json
63 | RemoteMetadataURL: remoteURL, // URL of where the TUF metadata is
64 | RemoteTargetsURL: targetsURL, // URL of where the target files should be downloaded from
65 | DisableLocalCache: false, // enable local caching of trusted metadata
66 | PrefixTargetsWithHash: true, // use hash-prefixed target files with consistent snapshots
67 | UnsafeLocalMode: false,
68 | }, nil
69 | }
70 |
71 | func (cfg *UpdaterConfig) EnsurePathsExist() error {
72 | if cfg.DisableLocalCache {
73 | return nil
74 | }
75 |
76 | for _, path := range []string{cfg.LocalMetadataDir, cfg.LocalTargetsDir} {
77 | if err := os.MkdirAll(path, os.ModePerm); err != nil {
78 | return err
79 | }
80 | }
81 |
82 | return nil
83 | }
84 |
--------------------------------------------------------------------------------
/metadata/config/config_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package config
13 |
14 | import (
15 | "net/url"
16 | "os"
17 | "path/filepath"
18 | "testing"
19 |
20 | "github.com/rdimitrov/go-tuf-metadata/metadata/fetcher"
21 | "github.com/stretchr/testify/assert"
22 | )
23 |
24 | func TestNewUpdaterConfig(t *testing.T) {
25 | // setup testing table (tt) and create subtest for each entry
26 | for _, tt := range []struct {
27 | name string
28 | desc string
29 | remoteURL string
30 | rootBytes []byte
31 | config *UpdaterConfig
32 | wantErr error
33 | }{
34 | {
35 | name: "success",
36 | desc: "No errors expected",
37 | remoteURL: "somepath",
38 | rootBytes: []byte("somerootbytes"),
39 | config: &UpdaterConfig{
40 | MaxRootRotations: 32,
41 | MaxDelegations: 32,
42 | RootMaxLength: 512000,
43 | TimestampMaxLength: 16384,
44 | SnapshotMaxLength: 2000000,
45 | TargetsMaxLength: 5000000,
46 | Fetcher: &fetcher.DefaultFetcher{},
47 | LocalTrustedRoot: []byte("somerootbytes"),
48 | RemoteMetadataURL: "somepath",
49 | RemoteTargetsURL: "somepath/targets",
50 | DisableLocalCache: false,
51 | PrefixTargetsWithHash: true,
52 | },
53 | wantErr: nil,
54 | },
55 | {
56 | name: "invalid character in URL",
57 | desc: "Invalid ASCII control sequence in input",
58 | remoteURL: string([]byte{0x7f}),
59 | rootBytes: []byte("somerootbytes"),
60 | config: nil,
61 | wantErr: &url.Error{}, // just make sure this is non-nil, url pkg has no exported errors
62 | },
63 | } {
64 | t.Run(tt.name, func(t *testing.T) {
65 | // this will only be printed if run in verbose mode or if test fails
66 | t.Logf("Desc: %s", tt.desc)
67 | // run the function under test
68 | updaterConfig, err := New(tt.remoteURL, tt.rootBytes)
69 | // special case if we expect no error
70 | if tt.wantErr == nil {
71 | assert.NoErrorf(t, err, "expected no error but got %v", err)
72 | assert.EqualExportedValuesf(t, *tt.config, *updaterConfig, "expected %#+v but got %#+v", tt.config, updaterConfig)
73 | return
74 | }
75 | // compare the error with our expected error
76 | assert.Nilf(t, updaterConfig, "expected nil but got %#+v", updaterConfig)
77 | assert.Errorf(t, err, "expected %v but got %v", tt.wantErr, err)
78 | })
79 | }
80 | }
81 |
82 | func TestEnsurePathsExist(t *testing.T) {
83 | // setup testing table (tt) and create subtest for each entry
84 | for _, tt := range []struct {
85 | name string
86 | desc string
87 | config *UpdaterConfig
88 | setup func(t *testing.T, cfg *UpdaterConfig)
89 | wantErr error
90 | }{
91 | {
92 | name: "success",
93 | desc: "No errors expected",
94 | config: &UpdaterConfig{
95 | DisableLocalCache: false,
96 | },
97 | setup: func(t *testing.T, cfg *UpdaterConfig) {
98 | t.Helper()
99 | tmp := t.TempDir()
100 | cfg.LocalTargetsDir = filepath.Join(tmp, "targets")
101 | cfg.LocalMetadataDir = filepath.Join(tmp, "metadata")
102 | },
103 | wantErr: nil,
104 | },
105 | {
106 | name: "path not exist",
107 | desc: "No local directories error",
108 | config: &UpdaterConfig{
109 | DisableLocalCache: false,
110 | },
111 | setup: func(t *testing.T, cfg *UpdaterConfig) {
112 | t.Helper()
113 | },
114 | wantErr: os.ErrNotExist,
115 | },
116 | {
117 | name: "no local cache",
118 | desc: "Test if method no-op works",
119 | config: &UpdaterConfig{
120 | DisableLocalCache: true,
121 | },
122 | setup: func(t *testing.T, cfg *UpdaterConfig) {
123 | t.Helper()
124 | },
125 | wantErr: nil,
126 | },
127 | } {
128 | t.Run(tt.name, func(t *testing.T) {
129 | // this will only be printed if run in verbose mode or if test fails
130 | t.Logf("Desc: %s", tt.desc)
131 | // run special test setup in case it is needed for any subtest
132 | tt.setup(t, tt.config)
133 | // run the method under test
134 | err := tt.config.EnsurePathsExist()
135 | // special case if we expect no error
136 | if tt.wantErr == nil {
137 | assert.NoErrorf(t, err, "expected no error but got %v", err)
138 | return
139 | }
140 | // compare the error with our expected error
141 | assert.ErrorIsf(t, err, tt.wantErr, "expected %v but got %v", tt.wantErr, err)
142 | })
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/metadata/errors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package metadata
13 |
14 | import (
15 | "fmt"
16 | )
17 |
18 | // Define TUF error types used inside the new modern implementation.
19 | // The names chosen for TUF error types should start in 'Err' except where
20 | // there is a good reason not to, and provide that reason in those cases.
21 |
22 | // Repository errors
23 |
24 | // ErrRepository - an error with a repository's state, such as a missing file.
25 | // It covers all exceptions that come from the repository side when
26 | // looking from the perspective of users of metadata API or client
27 | type ErrRepository struct {
28 | Msg string
29 | }
30 |
31 | func (e ErrRepository) Error() string {
32 | return fmt.Sprintf("repository error: %s", e.Msg)
33 | }
34 |
35 | // ErrUnsignedMetadata - An error about metadata object with insufficient threshold of signatures
36 | type ErrUnsignedMetadata struct {
37 | Msg string
38 | }
39 |
40 | func (e ErrUnsignedMetadata) Error() string {
41 | return fmt.Sprintf("unsigned metadata error: %s", e.Msg)
42 | }
43 |
44 | // ErrUnsignedMetadata is a subset of ErrRepository
45 | func (e ErrUnsignedMetadata) Is(target error) bool {
46 | return target == ErrRepository{} || target == ErrUnsignedMetadata{}
47 | }
48 |
49 | // ErrBadVersionNumber - An error for metadata that contains an invalid version number
50 | type ErrBadVersionNumber struct {
51 | Msg string
52 | }
53 |
54 | func (e ErrBadVersionNumber) Error() string {
55 | return fmt.Sprintf("bad version number error: %s", e.Msg)
56 | }
57 |
58 | // ErrBadVersionNumber is a subset of ErrRepository
59 | func (e ErrBadVersionNumber) Is(target error) bool {
60 | return target == ErrRepository{} || target == ErrBadVersionNumber{}
61 | }
62 |
63 | // ErrEqualVersionNumber - An error for metadata containing a previously verified version number
64 | type ErrEqualVersionNumber struct {
65 | Msg string
66 | }
67 |
68 | func (e ErrEqualVersionNumber) Error() string {
69 | return fmt.Sprintf("equal version number error: %s", e.Msg)
70 | }
71 |
72 | // ErrEqualVersionNumber is a subset of both ErrRepository and ErrBadVersionNumber
73 | func (e ErrEqualVersionNumber) Is(target error) bool {
74 | return target == ErrRepository{} || target == ErrBadVersionNumber{} || target == ErrEqualVersionNumber{}
75 | }
76 |
77 | // ErrExpiredMetadata - Indicate that a TUF Metadata file has expired
78 | type ErrExpiredMetadata struct {
79 | Msg string
80 | }
81 |
82 | func (e ErrExpiredMetadata) Error() string {
83 | return fmt.Sprintf("expired metadata error: %s", e.Msg)
84 | }
85 |
86 | // ErrExpiredMetadata is a subset of ErrRepository
87 | func (e ErrExpiredMetadata) Is(target error) bool {
88 | return target == ErrRepository{} || target == ErrExpiredMetadata{}
89 | }
90 |
91 | // ErrLengthOrHashMismatch - An error while checking the length and hash values of an object
92 | type ErrLengthOrHashMismatch struct {
93 | Msg string
94 | }
95 |
96 | func (e ErrLengthOrHashMismatch) Error() string {
97 | return fmt.Sprintf("length/hash verification error: %s", e.Msg)
98 | }
99 |
100 | // ErrLengthOrHashMismatch is a subset of ErrRepository
101 | func (e ErrLengthOrHashMismatch) Is(target error) bool {
102 | return target == ErrRepository{} || target == ErrLengthOrHashMismatch{}
103 | }
104 |
105 | // Download errors
106 |
107 | // ErrDownload - An error occurred while attempting to download a file
108 | type ErrDownload struct {
109 | Msg string
110 | }
111 |
112 | func (e ErrDownload) Error() string {
113 | return fmt.Sprintf("download error: %s", e.Msg)
114 | }
115 |
116 | // ErrDownloadLengthMismatch - Indicate that a mismatch of lengths was seen while downloading a file
117 | type ErrDownloadLengthMismatch struct {
118 | Msg string
119 | }
120 |
121 | func (e ErrDownloadLengthMismatch) Error() string {
122 | return fmt.Sprintf("download length mismatch error: %s", e.Msg)
123 | }
124 |
125 | // ErrDownloadLengthMismatch is a subset of ErrDownload
126 | func (e ErrDownloadLengthMismatch) Is(target error) bool {
127 | return target == ErrDownload{} || target == ErrDownloadLengthMismatch{}
128 | }
129 |
130 | // ErrDownloadHTTP - Returned by Fetcher interface implementations for HTTP errors
131 | type ErrDownloadHTTP struct {
132 | StatusCode int
133 | URL string
134 | }
135 |
136 | func (e ErrDownloadHTTP) Error() string {
137 | return fmt.Sprintf("failed to download %s, http status code: %d", e.URL, e.StatusCode)
138 | }
139 |
140 | // ErrDownloadHTTP is a subset of ErrDownload
141 | func (e ErrDownloadHTTP) Is(target error) bool {
142 | return target == ErrDownload{} || target == ErrDownloadHTTP{}
143 | }
144 |
145 | // ValueError
146 | type ErrValue struct {
147 | Msg string
148 | }
149 |
150 | func (e ErrValue) Error() string {
151 | return fmt.Sprintf("value error: %s", e.Msg)
152 | }
153 |
154 | // TypeError
155 | type ErrType struct {
156 | Msg string
157 | }
158 |
159 | func (e ErrType) Error() string {
160 | return fmt.Sprintf("type error: %s", e.Msg)
161 | }
162 |
163 | // RuntimeError
164 | type ErrRuntime struct {
165 | Msg string
166 | }
167 |
168 | func (e ErrRuntime) Error() string {
169 | return fmt.Sprintf("runtime error: %s", e.Msg)
170 | }
171 |
--------------------------------------------------------------------------------
/metadata/fetcher/fetcher.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package fetcher
13 |
14 | import (
15 | "fmt"
16 | "io"
17 | "net/http"
18 | "strconv"
19 | "time"
20 |
21 | "github.com/rdimitrov/go-tuf-metadata/metadata"
22 | )
23 |
24 | // Fetcher interface
25 | type Fetcher interface {
26 | DownloadFile(urlPath string, maxLength int64, timeout time.Duration) ([]byte, error)
27 | }
28 |
29 | // DefaultFetcher implements Fetcher
30 | type DefaultFetcher struct {
31 | httpUserAgent string
32 | }
33 |
34 | // DownloadFile downloads a file from urlPath, errors out if it failed,
35 | // its length is larger than maxLength or the timeout is reached.
36 | func (d *DefaultFetcher) DownloadFile(urlPath string, maxLength int64, timeout time.Duration) ([]byte, error) {
37 | client := &http.Client{Timeout: timeout}
38 | req, err := http.NewRequest("GET", urlPath, nil)
39 | if err != nil {
40 | return nil, err
41 | }
42 | // Use in case of multiple sessions.
43 | if d.httpUserAgent != "" {
44 | req.Header.Set("User-Agent", d.httpUserAgent)
45 | }
46 | // Execute the request.
47 | res, err := client.Do(req)
48 | if err != nil {
49 | return nil, err
50 | }
51 | defer res.Body.Close()
52 | // Handle HTTP status codes.
53 | if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusForbidden || res.StatusCode != http.StatusOK {
54 | return nil, metadata.ErrDownloadHTTP{StatusCode: res.StatusCode, URL: urlPath}
55 | }
56 | var length int64
57 | // Get content length from header (might not be accurate, -1 or not set).
58 | if header := res.Header.Get("Content-Length"); header != "" {
59 | length, err = strconv.ParseInt(header, 10, 0)
60 | if err != nil {
61 | return nil, err
62 | }
63 | // Error if the reported size is greater than what is expected.
64 | if length > maxLength {
65 | return nil, metadata.ErrDownloadLengthMismatch{Msg: fmt.Sprintf("download failed for %s, length %d is larger than expected %d", urlPath, length, maxLength)}
66 | }
67 | }
68 | // Although the size has been checked above, use a LimitReader in case
69 | // the reported size is inaccurate, or size is -1 which indicates an
70 | // unknown length. We read maxLength + 1 in order to check if the read data
71 | // surpased our set limit.
72 | data, err := io.ReadAll(io.LimitReader(res.Body, maxLength+1))
73 | if err != nil {
74 | return nil, err
75 | }
76 | // Error if the reported size is greater than what is expected.
77 | length = int64(len(data))
78 | if length > maxLength {
79 | return nil, metadata.ErrDownloadLengthMismatch{Msg: fmt.Sprintf("download failed for %s, length %d is larger than expected %d", urlPath, length, maxLength)}
80 | }
81 |
82 | return data, nil
83 | }
84 |
--------------------------------------------------------------------------------
/metadata/fetcher/fetcher_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package fetcher
13 |
14 | import (
15 | "net/url"
16 | "testing"
17 | "time"
18 |
19 | "github.com/rdimitrov/go-tuf-metadata/metadata"
20 | "github.com/stretchr/testify/assert"
21 | )
22 |
23 | func TestDownLoadFile(t *testing.T) {
24 | for _, tt := range []struct {
25 | name string
26 | desc string
27 | url string
28 | maxLength int64
29 | timeout time.Duration
30 | data []byte
31 | wantErr error
32 | }{
33 | {
34 | name: "success",
35 | desc: "No errors expected",
36 | url: "https://jku.github.io/tuf-demo/metadata/1.root.json",
37 | maxLength: 512000,
38 | timeout: 15 * time.Second,
39 | data: []byte{123, 10, 32, 34, 115, 105, 103, 110, 97, 116, 117, 114, 101, 115, 34, 58, 32, 91, 10, 32, 32, 123, 10, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 52, 99, 53, 54, 100, 101, 53, 98, 54, 50, 102, 100, 48, 54, 52, 102, 99, 57, 52, 52, 53, 51, 98, 54, 56, 48, 100, 102, 100, 51, 102, 97, 102, 54, 97, 48, 49, 98, 97, 97, 97, 98, 51, 98, 101, 97, 99, 50, 57, 54, 57, 50, 48, 102, 48, 99, 99, 102, 97, 50, 50, 55, 55, 53, 34, 44, 10, 32, 32, 32, 34, 115, 105, 103, 34, 58, 32, 34, 57, 54, 57, 98, 100, 101, 99, 51, 54, 100, 54, 102, 51, 100, 99, 53, 57, 99, 49, 55, 50, 48, 50, 97, 56, 53, 50, 56, 98, 98, 51, 53, 54, 97, 54, 101, 97, 53, 52, 100, 55, 99, 99, 57, 54, 98, 98, 51, 55, 49, 101, 101, 101, 52, 56, 101, 50, 52, 48, 49, 57, 50, 98, 99, 97, 99, 100, 53, 48, 53, 49, 51, 56, 56, 50, 52, 53, 49, 52, 52, 97, 97, 99, 97, 49, 48, 51, 57, 100, 51, 101, 98, 55, 48, 54, 50, 101, 48, 56, 55, 54, 55, 57, 53, 101, 56, 49, 101, 49, 100, 53, 54, 54, 102, 56, 100, 101, 100, 50, 99, 50, 56, 52, 97, 101, 101, 48, 102, 34, 10, 32, 32, 125, 44, 10, 32, 32, 123, 10, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 52, 53, 97, 57, 53, 55, 55, 99, 97, 52, 56, 51, 102, 51, 53, 56, 98, 100, 97, 52, 97, 50, 49, 97, 102, 57, 51, 98, 55, 54, 54, 48, 98, 56, 50, 98, 100, 57, 99, 48, 101, 49, 57, 51, 48, 97, 54, 98, 55, 100, 53, 50, 49, 98, 52, 50, 56, 57, 55, 97, 48, 102, 97, 51, 34, 44, 10, 32, 32, 32, 34, 115, 105, 103, 34, 58, 32, 34, 101, 100, 102, 97, 102, 51, 99, 53, 51, 56, 97, 48, 50, 51, 101, 55, 99, 102, 53, 98, 50, 54, 51, 97, 101, 52, 101, 54, 51, 99, 51, 51, 99, 57, 52, 97, 50, 98, 102, 99, 57, 102, 101, 56, 48, 56, 53, 57, 99, 52, 57, 51, 52, 100, 52, 97, 54, 54, 98, 48, 49, 53, 98, 54, 53, 98, 57, 48, 49, 101, 99, 53, 100, 53, 50, 57, 48, 101, 97, 53, 50, 52, 51, 51, 57, 101, 54, 97, 52, 48, 98, 53, 98, 56, 100, 98, 56, 97, 57, 53, 54, 49, 102, 51, 99, 49, 48, 51, 101, 50, 97, 101, 56, 55, 98, 57, 101, 101, 48, 51, 50, 97, 57, 101, 51, 48, 48, 49, 34, 10, 32, 32, 125, 10, 32, 93, 44, 10, 32, 34, 115, 105, 103, 110, 101, 100, 34, 58, 32, 123, 10, 32, 32, 34, 95, 116, 121, 112, 101, 34, 58, 32, 34, 114, 111, 111, 116, 34, 44, 10, 32, 32, 34, 99, 111, 110, 115, 105, 115, 116, 101, 110, 116, 95, 115, 110, 97, 112, 115, 104, 111, 116, 34, 58, 32, 116, 114, 117, 101, 44, 10, 32, 32, 34, 101, 120, 112, 105, 114, 101, 115, 34, 58, 32, 34, 50, 48, 50, 49, 45, 48, 55, 45, 49, 56, 84, 49, 51, 58, 51, 55, 58, 51, 56, 90, 34, 44, 10, 32, 32, 34, 107, 101, 121, 115, 34, 58, 32, 123, 10, 32, 32, 32, 34, 51, 56, 54, 48, 48, 56, 50, 48, 102, 49, 49, 97, 53, 102, 55, 100, 55, 102, 102, 52, 50, 101, 54, 100, 102, 99, 57, 98, 48, 51, 102, 100, 54, 48, 50, 55, 50, 97, 51, 98, 101, 54, 102, 56, 57, 53, 100, 97, 50, 100, 56, 56, 50, 99, 101, 97, 56, 98, 98, 49, 101, 50, 48, 102, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 51, 56, 54, 48, 48, 56, 50, 48, 102, 49, 49, 97, 53, 102, 55, 100, 55, 102, 102, 52, 50, 101, 54, 100, 102, 99, 57, 98, 48, 51, 102, 100, 54, 48, 50, 55, 50, 97, 51, 98, 101, 54, 102, 56, 57, 53, 100, 97, 50, 100, 56, 56, 50, 99, 101, 97, 56, 98, 98, 49, 101, 50, 48, 102, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 53, 48, 102, 52, 56, 54, 53, 57, 54, 54, 53, 98, 51, 101, 101, 98, 50, 50, 100, 52, 57, 51, 55, 52, 101, 49, 56, 51, 49, 57, 55, 101, 101, 102, 56, 101, 52, 50, 56, 55, 54, 97, 53, 99, 98, 57, 48, 57, 99, 57, 49, 97, 98, 55, 55, 101, 52, 50, 98, 49, 101, 99, 99, 54, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 52, 53, 97, 57, 53, 55, 55, 99, 97, 52, 56, 51, 102, 51, 53, 56, 98, 100, 97, 52, 97, 50, 49, 97, 102, 57, 51, 98, 55, 54, 54, 48, 98, 56, 50, 98, 100, 57, 99, 48, 101, 49, 57, 51, 48, 97, 54, 98, 55, 100, 53, 50, 49, 98, 52, 50, 56, 57, 55, 97, 48, 102, 97, 51, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 52, 53, 97, 57, 53, 55, 55, 99, 97, 52, 56, 51, 102, 51, 53, 56, 98, 100, 97, 52, 97, 50, 49, 97, 102, 57, 51, 98, 55, 54, 54, 48, 98, 56, 50, 98, 100, 57, 99, 48, 101, 49, 57, 51, 48, 97, 54, 98, 55, 100, 53, 50, 49, 98, 52, 50, 56, 57, 55, 97, 48, 102, 97, 51, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 49, 56, 101, 98, 50, 52, 56, 51, 49, 57, 54, 98, 55, 97, 97, 50, 53, 102, 97, 102, 98, 56, 49, 50, 55, 54, 99, 55, 48, 52, 102, 55, 57, 48, 51, 99, 99, 57, 98, 49, 101, 51, 52, 99, 97, 100, 99, 52, 101, 97, 102, 54, 55, 55, 98, 55, 97, 54, 55, 52, 100, 54, 102, 53, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 52, 99, 53, 54, 100, 101, 53, 98, 54, 50, 102, 100, 48, 54, 52, 102, 99, 57, 52, 52, 53, 51, 98, 54, 56, 48, 100, 102, 100, 51, 102, 97, 102, 54, 97, 48, 49, 98, 97, 97, 97, 98, 51, 98, 101, 97, 99, 50, 57, 54, 57, 50, 48, 102, 48, 99, 99, 102, 97, 50, 50, 55, 55, 53, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 52, 99, 53, 54, 100, 101, 53, 98, 54, 50, 102, 100, 48, 54, 52, 102, 99, 57, 52, 52, 53, 51, 98, 54, 56, 48, 100, 102, 100, 51, 102, 97, 102, 54, 97, 48, 49, 98, 97, 97, 97, 98, 51, 98, 101, 97, 99, 50, 57, 54, 57, 50, 48, 102, 48, 99, 99, 102, 97, 50, 50, 55, 55, 53, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 57, 50, 49, 101, 99, 99, 56, 54, 101, 101, 57, 49, 102, 100, 100, 51, 97, 53, 53, 49, 52, 48, 50, 51, 100, 102, 49, 57, 99, 100, 56, 53, 57, 49, 53, 57, 52, 54, 55, 55, 54, 52, 102, 54, 48, 102, 99, 52, 49, 101, 49, 101, 101, 97, 99, 56, 53, 48, 51, 53, 49, 49, 54, 49, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 56, 102, 51, 99, 50, 55, 57, 52, 102, 50, 52, 52, 50, 54, 48, 49, 52, 102, 99, 50, 54, 97, 100, 99, 98, 98, 56, 101, 102, 100, 57, 52, 52, 49, 49, 102, 99, 101, 56, 56, 49, 102, 97, 54, 48, 102, 99, 56, 55, 50, 53, 97, 56, 57, 57, 49, 49, 53, 55, 53, 48, 101, 102, 97, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 56, 102, 51, 99, 50, 55, 57, 52, 102, 50, 52, 52, 50, 54, 48, 49, 52, 102, 99, 50, 54, 97, 100, 99, 98, 98, 56, 101, 102, 100, 57, 52, 52, 49, 49, 102, 99, 101, 56, 56, 49, 102, 97, 54, 48, 102, 99, 56, 55, 50, 53, 97, 56, 57, 57, 49, 49, 53, 55, 53, 48, 101, 102, 97, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 56, 57, 53, 55, 54, 57, 49, 55, 100, 49, 54, 48, 50, 56, 52, 51, 56, 52, 97, 52, 55, 55, 53, 57, 101, 101, 99, 49, 102, 99, 48, 102, 53, 98, 55, 52, 54, 99, 97, 51, 100, 102, 97, 100, 56, 49, 51, 101, 101, 51, 48, 56, 55, 53, 99, 51, 50, 98, 97, 99, 51, 54, 57, 99, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 57, 100, 55, 56, 53, 52, 51, 98, 53, 48, 56, 102, 57, 57, 97, 57, 53, 97, 51, 99, 51, 49, 102, 97, 100, 51, 99, 102, 102, 101, 102, 48, 54, 52, 52, 49, 51, 52, 102, 49, 97, 48, 50, 56, 98, 51, 48, 53, 48, 49, 97, 99, 99, 49, 50, 48, 53, 56, 99, 55, 99, 51, 101, 56, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 34, 58, 32, 34, 57, 100, 55, 56, 53, 52, 51, 98, 53, 48, 56, 102, 57, 57, 97, 57, 53, 97, 51, 99, 51, 49, 102, 97, 100, 51, 99, 102, 102, 101, 102, 48, 54, 52, 52, 49, 51, 52, 102, 49, 97, 48, 50, 56, 98, 51, 48, 53, 48, 49, 97, 99, 99, 49, 50, 48, 53, 56, 99, 55, 99, 51, 101, 56, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 116, 121, 112, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 44, 10, 32, 32, 32, 32, 34, 107, 101, 121, 118, 97, 108, 34, 58, 32, 123, 10, 32, 32, 32, 32, 32, 34, 112, 117, 98, 108, 105, 99, 34, 58, 32, 34, 48, 52, 101, 102, 51, 51, 53, 54, 102, 98, 53, 99, 100, 48, 48, 57, 55, 53, 100, 102, 99, 101, 57, 102, 56, 102, 52, 50, 100, 53, 98, 49, 50, 98, 55, 98, 56, 51, 102, 56, 98, 97, 49, 53, 99, 50, 101, 57, 56, 102, 100, 48, 52, 49, 53, 49, 52, 99, 55, 52, 98, 101, 98, 50, 34, 10, 32, 32, 32, 32, 125, 44, 10, 32, 32, 32, 32, 34, 115, 99, 104, 101, 109, 101, 34, 58, 32, 34, 101, 100, 50, 53, 53, 49, 57, 34, 10, 32, 32, 32, 125, 10, 32, 32, 125, 44, 10, 32, 32, 34, 114, 111, 108, 101, 115, 34, 58, 32, 123, 10, 32, 32, 32, 34, 114, 111, 111, 116, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 34, 52, 53, 97, 57, 53, 55, 55, 99, 97, 52, 56, 51, 102, 51, 53, 56, 98, 100, 97, 52, 97, 50, 49, 97, 102, 57, 51, 98, 55, 54, 54, 48, 98, 56, 50, 98, 100, 57, 99, 48, 101, 49, 57, 51, 48, 97, 54, 98, 55, 100, 53, 50, 49, 98, 52, 50, 56, 57, 55, 97, 48, 102, 97, 51, 34, 44, 10, 32, 32, 32, 32, 32, 34, 52, 99, 53, 54, 100, 101, 53, 98, 54, 50, 102, 100, 48, 54, 52, 102, 99, 57, 52, 52, 53, 51, 98, 54, 56, 48, 100, 102, 100, 51, 102, 97, 102, 54, 97, 48, 49, 98, 97, 97, 97, 98, 51, 98, 101, 97, 99, 50, 57, 54, 57, 50, 48, 102, 48, 99, 99, 102, 97, 50, 50, 55, 55, 53, 34, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 116, 104, 114, 101, 115, 104, 111, 108, 100, 34, 58, 32, 50, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 115, 110, 97, 112, 115, 104, 111, 116, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 34, 57, 100, 55, 56, 53, 52, 51, 98, 53, 48, 56, 102, 57, 57, 97, 57, 53, 97, 51, 99, 51, 49, 102, 97, 100, 51, 99, 102, 102, 101, 102, 48, 54, 52, 52, 49, 51, 52, 102, 49, 97, 48, 50, 56, 98, 51, 48, 53, 48, 49, 97, 99, 99, 49, 50, 48, 53, 56, 99, 55, 99, 51, 101, 56, 34, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 116, 104, 114, 101, 115, 104, 111, 108, 100, 34, 58, 32, 49, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 116, 97, 114, 103, 101, 116, 115, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 34, 56, 102, 51, 99, 50, 55, 57, 52, 102, 50, 52, 52, 50, 54, 48, 49, 52, 102, 99, 50, 54, 97, 100, 99, 98, 98, 56, 101, 102, 100, 57, 52, 52, 49, 49, 102, 99, 101, 56, 56, 49, 102, 97, 54, 48, 102, 99, 56, 55, 50, 53, 97, 56, 57, 57, 49, 49, 53, 55, 53, 48, 101, 102, 97, 34, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 116, 104, 114, 101, 115, 104, 111, 108, 100, 34, 58, 32, 49, 10, 32, 32, 32, 125, 44, 10, 32, 32, 32, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 32, 123, 10, 32, 32, 32, 32, 34, 107, 101, 121, 105, 100, 115, 34, 58, 32, 91, 10, 32, 32, 32, 32, 32, 34, 51, 56, 54, 48, 48, 56, 50, 48, 102, 49, 49, 97, 53, 102, 55, 100, 55, 102, 102, 52, 50, 101, 54, 100, 102, 99, 57, 98, 48, 51, 102, 100, 54, 48, 50, 55, 50, 97, 51, 98, 101, 54, 102, 56, 57, 53, 100, 97, 50, 100, 56, 56, 50, 99, 101, 97, 56, 98, 98, 49, 101, 50, 48, 102, 34, 10, 32, 32, 32, 32, 93, 44, 10, 32, 32, 32, 32, 34, 116, 104, 114, 101, 115, 104, 111, 108, 100, 34, 58, 32, 49, 10, 32, 32, 32, 125, 10, 32, 32, 125, 44, 10, 32, 32, 34, 115, 112, 101, 99, 95, 118, 101, 114, 115, 105, 111, 110, 34, 58, 32, 34, 49, 46, 48, 46, 49, 57, 34, 44, 10, 32, 32, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 32, 49, 44, 10, 32, 32, 34, 120, 45, 116, 117, 102, 114, 101, 112, 111, 45, 101, 120, 112, 105, 114, 121, 45, 112, 101, 114, 105, 111, 100, 34, 58, 32, 56, 54, 52, 48, 48, 10, 32, 125, 10, 125},
40 | wantErr: nil,
41 | },
42 | {
43 | name: "invalid url",
44 | desc: "URL does not exist",
45 | url: "https://somebadtufrepourl.com/metadata/",
46 | data: nil,
47 | wantErr: &url.Error{},
48 | },
49 | {
50 | name: "invalid url format",
51 | desc: "URL is malformed",
52 | url: string([]byte{0x7f}),
53 | data: nil,
54 | wantErr: &url.Error{},
55 | },
56 | {
57 | name: "invalid path",
58 | desc: "Path does not exist",
59 | url: "https://jku.github.io/tuf-demo/metadata/badPath.json",
60 | data: nil,
61 | wantErr: metadata.ErrDownloadHTTP{},
62 | },
63 | {
64 | name: "data too long",
65 | desc: "Returned data is longer than maxLength",
66 | url: "https://jku.github.io/tuf-demo/metadata/1.root.json",
67 | maxLength: 1,
68 | data: nil,
69 | wantErr: metadata.ErrDownloadLengthMismatch{},
70 | },
71 | } {
72 | t.Run(tt.name, func(t *testing.T) {
73 | // this will only be printed if run in verbose mode or if test fails
74 | t.Logf("Desc: %s", tt.desc)
75 | // run the function under test
76 | fetcher := DefaultFetcher{httpUserAgent: "Metadata_Unit_Test/1.0"}
77 | data, err := fetcher.DownloadFile(tt.url, tt.maxLength, tt.timeout)
78 | // special case if we expect no error
79 | if tt.wantErr == nil {
80 | assert.NoErrorf(t, err, "expected no error but got %v", err)
81 | return
82 | }
83 | // compare the error and data with our expected error and data
84 | assert.Equal(t, tt.data, data, "fetched data did not match")
85 | assert.IsTypef(t, tt.wantErr, err, "expected %v but got %v", tt.wantErr, err)
86 | })
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/metadata/keys.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package metadata
13 |
14 | import (
15 | "crypto"
16 | "crypto/ecdsa"
17 | "crypto/ed25519"
18 | "crypto/rsa"
19 | "crypto/sha256"
20 | "crypto/x509"
21 | "encoding/hex"
22 | "fmt"
23 |
24 | "github.com/secure-systems-lab/go-securesystemslib/cjson"
25 | "github.com/sigstore/sigstore/pkg/cryptoutils"
26 | )
27 |
28 | const (
29 | KeyTypeEd25519 = "ed25519"
30 | KeyTypeECDSA_SHA2_P256_COMPAT = "ecdsa-sha2-nistp256"
31 | KeyTypeECDSA_SHA2_P256 = "ecdsa"
32 | KeyTypeRSASSA_PSS_SHA256 = "rsa"
33 | KeySchemeEd25519 = "ed25519"
34 | KeySchemeECDSA_SHA2_P256 = "ecdsa-sha2-nistp256"
35 | KeySchemeRSASSA_PSS_SHA256 = "rsassa-pss-sha256"
36 | )
37 |
38 | // ToPublicKey generate crypto.PublicKey from metadata type Key
39 | func (k *Key) ToPublicKey() (crypto.PublicKey, error) {
40 | switch k.Type {
41 | case KeyTypeRSASSA_PSS_SHA256:
42 | publicKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(k.Value.PublicKey))
43 | if err != nil {
44 | return nil, err
45 | }
46 | rsaKey, ok := publicKey.(*rsa.PublicKey)
47 | if !ok {
48 | return nil, fmt.Errorf("invalid rsa public key")
49 | }
50 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357
51 | if _, err := x509.MarshalPKIXPublicKey(rsaKey); err != nil {
52 | return nil, err
53 | }
54 | return rsaKey, nil
55 | case KeyTypeECDSA_SHA2_P256, KeyTypeECDSA_SHA2_P256_COMPAT: // handle "ecdsa" too as python-tuf/sslib keys are using it for keytype instead of https://theupdateframework.github.io/specification/latest/index.html#keytype-ecdsa-sha2-nistp256
56 | publicKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(k.Value.PublicKey))
57 | if err != nil {
58 | return nil, err
59 | }
60 | ecdsaKey, ok := publicKey.(*ecdsa.PublicKey)
61 | if !ok {
62 | return nil, fmt.Errorf("invalid ecdsa public key")
63 | }
64 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357
65 | if _, err := x509.MarshalPKIXPublicKey(ecdsaKey); err != nil {
66 | return nil, err
67 | }
68 | return ecdsaKey, nil
69 | case KeyTypeEd25519:
70 | publicKey, err := hex.DecodeString(k.Value.PublicKey)
71 | if err != nil {
72 | return nil, err
73 | }
74 | ed25519Key := ed25519.PublicKey(publicKey)
75 | // done for verification - ref. https://github.com/theupdateframework/go-tuf/pull/357
76 | if _, err := x509.MarshalPKIXPublicKey(ed25519Key); err != nil {
77 | return nil, err
78 | }
79 | return ed25519Key, nil
80 | }
81 | return nil, fmt.Errorf("unsupported public key type")
82 | }
83 |
84 | // KeyFromPublicKey generate metadata type Key from crypto.PublicKey
85 | func KeyFromPublicKey(k crypto.PublicKey) (*Key, error) {
86 | key := &Key{}
87 | switch k := k.(type) {
88 | case *rsa.PublicKey:
89 | key.Type = KeyTypeRSASSA_PSS_SHA256
90 | key.Scheme = KeySchemeRSASSA_PSS_SHA256
91 | pemKey, err := cryptoutils.MarshalPublicKeyToPEM(k)
92 | if err != nil {
93 | return nil, err
94 | }
95 | key.Value.PublicKey = string(pemKey)
96 | case *ecdsa.PublicKey:
97 | key.Type = KeyTypeECDSA_SHA2_P256
98 | key.Scheme = KeySchemeECDSA_SHA2_P256
99 | pemKey, err := cryptoutils.MarshalPublicKeyToPEM(k)
100 | if err != nil {
101 | return nil, err
102 | }
103 | key.Value.PublicKey = string(pemKey)
104 | case ed25519.PublicKey:
105 | key.Type = KeyTypeEd25519
106 | key.Scheme = KeySchemeEd25519
107 | key.Value.PublicKey = hex.EncodeToString(k)
108 | default:
109 | return nil, fmt.Errorf("unsupported public key type")
110 | }
111 | return key, nil
112 | }
113 |
114 | // ID returns the keyID value for the given Key
115 | func (k *Key) ID() string {
116 | // the identifier is a hexdigest of the SHA-256 hash of the canonical form of the key
117 | if k.id == "" {
118 | data, err := cjson.EncodeCanonical(k)
119 | if err != nil {
120 | panic(fmt.Errorf("error creating key ID: %w", err))
121 | }
122 | digest := sha256.Sum256(data)
123 | k.id = hex.EncodeToString(digest[:])
124 | }
125 | return k.id
126 | }
127 |
--------------------------------------------------------------------------------
/metadata/logger.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package metadata
13 |
14 | var log Logger = DiscardLogger{}
15 |
16 | // Logger partially implements the go-log/logr's interface:
17 | // https://github.com/go-logr/logr/blob/master/logr.go
18 | type Logger interface {
19 | // Info logs a non-error message with key/value pairs
20 | Info(msg string, kv ...any)
21 | // Error logs an error with a given message and key/value pairs.
22 | Error(err error, msg string, kv ...any)
23 | }
24 |
25 | type DiscardLogger struct{}
26 |
27 | func (d DiscardLogger) Info(msg string, kv ...any) {
28 | }
29 |
30 | func (d DiscardLogger) Error(err error, msg string, kv ...any) {
31 | }
32 |
33 | func SetLogger(logger Logger) {
34 | log = logger
35 | }
36 |
37 | func GetLogger() Logger {
38 | return log
39 | }
40 |
--------------------------------------------------------------------------------
/metadata/logger_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package metadata
13 |
14 | import (
15 | stdlog "log"
16 | "os"
17 | "testing"
18 |
19 | "github.com/go-logr/stdr"
20 | "github.com/stretchr/testify/assert"
21 | )
22 |
23 | func TestSetLogger(t *testing.T) {
24 | // This function is just a simple setter, no need for testing table
25 | testLogger := stdr.New(stdlog.New(os.Stdout, "test", stdlog.LstdFlags))
26 | SetLogger(testLogger)
27 | assert.Equal(t, testLogger, log, "setting package global logger was unsuccessful")
28 | }
29 |
30 | func TestGetLogger(t *testing.T) {
31 | // This function is just a simple getter, no need for testing table
32 | testLogger := GetLogger()
33 | assert.Equal(t, log, testLogger, "function did not return current logger")
34 | }
35 |
--------------------------------------------------------------------------------
/metadata/repository/repository.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package repository
13 |
14 | import (
15 | "github.com/rdimitrov/go-tuf-metadata/metadata"
16 | )
17 |
18 | // repositoryType struct for storing metadata
19 | type repositoryType struct {
20 | root *metadata.Metadata[metadata.RootType]
21 | snapshot *metadata.Metadata[metadata.SnapshotType]
22 | timestamp *metadata.Metadata[metadata.TimestampType]
23 | targets map[string]*metadata.Metadata[metadata.TargetsType]
24 | }
25 |
26 | // New creates an empty repository instance
27 | func New() *repositoryType {
28 | return &repositoryType{
29 | targets: map[string]*metadata.Metadata[metadata.TargetsType]{},
30 | }
31 | }
32 |
33 | // Root returns metadata of type Root
34 | func (r *repositoryType) Root() *metadata.Metadata[metadata.RootType] {
35 | return r.root
36 | }
37 |
38 | // SetRoot sets metadata of type Root
39 | func (r *repositoryType) SetRoot(meta *metadata.Metadata[metadata.RootType]) {
40 | r.root = meta
41 | }
42 |
43 | // Snapshot returns metadata of type Snapshot
44 | func (r *repositoryType) Snapshot() *metadata.Metadata[metadata.SnapshotType] {
45 | return r.snapshot
46 | }
47 |
48 | // SetSnapshot sets metadata of type Snapshot
49 | func (r *repositoryType) SetSnapshot(meta *metadata.Metadata[metadata.SnapshotType]) {
50 | r.snapshot = meta
51 | }
52 |
53 | // Timestamp returns metadata of type Timestamp
54 | func (r *repositoryType) Timestamp() *metadata.Metadata[metadata.TimestampType] {
55 | return r.timestamp
56 | }
57 |
58 | // SetTimestamp sets metadata of type Timestamp
59 | func (r *repositoryType) SetTimestamp(meta *metadata.Metadata[metadata.TimestampType]) {
60 | r.timestamp = meta
61 | }
62 |
63 | // Targets returns metadata of type Targets
64 | func (r *repositoryType) Targets(name string) *metadata.Metadata[metadata.TargetsType] {
65 | return r.targets[name]
66 | }
67 |
68 | // SetTargets sets metadata of type Targets
69 | func (r *repositoryType) SetTargets(name string, meta *metadata.Metadata[metadata.TargetsType]) {
70 | r.targets[name] = meta
71 | }
72 |
--------------------------------------------------------------------------------
/metadata/repository/repository_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package repository
13 |
14 | import (
15 | "testing"
16 | "time"
17 |
18 | "github.com/rdimitrov/go-tuf-metadata/metadata"
19 | "github.com/stretchr/testify/assert"
20 | )
21 |
22 | func TestNewRepository(t *testing.T) {
23 | repo := New()
24 |
25 | now := time.Now().UTC()
26 | safeExpiry := now.Truncate(time.Second).AddDate(0, 0, 30)
27 |
28 | root := metadata.Root(safeExpiry)
29 | repo.SetRoot(root)
30 | assert.Equal(t, "root", repo.Root().Signed.Type)
31 | assert.Equal(t, int64(1), repo.Root().Signed.Version)
32 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Root().Signed.SpecVersion)
33 |
34 | targets := metadata.Targets(safeExpiry)
35 | repo.SetTargets("targets", targets)
36 | assert.Equal(t, "targets", repo.Targets("targets").Signed.Type)
37 | assert.Equal(t, int64(1), repo.Targets("targets").Signed.Version)
38 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Targets("targets").Signed.SpecVersion)
39 |
40 | timestamp := metadata.Timestamp(safeExpiry)
41 | repo.SetTimestamp(timestamp)
42 | // repo.SetRoot(root)
43 | assert.Equal(t, "timestamp", repo.Timestamp().Signed.Type)
44 | assert.Equal(t, int64(1), repo.Timestamp().Signed.Version)
45 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Timestamp().Signed.SpecVersion)
46 |
47 | snapshot := metadata.Snapshot(safeExpiry)
48 | repo.SetSnapshot(snapshot)
49 | assert.Equal(t, "snapshot", repo.Snapshot().Signed.Type)
50 | assert.Equal(t, int64(1), repo.Snapshot().Signed.Version)
51 | assert.Equal(t, metadata.SPECIFICATION_VERSION, repo.Snapshot().Signed.SpecVersion)
52 | }
53 |
--------------------------------------------------------------------------------
/metadata/types.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package metadata
13 |
14 | import (
15 | "encoding/json"
16 | "time"
17 | )
18 |
19 | // Generic type constraint
20 | type Roles interface {
21 | RootType | SnapshotType | TimestampType | TargetsType
22 | }
23 |
24 | // Define version of the TUF specification
25 | const (
26 | SPECIFICATION_VERSION = "1.0.31"
27 | )
28 |
29 | // Define top level role names
30 | const (
31 | ROOT = "root"
32 | SNAPSHOT = "snapshot"
33 | TARGETS = "targets"
34 | TIMESTAMP = "timestamp"
35 | )
36 |
37 | var TOP_LEVEL_ROLE_NAMES = [...]string{ROOT, TIMESTAMP, SNAPSHOT, TARGETS}
38 |
39 | // Metadata[T Roles] represents a TUF metadata.
40 | // Provides methods to read and write to and
41 | // from file and bytes, also to create, verify and clear metadata signatures.
42 | type Metadata[T Roles] struct {
43 | Signed T `json:"signed"`
44 | Signatures []Signature `json:"signatures"`
45 | UnrecognizedFields map[string]any `json:"-"`
46 | }
47 |
48 | // Signature represents the Signature part of a TUF metadata
49 | type Signature struct {
50 | KeyID string `json:"keyid"`
51 | Signature HexBytes `json:"sig"`
52 | UnrecognizedFields map[string]any `json:"-"`
53 | }
54 |
55 | // RootType represents the Signed portion of a root metadata
56 | type RootType struct {
57 | Type string `json:"_type"`
58 | SpecVersion string `json:"spec_version"`
59 | ConsistentSnapshot bool `json:"consistent_snapshot"`
60 | Version int64 `json:"version"`
61 | Expires time.Time `json:"expires"`
62 | Keys map[string]*Key `json:"keys"`
63 | Roles map[string]*Role `json:"roles"`
64 | UnrecognizedFields map[string]any `json:"-"`
65 | }
66 |
67 | // SnapshotType represents the Signed portion of a snapshot metadata
68 | type SnapshotType struct {
69 | Type string `json:"_type"`
70 | SpecVersion string `json:"spec_version"`
71 | Version int64 `json:"version"`
72 | Expires time.Time `json:"expires"`
73 | Meta map[string]*MetaFiles `json:"meta"`
74 | UnrecognizedFields map[string]any `json:"-"`
75 | }
76 |
77 | // TargetsType represents the Signed portion of a targets metadata
78 | type TargetsType struct {
79 | Type string `json:"_type"`
80 | SpecVersion string `json:"spec_version"`
81 | Version int64 `json:"version"`
82 | Expires time.Time `json:"expires"`
83 | Targets map[string]*TargetFiles `json:"targets"`
84 | Delegations *Delegations `json:"delegations,omitempty"`
85 | UnrecognizedFields map[string]any `json:"-"`
86 | }
87 |
88 | // TimestampType represents the Signed portion of a timestamp metadata
89 | type TimestampType struct {
90 | Type string `json:"_type"`
91 | SpecVersion string `json:"spec_version"`
92 | Version int64 `json:"version"`
93 | Expires time.Time `json:"expires"`
94 | Meta map[string]*MetaFiles `json:"meta"`
95 | UnrecognizedFields map[string]any `json:"-"`
96 | }
97 |
98 | // Key represents a key in TUF
99 | type Key struct {
100 | Type string `json:"keytype"`
101 | Scheme string `json:"scheme"`
102 | Value KeyVal `json:"keyval"`
103 | id string `json:"-"`
104 | UnrecognizedFields map[string]any `json:"-"`
105 | }
106 |
107 | type KeyVal struct {
108 | PublicKey string `json:"public"`
109 | UnrecognizedFields map[string]any `json:"-"`
110 | }
111 |
112 | // Role represents one of the top-level roles in TUF
113 | type Role struct {
114 | KeyIDs []string `json:"keyids"`
115 | Threshold int `json:"threshold"`
116 | UnrecognizedFields map[string]any `json:"-"`
117 | }
118 |
119 | type HexBytes []byte
120 |
121 | type Hashes map[string]HexBytes
122 |
123 | // MetaFiles represents the value portion of METAFILES in TUF (used in Snapshot and Timestamp metadata). Used to store information about a particular meta file.
124 | type MetaFiles struct {
125 | Length int64 `json:"length,omitempty"`
126 | Hashes Hashes `json:"hashes,omitempty"`
127 | Version int64 `json:"version"`
128 | UnrecognizedFields map[string]any `json:"-"`
129 | }
130 |
131 | // TargetFiles represents the value portion of TARGETS in TUF (used Targets metadata). Used to store information about a particular target file.
132 | type TargetFiles struct {
133 | Length int64 `json:"length"`
134 | Hashes Hashes `json:"hashes"`
135 | Custom *json.RawMessage `json:"custom,omitempty"`
136 | Path string `json:"-"`
137 | UnrecognizedFields map[string]any `json:"-"`
138 | }
139 |
140 | // Delegations is an optional object which represents delegation roles and their corresponding keys
141 | type Delegations struct {
142 | Keys map[string]*Key `json:"keys"`
143 | Roles []DelegatedRole `json:"roles,omitempty"`
144 | SuccinctRoles *SuccinctRoles `json:"succinct_roles,omitempty"`
145 | UnrecognizedFields map[string]any `json:"-"`
146 | }
147 |
148 | // DelegatedRole represents a delegated role in TUF
149 | type DelegatedRole struct {
150 | Name string `json:"name"`
151 | KeyIDs []string `json:"keyids"`
152 | Threshold int `json:"threshold"`
153 | Terminating bool `json:"terminating"`
154 | PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"`
155 | Paths []string `json:"paths,omitempty"`
156 | UnrecognizedFields map[string]any `json:"-"`
157 | }
158 |
159 | // SuccinctRoles represents a delegation graph that covers all targets,
160 | // distributing them uniformly over the delegated roles (i.e. bins) in the graph.
161 | type SuccinctRoles struct {
162 | KeyIDs []string `json:"keyids"`
163 | Threshold int `json:"threshold"`
164 | BitLength int `json:"bit_length"`
165 | NamePrefix string `json:"name_prefix"`
166 | UnrecognizedFields map[string]any `json:"-"`
167 | }
168 |
--------------------------------------------------------------------------------
/metadata/updater/updater_consistent_snapshot_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package updater
13 |
14 | import (
15 | "testing"
16 |
17 | "github.com/stretchr/testify/assert"
18 |
19 | "github.com/rdimitrov/go-tuf-metadata/metadata"
20 | simulator "github.com/rdimitrov/go-tuf-metadata/testutils/simulator"
21 | )
22 |
23 | func TestTopLevelRolesUpdateWithConsistentSnapshotDisabled(t *testing.T) {
24 | // Test if the client fetches and stores metadata files with the
25 | // correct version prefix when ConsistentSnapshot is false
26 |
27 | err := loadOrResetTrustedRootMetadata()
28 | assert.NoError(t, err)
29 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = false
30 | simulator.Sim.MDRoot.Signed.Version += 1
31 | simulator.Sim.PublishRoot()
32 |
33 | updaterConfig, err := loadUpdaterConfig()
34 | assert.NoError(t, err)
35 | updater := initUpdater(updaterConfig)
36 |
37 | // cleanup fetch tracker metadata
38 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{}
39 | err = updater.Refresh()
40 | assert.NoError(t, err)
41 |
42 | // metadata files are fetched with the expected version (or None)
43 | expectedsnapshotEnabled := []simulator.FTMetadata{
44 | {Name: "root", Value: 2},
45 | {Name: "root", Value: 3},
46 | {Name: "timestamp", Value: -1},
47 | {Name: "snapshot", Value: -1},
48 | {Name: "targets", Value: -1},
49 | }
50 | assert.EqualValues(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata)
51 | // metadata files are always persisted without a version prefix
52 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:])
53 | }
54 |
55 | func TestTopLevelRolesUpdateWithConsistentSnapshotEnabled(t *testing.T) {
56 | // Test if the client fetches and stores metadata files with the
57 | // correct version prefix when ConsistentSnapshot is true
58 |
59 | err := loadOrResetTrustedRootMetadata()
60 | assert.NoError(t, err)
61 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = true
62 | simulator.Sim.MDRoot.Signed.Version += 1
63 | simulator.Sim.PublishRoot()
64 |
65 | updaterConfig, err := loadUpdaterConfig()
66 | assert.NoError(t, err)
67 | updater := initUpdater(updaterConfig)
68 |
69 | // cleanup fetch tracker metadata
70 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{}
71 | err = updater.Refresh()
72 | assert.NoError(t, err)
73 |
74 | // metadata files are fetched with the expected version (or None)
75 | expectedSnapshotDisabled := []simulator.FTMetadata{
76 | {Name: "root", Value: 2},
77 | {Name: "root", Value: 3},
78 | {Name: "timestamp", Value: -1},
79 | {Name: "snapshot", Value: 1},
80 | {Name: "targets", Value: 1},
81 | }
82 | assert.EqualValues(t, expectedSnapshotDisabled, simulator.Sim.FetchTracker.Metadata)
83 | // metadata files are always persisted without a version prefix
84 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:])
85 | }
86 |
87 | func TestDelegatesRolesUpdateWithConsistentSnapshotDisabled(t *testing.T) {
88 | // Test if the client fetches and stores delegated metadata files with
89 | // the correct version prefix when ConsistentSnapshot is false
90 |
91 | err := loadOrResetTrustedRootMetadata()
92 | assert.NoError(t, err)
93 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = false
94 | simulator.Sim.MDRoot.Signed.Version += 1
95 | simulator.Sim.PublishRoot()
96 |
97 | target := metadata.Targets(simulator.Sim.SafeExpiry)
98 |
99 | delegatedRole := metadata.DelegatedRole{
100 | Name: "role1",
101 | KeyIDs: []string{},
102 | Threshold: 1,
103 | Terminating: false,
104 | Paths: []string{"*"},
105 | }
106 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed)
107 |
108 | delegatedRole = metadata.DelegatedRole{
109 | Name: "..",
110 | KeyIDs: []string{},
111 | Threshold: 1,
112 | Terminating: false,
113 | Paths: []string{"*"},
114 | }
115 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed)
116 |
117 | delegatedRole = metadata.DelegatedRole{
118 | Name: ".",
119 | KeyIDs: []string{},
120 | Threshold: 1,
121 | Terminating: false,
122 | Paths: []string{"*"},
123 | }
124 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed)
125 |
126 | simulator.Sim.UpdateSnapshot()
127 | updaterConfig, err := loadUpdaterConfig()
128 | assert.NoError(t, err)
129 | updater := initUpdater(updaterConfig)
130 |
131 | err = updater.Refresh()
132 | assert.NoError(t, err)
133 |
134 | // cleanup fetch tracker metadata
135 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{}
136 | // trigger updater to fetch the delegated metadata
137 | _, err = updater.GetTargetInfo("anything")
138 | assert.ErrorContains(t, err, "target anything not found")
139 |
140 | // metadata files are fetched with the expected version (or None)
141 | expectedsnapshotEnabled := []simulator.FTMetadata{
142 | {Name: "role1", Value: -1},
143 | {Name: "..", Value: -1},
144 | {Name: ".", Value: -1},
145 | }
146 | assert.EqualValues(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata)
147 | // metadata files are always persisted without a version prefix
148 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:])
149 | }
150 |
151 | func TestDelegatesRolesUpdateWithConsistentSnapshotEnabled(t *testing.T) {
152 | // Test if the client fetches and stores delegated metadata files with
153 | // the correct version prefix when ConsistentSnapshot is true
154 |
155 | err := loadOrResetTrustedRootMetadata()
156 | assert.NoError(t, err)
157 | simulator.Sim.MDRoot.Signed.ConsistentSnapshot = true
158 | simulator.Sim.MDRoot.Signed.Version += 1
159 | simulator.Sim.PublishRoot()
160 |
161 | target := metadata.Targets(simulator.Sim.SafeExpiry)
162 |
163 | delegatedRole := metadata.DelegatedRole{
164 | Name: "role1",
165 | KeyIDs: []string{},
166 | Threshold: 1,
167 | Terminating: false,
168 | Paths: []string{"*"},
169 | }
170 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed)
171 |
172 | delegatedRole = metadata.DelegatedRole{
173 | Name: "..",
174 | KeyIDs: []string{},
175 | Threshold: 1,
176 | Terminating: false,
177 | Paths: []string{"*"},
178 | }
179 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed)
180 |
181 | delegatedRole = metadata.DelegatedRole{
182 | Name: ".",
183 | KeyIDs: []string{},
184 | Threshold: 1,
185 | Terminating: false,
186 | Paths: []string{"*"},
187 | }
188 | simulator.Sim.AddDelegation("targets", delegatedRole, target.Signed)
189 |
190 | simulator.Sim.UpdateSnapshot()
191 | updaterConfig, err := loadUpdaterConfig()
192 | assert.NoError(t, err)
193 | updater := initUpdater(updaterConfig)
194 |
195 | err = updater.Refresh()
196 | assert.NoError(t, err)
197 |
198 | // cleanup fetch tracker metadata
199 | simulator.Sim.FetchTracker.Metadata = []simulator.FTMetadata{}
200 | // trigger updater to fetch the delegated metadata
201 | _, err = updater.GetTargetInfo("anything")
202 | assert.ErrorContains(t, err, "target anything not found")
203 |
204 | // metadata files are fetched with the expected version (or None)
205 | expectedsnapshotEnabled := []simulator.FTMetadata{
206 | {Name: "role1", Value: 1},
207 | {Name: "..", Value: 1},
208 | {Name: ".", Value: 1},
209 | }
210 | assert.ElementsMatch(t, expectedsnapshotEnabled, simulator.Sim.FetchTracker.Metadata)
211 | // metadata files are always persisted without a version prefix
212 | assertFilesExist(t, metadata.TOP_LEVEL_ROLE_NAMES[:])
213 | }
214 |
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/delegation_key:
--------------------------------------------------------------------------------
1 | 68593a508472ad3007915379e6b1f3c0@@@@100000@@@@615986af4d1ba89aeadc2f489f89b0e8d46da133a6f75c7b162b8f99f63f86ed@@@@8319255f9856c4f40f9d71bc10e79e5d@@@@1dc7b20f1c668a1f544dc39c7a9fcb3c4a4dd34d1cc8c9d8f779bab026cf0b8e0f46e53bc5ed20bf0e5048b94a5d2ea176e79c12bcc7daa65cd55bf810deebeec5bc903ce9e5316d7dbba88f1a2b51d3f9bc782f8fa9b21dff91609ad0260e21a2039223f816d0fe97ace2e204d0025d327b38d27aa6cd87e85aa8883bfcb6d12f93155d72ffd3c7717a0570cf9811eb6d6a340baa0f27433315d83322c685fec02053ff8c173c4ebf91a258e83402f39546821e3352baa7b246e33b2a573a8ff7b289682407abbcb9184249d4304db68d3bf8e124e94377fd62dde5c4f3b7617d483776345154d047d139b1e559351577da315f54e16153c510159e1908231574bcf49c4f96cafe6530e86a09e9eee47bcff78f2fed2984754c895733938999ff085f9e3532d7174fd76dc09921506dd2137e16ec4926998f5d9df8a8ffb3e6649c71bc32571b2e24357739fa1a56be
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/delegation_key.pub:
--------------------------------------------------------------------------------
1 | {"keyval": {"public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]}
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/root_key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXQIBAAKBgQDydf/VEpxBOCDoxpM6IVhq9i67P9BiVv2zwZSUO/M0RTToAvFv
3 | NgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7WIT+mwy86/fIdnllJDMw5tmL
4 | r2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91iqpdRVa3zJw5ALvLdwIDAQAB
5 | AoGBAJlhwoUVb9nmWxNGw86LV7bapDd6qCX96CL2PDsGLdWMTmrTqc5zuE5NkBZz
6 | z2THvISWIJE/l6gHQJv1uBDbMxfquhK40k+GfE/fApVODN8KeBLLRUzYyHNz7KwW
7 | aNF3jY8AbO4HzWpdaFYce5r+YqlWZoaVPR9i6LCW3sZXALyRAkEA/lSVaT0azp55
8 | 2GI4Gn+EQQFqFJWEbNwJ8i3FZ4aG+/gnw2WmxJr+2nQcUlLb2cpQCCcMyWxvCfLK
9 | +DapvvgZXwJBAPQNd+liOrKKd1gPR3S6y+D4h1ewj8ii1MHzRtAsCKCRG/e+v+hC
10 | xp77Rc/qtZXKvVTGrccnKqCVAvG7F15rzOkCQQDCswgKn6+0+5c1ssNWbcZWaXnH
11 | NktBdxXaI3Ya8d7GaEwwhtIrcqilnfvMfgg2a23nP9XHIU7EI+2EJXy/aHkrAkBH
12 | wH30u9COFW+pEDTt+M1gQzFncp2TW2w56ZB0O739lywl1osNejRzIWURD+x7MbQg
13 | bJlC6Bz8QVMwRtVECWWhAkAflD6eIJeceDhVHClHB/QwmF8mwR1o63RN7ZFlgel1
14 | kwMt6bPZZ1cyrRoj6Cdi4pyqBssDBuQmbBLWyYuijIwz
15 | -----END RSA PRIVATE KEY-----
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/root_key.pub:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDydf/VEpxBOCDoxpM6IVhq9i67
3 | P9BiVv2zwZSUO/M0RTToAvFvNgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7
4 | WIT+mwy86/fIdnllJDMw5tmLr2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91
5 | iqpdRVa3zJw5ALvLdwIDAQAB
6 | -----END PUBLIC KEY-----
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/root_key2:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXwIBAAKBgQC9sG1iNSW+JbnSuITAuWL46YphgqOXU3DhGZp2hM/1FaaRzLlR
3 | HTyH7EvzbKqlaGBs9Caatx6KlN7e8J45vpLO1PkKnLOHdKBuy39Fzxw1dhxJWvTI
4 | 0mNDv68JbWxvgRxjEPE7wc24jYubovxiiBD8g2DbOnWEkyt7owECPRlXAwIDAQAB
5 | AoGBAKquh54oqG9yTsRXF8y6g13p9oRLIpxVjmpduWkPlHe5JYpnphBguEitzKGa
6 | k+oGA03GWr44K5kS33/HDvhyjHFXCqniittlVUwbaa4kbJiWi4lQ3K/m8F2DzHJP
7 | s4YaqmzG30v9j9z3nOgLhM7iye65beala72zJnGOXivUAuhRAkEA3KnfY/SFjTbo
8 | rsluVa03hC5KVQm/yDf4wShDnl8drYHnJ1XFkSC0UbBruRyu8JeWE93dAKu9IxdK
9 | WEdHOtxR3wJBANwQwX/wPJ8/+yo4lToyUuN0omx94sK/JuRmvi9dzCbdfQbkgvDa
10 | MEyWc0LNwxDBPYJ2bej/ORGmD+nOJo59h10CQQCCj/x+jvrKcFfCu6qOBRyZGC6h
11 | HFCebgfAektwFIVh2T/lNUndsgUfZIyIjeEwt/Bzts2CDRuu/KPfkeUifaPvAkEA
12 | m9iR8FTl2bGp4cCojcpNwR88V7DfAiP1GxNX5Jt8lJmOjW8O/BrI0bRKdCjb1+XB
13 | 9b6BH9x/QexkoKOJ0qc7UQJBAINLHep9QG2b3AEGZ692Z+iyU1Lub7rWNBKsmodh
14 | 0x9rwYs0D0EJo0BYozYhExz7ugaSzXW61H26IWbHtsg+5a0=
15 | -----END RSA PRIVATE KEY-----
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/root_key2.pub:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9sG1iNSW+JbnSuITAuWL46Yph
3 | gqOXU3DhGZp2hM/1FaaRzLlRHTyH7EvzbKqlaGBs9Caatx6KlN7e8J45vpLO1PkK
4 | nLOHdKBuy39Fzxw1dhxJWvTI0mNDv68JbWxvgRxjEPE7wc24jYubovxiiBD8g2Db
5 | OnWEkyt7owECPRlXAwIDAQAB
6 | -----END PUBLIC KEY-----
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/snapshot_key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICXAIBAAKBgQCPQoHresXRRRGoinN3bNn+BI23KolXdXLGqYkTvr9AjemUQJxb
3 | qmvZXHboQMAYw8OuBrRNt5Fz20wjsrJwOBEU5U3nHSJI4zYPGckYci0/0Eo2Kjws
4 | 5BmIj38qgIfhsH4zyZ4FZZ+GLRn+W3i3wl6SfRMC/HCg0DDwi75faC0vGQIDAQAB
5 | AoGAbPFYt2hf8qqhqRfQgysmA4QW+QnB895+8BCRC5DtA/xnerQ/s33AEkW8rxY+
6 | fxawQjEbAFbup7pHBoaoJ6qbYbKDBSGgZFSEbh40nriX1V0oYb9E+BCAFHE+42Rj
7 | WYYNxXRp7LGoUQqisTsfoR1bvmrLC+9I/tDArHuMudm1slkCQQDOVn9AKTcaBGuQ
8 | Y+JQqoRmi9eMN6XztKIAKQ+P/57BofwlKJDFnwttsvMxRud6rvN1FCnCDM638HNb
9 | I0JDY0JXAkEAsb10uNV+SaWsHJOxfHzwK+uZJV1SkYzpBMizUREHuIyKT4MfpYNw
10 | kn00KpyCvhIp6buwNyYo76TssejYN86UDwJAGi3ZSU+xYQisiQ5TOX7Y+5XEjFLH
11 | KGuDnleXVOLOxqyBrElATQKH1aw9tMPVPLiTxQgA4FD1rVrBmA+aKaifUwJALBp8
12 | yhh/u7qWWIj1c5R07BEL8U+U23UBpSRACo+VQN/uuggpZCKXXmIe/avUbWGIcO0X
13 | rreTVNOxv/utGzvxVQJBAL7Kpqt9d50SL1ndLr2EdqGw8ZB/B2dKMlZf7AWwbk0k
14 | HHdvWfSDYhtvGo3ilLibHLesE/Tq1fm/2aEOds95/Eo=
15 | -----END RSA PRIVATE KEY-----
16 |
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/snapshot_key.pub:
--------------------------------------------------------------------------------
1 | {"keyval": {"public": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCG5DWxCcw4FW2G21RwTmuR7gdkv+ZrjZVOx0KsvJc/51QBxo/Y9xPVeoFF7YrhE8EV6A6b0qsLufIo1E63sQ6kjLOPfIMjag6dYPlmEyGcbxNDokv2elxZk7jS98iBQLxEmJLicrdERmxC2t2OOEQ6ELi5dt+C13QvNJFg4+OaTwIDAQAB"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]}
2 |
3 |
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/targets_key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICWwIBAAKBgQCjm6HPktvTGsygQ8Gvmu+zydTNe1zqoxLxV7mVRbmsCI4kn7JT
3 | Hc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3w8wqv8cuPxbmsFSxMoHfzBBI
4 | uJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNMDn+9T55B0WJYT/VPxwIDAQAB
5 | AoGANYaYRLHWS1WMNq6UMmBtJZPVlDhU6MrbSqwZojWCjj7qSh8ZF0o8AmiMdDxT
6 | wAJGZ17PyiQY1cQTEVvmaqWIfJKvipAcTvkiXFrAxeIf/HYIVfCP9UB8RqhJufsc
7 | XzDQyvZTmJdatHfKe2JV+q42GrsN4VN61wFEed3NuF8NGjECQQDSA5b+N1wMn5X4
8 | G5fxPYjhlwQmK3tlBHIPIVcVAsGOxU9Ry55xLQ8LpfKwJZIt2+LvgBIXf4DZY2u6
9 | GEnyR7epAkEAx267l7XX+9Dh8bHPluQSgH/tDrCp1hUNmyV4XzZCwavI/FaucANa
10 | h8ChpUOSZTq5mR76YaUL7O3Sx8N7L/2x7wJAZDvgYf6sCT5VhnAtCa+T2A+KpGkW
11 | YLVJdt0zwcxp8ylK3UAwo9Wcm7Oda+LSrN6IpkRa3io1pguki9Ix4NfH2QJATsXA
12 | NxZOb1p8RFk1Y6ZGYJcm7Wx+SN8b9rIAL6thBtpxkqoyUHAirAg8UOi1xGJDuOVx
13 | hGwKn9T4MotV9wi/5QJAB+1/2TaUMKjyL5Ca8Fh5SMigrwHp8SnX2vl7HV4hiBXi
14 | 0FaVxMPGH94tuFqHQ+q53tiTT1cp6YwcMMgpezTRRA==
15 | -----END RSA PRIVATE KEY-----
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/targets_key.pub:
--------------------------------------------------------------------------------
1 | {"keyval": {"public": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjm6HPktvTGsygQ8Gvmu+zydTNe1zqoxLxV7mVRbmsCI4kn7JTHc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3w8wqv8cuPxbmsFSxMoHfzBBIuJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNMDn+9T55B0WJYT/VPxwIDAQAB"}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]}
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/timestamp_key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIICWgIBAAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHPTbyu7OImP6O+uHg8hui8C1nY
3 | /mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVstWpeCalZYWi55lZSLe9KixQIA
4 | yg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C5yqvqzcOoCTQMeWbAgMBAAEC
5 | gYAMlDvAUKS7NZOwCIj62FPDTADW2/juhjfOlcg6n7ItWkAG+3G2n5ndwruATSeY
6 | pNCA3H5+DmVeknlGU9LFvgx7dhJMw3WSkq7rImOGbwLN1jCVfwKP0AEEqb7GrtCU
7 | a9lvm2ZFvKj+2VVFS2yifeluDG1Xm10ygq+RDd2lL2g6eQJBAMZrMTUwxWT/Cc0j
8 | Yi7CFPl9V8GkYzLCKRQGR3x4QiNuXpNtQ3D+ivxHieBMEtw6M244PMDC+GpLxAfc
9 | DtiGEl8CQQCYGXeycwkgn2YfH3w1/Mw6TWsdv4rVLPOieiQPrhZbVsBc6NT24MYW
10 | b3c7osW5ypf7lo+xU8E6ylFUyeeVSk5FAkADTAqwSJQvHnHKP9lEz6LLloKbzCB9
11 | 2m4WUBhmABWRQyc9Keah/QjQMlwfJwR1Nl5eaX7Q8Sxxj7q9KrHwdSHfAkAS1yTC
12 | kAlTZytJM6c5MMVDe4+HMdDKszTCrYqF/rR6P/a4C4dFxXYEFW6ZjoIbj4LgAThv
13 | aMaIt8L3U8NB9OBZAkA3ke4kilnVnjEyB9ibJ/SbDiUgh7e7M/XDbNQuXwSipFft
14 | keBYEwL4Njms9uwMT4Gl59HyQls7BE2XEoiFjsY1
15 | -----END RSA PRIVATE KEY-----
--------------------------------------------------------------------------------
/testutils/repository_data/keystore/timestamp_key.pub:
--------------------------------------------------------------------------------
1 | {"keyval": {"public": "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHPTbyu7OImP6O+uHg8hui8C1nY/mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVstWpeCalZYWi55lZSLe9KixQIAyg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C5yqvqzcOoCTQMeWbAgMBAAE="}, "keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"]}
--------------------------------------------------------------------------------
/testutils/repository_data/repository/metadata/1.root.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7",
5 | "sig": "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "root",
10 | "consistent_snapshot": true,
11 | "expires": "2030-08-15T14:30:45.0000001Z",
12 | "keys": {
13 | "0a5842e65e9c8c428354f40708435de6793ac379a275effe40d6358be2de835c": {
14 | "keytype": "ed25519",
15 | "keyval": {
16 | "public": "4e10fe156f07e6f6e1f6fb1579105b7d3e62790b6a62dbf7727b91f82d2bc9db"
17 | },
18 | "scheme": "ed25519"
19 | },
20 | "409fb816e403e0c00646665eac21cb8adfab8e318272ca7589b2d1fc0bccb255": {
21 | "keytype": "ed25519",
22 | "keyval": {
23 | "public": "23e5dc4eb18d5c116e76a92b02e44a7d7279622574457050b85fb8fd9260422c"
24 | },
25 | "scheme": "ed25519"
26 | },
27 | "700464ea12f4cb5f06a7512c75b73c0b6eeb2cd42854b085eed5b3c993607cba": {
28 | "keytype": "ed25519",
29 | "keyval": {
30 | "public": "1603f99998ca46c35c238a2c1a2a015e0f32b38771e4fa5401348ce0a677d63f"
31 | },
32 | "scheme": "ed25519"
33 | },
34 | "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7": {
35 | "keytype": "ed25519",
36 | "keyval": {
37 | "public": "17454b5e7a6594e7f00ceadda10d0267b94d0118b82f541f4f69f0d327c5a41a"
38 | },
39 | "scheme": "ed25519"
40 | }
41 | },
42 | "roles": {
43 | "root": {
44 | "keyids": [
45 | "d5fa855fce82db75ec64283e828cc90517df5edf5cdc57e7958a890d6556f5b7"
46 | ],
47 | "threshold": 1
48 | },
49 | "snapshot": {
50 | "keyids": [
51 | "700464ea12f4cb5f06a7512c75b73c0b6eeb2cd42854b085eed5b3c993607cba"
52 | ],
53 | "threshold": 1
54 | },
55 | "targets": {
56 | "keyids": [
57 | "409fb816e403e0c00646665eac21cb8adfab8e318272ca7589b2d1fc0bccb255"
58 | ],
59 | "threshold": 1
60 | },
61 | "timestamp": {
62 | "keyids": [
63 | "0a5842e65e9c8c428354f40708435de6793ac379a275effe40d6358be2de835c"
64 | ],
65 | "threshold": 1
66 | }
67 | },
68 | "spec_version": "1.0.31",
69 | "version": 1,
70 | "test": "true"
71 | }
72 | }
--------------------------------------------------------------------------------
/testutils/repository_data/repository/metadata/role1.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a",
5 | "sig": "9408b46569e622a46f1d35d9fa3c10e17a9285631ced4f2c9c2bba2c2842413fcb796db4e81d6f988fc056c21c407fdc3c10441592cf1e837e088f2e2dfd5403"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "targets",
10 | "delegations": {
11 | "keys": {
12 | "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": {
13 | "keyid_hash_algorithms": [
14 | "sha256",
15 | "sha512"
16 | ],
17 | "keytype": "ed25519",
18 | "keyval": {
19 | "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9"
20 | },
21 | "scheme": "ed25519"
22 | }
23 | },
24 | "roles": [
25 | {
26 | "keyids": [
27 | "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a"
28 | ],
29 | "name": "role2",
30 | "paths": [],
31 | "terminating": false,
32 | "threshold": 1
33 | }
34 | ]
35 | },
36 | "expires": "2030-01-01T00:00:00Z",
37 | "spec_version": "1.0.0",
38 | "targets": {
39 | "file3.txt": {
40 | "hashes": {
41 | "sha256": "141f740f53781d1ca54b8a50af22cbf74e44c21a998fa2a8a05aaac2c002886b",
42 | "sha512": "ef5beafa16041bcdd2937140afebd485296cd54f7348ecd5a4d035c09759608de467a7ac0eb58753d0242df873c305e8bffad2454aa48f44480f15efae1cacd0"
43 | },
44 | "length": 28
45 | }
46 | },
47 | "version": 1
48 | }
49 | }
--------------------------------------------------------------------------------
/testutils/repository_data/repository/metadata/role2.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a",
5 | "sig": "75b196a224fd200e46e738b1216b3316c5384f61083872f8d14b8b0a378b2344e64b1a6f1a89a711206a66a0b199d65ac0e30fe15ddbc4de89fa8ff645f99403"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "targets",
10 | "expires": "2030-01-01T00:00:00Z",
11 | "spec_version": "1.0.0",
12 | "targets": {},
13 | "version": 1
14 | }
15 | }
--------------------------------------------------------------------------------
/testutils/repository_data/repository/metadata/root.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2",
5 | "sig": "d0283ac0653e324ce132e47a518f8a1539b59430efe5cdec58ec53f824bec28628b57dd5fb2452bde83fc8f5d11ab0b7350a9bbcbefc7acc6c447785545fa1e36f1352c9e20dd1ebcc3ab16a2a7ff702e32e481ceba88e0f348dc2cddd26ca577445d00c7194e8656d901fd2382c479555af93a64eef48cf79cdff6ecdcd7cb7"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "root",
10 | "consistent_snapshot": true,
11 | "expires": "2030-08-15T14:30:45.0000001Z",
12 | "keys": {
13 | "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946": {
14 | "keytype": "rsa",
15 | "keyval": {
16 | "public": "-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHXjYnWGuCIOh5T3XGmgG/RsXWHP\nTbyu7OImP6O+uHg8hui8C1nY/mcJdFdxqgl1vKEco/Nwebh2T8L6XbNfcgV9VVst\nWpeCalZYWi55lZSLe9KixQIAyg15rNdhN9pcD3OuLmFvslgTx+dTbZ3ZoYMbcb4C\n5yqvqzcOoCTQMeWbAgMBAAE=\n-----END PUBLIC KEY-----\n"
17 | },
18 | "scheme": "rsassa-pss-sha256"
19 | },
20 | "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d": {
21 | "keytype": "rsa",
22 | "keyval": {
23 | "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjm6HPktvTGsygQ8Gvmu+zydTN\ne1zqoxLxV7mVRbmsCI4kn7JTHc4fmWZwvo7f/Wbto6Xj5HqGJFSlYIGZuTwZqPg3\nw8wqv8cuPxbmsFSxMoHfzBBIuJe0FlwXFysojbdhrSUqNL84tlwTFXEhePYrpTNM\nDn+9T55B0WJYT/VPxwIDAQAB\n-----END PUBLIC KEY-----\n"
24 | },
25 | "scheme": "rsassa-pss-sha256"
26 | },
27 | "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2": {
28 | "keytype": "rsa",
29 | "keyval": {
30 | "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDydf/VEpxBOCDoxpM6IVhq9i67\nP9BiVv2zwZSUO/M0RTToAvFvNgDKXwtnp8LyjVk++wMA1aceMa+pS7vYrKvPIJa7\nWIT+mwy86/fIdnllJDMw5tmLr2mE3oBMxOhpEiD2tO+liGacklFNk6nHHorX9S91\niqpdRVa3zJw5ALvLdwIDAQAB\n-----END PUBLIC KEY-----\n"
31 | },
32 | "scheme": "rsassa-pss-sha256"
33 | },
34 | "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6": {
35 | "keytype": "rsa",
36 | "keyval": {
37 | "public": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCPQoHresXRRRGoinN3bNn+BI23\nKolXdXLGqYkTvr9AjemUQJxbqmvZXHboQMAYw8OuBrRNt5Fz20wjsrJwOBEU5U3n\nHSJI4zYPGckYci0/0Eo2Kjws5BmIj38qgIfhsH4zyZ4FZZ+GLRn+W3i3wl6SfRMC\n/HCg0DDwi75faC0vGQIDAQAB\n-----END PUBLIC KEY-----\n"
38 | },
39 | "scheme": "rsassa-pss-sha256"
40 | }
41 | },
42 | "roles": {
43 | "root": {
44 | "keyids": [
45 | "74b58be26a6ff00ab2eec9b14da29038591a69c212223033f4efdf24489913f2"
46 | ],
47 | "threshold": 1
48 | },
49 | "snapshot": {
50 | "keyids": [
51 | "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6"
52 | ],
53 | "threshold": 1
54 | },
55 | "targets": {
56 | "keyids": [
57 | "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d"
58 | ],
59 | "threshold": 1
60 | },
61 | "timestamp": {
62 | "keyids": [
63 | "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946"
64 | ],
65 | "threshold": 1
66 | }
67 | },
68 | "spec_version": "1.0.31",
69 | "version": 1
70 | }
71 | }
--------------------------------------------------------------------------------
/testutils/repository_data/repository/metadata/snapshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "8a14f637b21578cc292a67899df0e46cc160d7fd56e9beae898adb666f4fd9d6",
5 | "sig": "3075fe9ef3008603eb0531500a93101b8f7eb52b07ce63fb71abaffd5eb20784bcab888abfca8041798b13dd35c6e18ff4a64d536161c4d5e7535f006edec3a46c71684a632269222da82d50bf380e20eb477032e45df0b44af9e1dc46f25cd72f9901b4fc41b90869649b6257a66188b61b83c7295baf16f113e9cc4d39b3a6"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "snapshot",
10 | "expires": "2030-08-15T14:30:45.0000001Z",
11 | "meta": {
12 | "role1.json": {
13 | "version": 1
14 | },
15 | "role2.json": {
16 | "version": 1
17 | },
18 | "targets.json": {
19 | "version": 1
20 | }
21 | },
22 | "spec_version": "1.0.31",
23 | "version": 1
24 | }
25 | }
--------------------------------------------------------------------------------
/testutils/repository_data/repository/metadata/targets.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "282612f348dcd7fe3f19e0f890e89fad48d45335deeb91deef92873934e6fe6d",
5 | "sig": "80cd125a4b128c9508df8bc6f71ad2ed9896a9e7afccd53fca9e7dbc2f02db69c3ae712234d3730c929d891fa035bdf059736e7debf62cbac6f0e8d22ab0c5de3b3e47b249eb0d41dea66d9fda9588893cde824a95614129263b6fed72fafb21cd7114e603fe3a30e3871e9eb5b5029e3e9a8353190f1bcb332a81ec211a93eb"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "targets",
10 | "delegations": {
11 | "keys": {
12 | "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a": {
13 | "keyid_hash_algorithms": [
14 | "sha256",
15 | "sha512"
16 | ],
17 | "keytype": "ed25519",
18 | "keyval": {
19 | "public": "fcf224e55fa226056adf113ef1eb3d55e308b75b321c8c8316999d8c4fd9e0d9"
20 | },
21 | "scheme": "ed25519"
22 | }
23 | },
24 | "roles": [
25 | {
26 | "keyids": [
27 | "c8022fa1e9b9cb239a6b362bbdffa9649e61ad2cb699d2e4bc4fdf7930a0e64a"
28 | ],
29 | "name": "role1",
30 | "paths": [
31 | "file3.txt"
32 | ],
33 | "terminating": false,
34 | "threshold": 1
35 | }
36 | ]
37 | },
38 | "expires": "2030-08-15T14:30:45.0000001Z",
39 | "spec_version": "1.0.31",
40 | "targets": {
41 | "file1.txt": {
42 | "hashes": {
43 | "sha256": "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da"
44 | },
45 | "length": 31
46 | }
47 | },
48 | "version": 1
49 | }
50 | }
--------------------------------------------------------------------------------
/testutils/repository_data/repository/metadata/timestamp.json:
--------------------------------------------------------------------------------
1 | {
2 | "signatures": [
3 | {
4 | "keyid": "142919f8e933d7045abff3be450070057814da36331d7a22ccade8b35a9e3946",
5 | "sig": "639c9ce3dbb705265b5e9ad6d67fea2b38780c48ff7917e372adace8e50a7a2f054383d5960457a113059be521b8ce7e6d8a5787c600c4850b8c0ed1ae17a931a6bfe794476e7824c6f53df5232561e0a2e146b11dde7889b397c6f8136e2105bbb21b4b59b5addc032a0e755d97e531255f3b458d474184168541e542626e81"
6 | }
7 | ],
8 | "signed": {
9 | "_type": "timestamp",
10 | "expires": "2030-08-15T14:30:45.0000001Z",
11 | "meta": {
12 | "snapshot.json": {
13 | "version": 1
14 | }
15 | },
16 | "spec_version": "1.0.31",
17 | "version": 1
18 | }
19 | }
--------------------------------------------------------------------------------
/testutils/repository_data/repository/targets/file1.txt:
--------------------------------------------------------------------------------
1 | This is an example target file.
--------------------------------------------------------------------------------
/testutils/repository_data/repository/targets/file2.txt:
--------------------------------------------------------------------------------
1 | This is an another example target file.
--------------------------------------------------------------------------------
/testutils/repository_data/repository/targets/file3.txt:
--------------------------------------------------------------------------------
1 | This is role1's target file.
--------------------------------------------------------------------------------
/testutils/simulator/repository_simulator_setup.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package simulator
13 |
14 | import (
15 | "os"
16 | "time"
17 |
18 | log "github.com/sirupsen/logrus"
19 | )
20 |
21 | var (
22 | MetadataURL = "https://jku.github.io/tuf-demo/metadata"
23 | TargetsURL = "https://jku.github.io/tuf-demo/targets"
24 |
25 | MetadataDir string
26 | RootBytes []byte
27 | PastDateTime time.Time
28 | Sim *RepositorySimulator
29 |
30 | metadataPath = "/metadata"
31 | targetsPath = "/targets"
32 | LocalDir string
33 | DumpDir string
34 | )
35 |
36 | func InitLocalEnv() error {
37 |
38 | tmp := os.TempDir()
39 |
40 | tmpDir, err := os.MkdirTemp(tmp, "0750")
41 | if err != nil {
42 | log.Fatal("failed to create temporary directory: ", err)
43 | }
44 |
45 | err = os.Mkdir(tmpDir+metadataPath, 0750)
46 | if err != nil {
47 | log.Debugf("repository simulator: failed to create dir: %v", err)
48 | }
49 | err = os.Mkdir(tmpDir+targetsPath, 0750)
50 | if err != nil {
51 | log.Debugf("repository simulator: failed to create dir: %v", err)
52 | }
53 | LocalDir = tmpDir
54 | return nil
55 | }
56 |
57 | func InitMetadataDir() (*RepositorySimulator, string, string, error) {
58 | err := InitLocalEnv()
59 | if err != nil {
60 | log.Fatal("failed to initialize environment: ", err)
61 | }
62 | metadataDir := LocalDir + metadataPath
63 |
64 | sim := NewRepository()
65 |
66 | f, err := os.Create(metadataDir + "/root.json")
67 | if err != nil {
68 | log.Fatalf("failed to create root: %v", err)
69 | }
70 |
71 | _, err = f.Write(sim.SignedRoots[0])
72 | if err != nil {
73 | log.Debugf("repository simulator setup: failed to write signed roots: %v", err)
74 | }
75 | targetsDir := LocalDir + targetsPath
76 | sim.LocalDir = LocalDir
77 | return sim, metadataDir, targetsDir, err
78 | }
79 |
80 | func GetRootBytes(localMetadataDir string) ([]byte, error) {
81 | return os.ReadFile(localMetadataDir + "/root.json")
82 | }
83 |
84 | func RepositoryCleanup(tmpDir string) {
85 | log.Printf("Cleaning temporary directory: %s\n", tmpDir)
86 | os.RemoveAll(tmpDir)
87 | }
88 |
--------------------------------------------------------------------------------
/testutils/testutils/setup.go:
--------------------------------------------------------------------------------
1 | // Copyright 2023 VMware, Inc.
2 | //
3 | // This product is licensed to you under the BSD-2 license (the "License").
4 | // You may not use this product except in compliance with the BSD-2 License.
5 | // This product may include a number of subcomponents with separate copyright
6 | // notices and license terms. Your use of these subcomponents is subject to
7 | // the terms and conditions of the subcomponent's license, as noted in the
8 | // LICENSE file.
9 | //
10 | // SPDX-License-Identifier: BSD-2-Clause
11 |
12 | package testutils
13 |
14 | import (
15 | "fmt"
16 | "log"
17 | "os"
18 | "path/filepath"
19 | )
20 |
21 | var (
22 | TempDir string
23 | RepoDir string
24 | TargetsDir string
25 | KeystoreDir string
26 | )
27 |
28 | func SetupTestDirs(repoPath string, targetsPath string, keystorePath string) error {
29 | tmp := os.TempDir()
30 | var err error
31 | TempDir, err = os.MkdirTemp(tmp, "0750")
32 | if err != nil {
33 | return fmt.Errorf("failed to create temporary directory: %w", err)
34 | }
35 |
36 | RepoDir = fmt.Sprintf("%s/repository_data/repository", TempDir)
37 | absPath, err := filepath.Abs(repoPath)
38 | if err != nil {
39 | return fmt.Errorf("failed to get absolute path: %w", err)
40 | }
41 | err = Copy(absPath, RepoDir)
42 | if err != nil {
43 | return fmt.Errorf("failed to copy metadata to %s: %w", RepoDir, err)
44 | }
45 |
46 | TargetsDir = fmt.Sprintf("%s/repository_data/repository/targets", TempDir)
47 | targetsAbsPath, err := filepath.Abs(targetsPath)
48 | if err != nil {
49 | return fmt.Errorf("failed to get absolute targets path: %w", err)
50 | }
51 | err = Copy(targetsAbsPath, TargetsDir)
52 | if err != nil {
53 | return fmt.Errorf("failed to copy metadata to %s: %w", RepoDir, err)
54 | }
55 |
56 | KeystoreDir = fmt.Sprintf("%s/keystore", TempDir)
57 | err = os.Mkdir(KeystoreDir, 0750)
58 | if err != nil {
59 | return fmt.Errorf("failed to create keystore dir %s: %w", KeystoreDir, err)
60 | }
61 | absPath, err = filepath.Abs(keystorePath)
62 | if err != nil {
63 | return fmt.Errorf("failed to get absolute path: %w", err)
64 | }
65 | err = Copy(absPath, KeystoreDir)
66 | if err != nil {
67 | return fmt.Errorf("failed to copy keystore to %s: %w", KeystoreDir, err)
68 | }
69 |
70 | return nil
71 | }
72 |
73 | func Copy(fromPath string, toPath string) error {
74 | err := os.MkdirAll(toPath, 0750)
75 | if err != nil {
76 | return fmt.Errorf("failed to create directory %s: %w", toPath, err)
77 | }
78 | files, err := os.ReadDir(fromPath)
79 | if err != nil {
80 | return fmt.Errorf("failed to read path %s: %w", fromPath, err)
81 | }
82 | for _, file := range files {
83 | data, err := os.ReadFile(fmt.Sprintf("%s/%s", fromPath, file.Name()))
84 | if err != nil {
85 | return fmt.Errorf("failed to read file %s: %w", file.Name(), err)
86 | }
87 | filePath := fmt.Sprintf("%s/%s", toPath, file.Name())
88 | err = os.WriteFile(filePath, data, 0750)
89 | if err != nil {
90 | return fmt.Errorf("failed to write file %s: %w", filePath, err)
91 | }
92 | }
93 | return nil
94 | }
95 |
96 | func Cleanup() {
97 | log.Printf("cleaning temporary directory: %s\n", TempDir)
98 | err := os.RemoveAll(TempDir)
99 | if err != nil {
100 | log.Fatalf("failed to cleanup test directories: %v", err)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------